markmap-view
Version:
View markmaps in browser
1,428 lines • 52 kB
JavaScript
(function(exports, d32) {
"use strict";
class Hook {
constructor() {
this.listeners = [];
}
tap(fn) {
this.listeners.push(fn);
return () => this.revoke(fn);
}
revoke(fn) {
const i = this.listeners.indexOf(fn);
if (i >= 0) this.listeners.splice(i, 1);
}
revokeAll() {
this.listeners.splice(0);
}
call(...args) {
for (const fn of this.listeners) {
fn(...args);
}
}
}
const uniqId = Math.random().toString(36).slice(2, 8);
let globalIndex = 0;
function getId() {
globalIndex += 1;
return `mm-${uniqId}-${globalIndex}`;
}
function noop() {
}
function walkTree(tree, callback) {
const walk = (item, parent) => callback(
item,
() => {
var _a;
return (_a = item.children) == null ? void 0 : _a.map((child) => walk(child, item));
},
parent
);
return walk(tree);
}
function addClass(className, ...rest) {
const classList = (className || "").split(" ").filter(Boolean);
rest.forEach((item) => {
if (item && classList.indexOf(item) < 0) classList.push(item);
});
return classList.join(" ");
}
function defer() {
const obj = {};
obj.promise = new Promise((resolve, reject) => {
obj.resolve = resolve;
obj.reject = reject;
});
return obj;
}
function memoize(fn) {
const cache = {};
return function memoized(...args) {
const key = `${args[0]}`;
let data = cache[key];
if (!data) {
data = {
value: fn(...args)
};
cache[key] = data;
}
return data.value;
};
}
function debounce(fn, time) {
const state = {
timer: 0
};
function reset() {
if (state.timer) {
window.clearTimeout(state.timer);
state.timer = 0;
}
}
function run() {
reset();
if (state.args) state.result = fn(...state.args);
}
return function debounced(...args) {
reset();
state.args = args;
state.timer = window.setTimeout(run, time);
return state.result;
};
}
/*! @gera2ld/jsx-dom v2.2.2 | ISC License */
const VTYPE_ELEMENT = 1;
const VTYPE_FUNCTION = 2;
const SVG_NS = "http://www.w3.org/2000/svg";
const XLINK_NS = "http://www.w3.org/1999/xlink";
const NS_ATTRS = {
show: XLINK_NS,
actuate: XLINK_NS,
href: XLINK_NS
};
const isLeaf = (c) => typeof c === "string" || typeof c === "number";
const isElement = (c) => (c == null ? void 0 : c.vtype) === VTYPE_ELEMENT;
const isRenderFunction = (c) => (c == null ? void 0 : c.vtype) === VTYPE_FUNCTION;
function h(type, props, ...children) {
props = Object.assign({}, props, {
children: children.length === 1 ? children[0] : children
});
return jsx(type, props);
}
function jsx(type, props) {
let vtype;
if (typeof type === "string") vtype = VTYPE_ELEMENT;
else if (typeof type === "function") vtype = VTYPE_FUNCTION;
else throw new Error("Invalid VNode type");
return {
vtype,
type,
props
};
}
function Fragment(props) {
return props.children;
}
const DEFAULT_ENV = {
isSvg: false
};
function insertDom(parent, nodes) {
if (!Array.isArray(nodes)) nodes = [nodes];
nodes = nodes.filter(Boolean);
if (nodes.length) parent.append(...nodes);
}
function mountAttributes(domElement, props, env) {
for (const key in props) {
if (key === "key" || key === "children" || key === "ref") continue;
if (key === "dangerouslySetInnerHTML") {
domElement.innerHTML = props[key].__html;
} else if (key === "innerHTML" || key === "textContent" || key === "innerText" || key === "value" && ["textarea", "select"].includes(domElement.tagName)) {
const value = props[key];
if (value != null) domElement[key] = value;
} else if (key.startsWith("on")) {
domElement[key.toLowerCase()] = props[key];
} else {
setDOMAttribute(domElement, key, props[key], env.isSvg);
}
}
}
const attrMap = {
className: "class",
labelFor: "for"
};
function setDOMAttribute(el, attr, value, isSVG) {
attr = attrMap[attr] || attr;
if (value === true) {
el.setAttribute(attr, "");
} else if (value === false) {
el.removeAttribute(attr);
} else {
const namespace = isSVG ? NS_ATTRS[attr] : void 0;
if (namespace !== void 0) {
el.setAttributeNS(namespace, attr, value);
} else {
el.setAttribute(attr, value);
}
}
}
function flatten(arr) {
return arr.reduce((prev, item) => prev.concat(item), []);
}
function mountChildren(children, env) {
return Array.isArray(children) ? flatten(children.map((child) => mountChildren(child, env))) : mount(children, env);
}
function mount(vnode, env = DEFAULT_ENV) {
if (vnode == null || typeof vnode === "boolean") {
return null;
}
if (vnode instanceof Node) {
return vnode;
}
if (isRenderFunction(vnode)) {
const {
type,
props
} = vnode;
if (type === Fragment) {
const node = document.createDocumentFragment();
if (props.children) {
const children = mountChildren(props.children, env);
insertDom(node, children);
}
return node;
}
const childVNode = type(props);
return mount(childVNode, env);
}
if (isLeaf(vnode)) {
return document.createTextNode(`${vnode}`);
}
if (isElement(vnode)) {
let node;
const {
type,
props
} = vnode;
if (!env.isSvg && type === "svg") {
env = Object.assign({}, env, {
isSvg: true
});
}
if (!env.isSvg) {
node = document.createElement(type);
} else {
node = document.createElementNS(SVG_NS, type);
}
mountAttributes(node, props, env);
if (props.children) {
let childEnv = env;
if (env.isSvg && type === "foreignObject") {
childEnv = Object.assign({}, childEnv, {
isSvg: false
});
}
const children = mountChildren(props.children, childEnv);
if (children != null) insertDom(node, children);
}
const {
ref
} = props;
if (typeof ref === "function") ref(node);
return node;
}
throw new Error("mount: Invalid Vnode!");
}
function mountDom(vnode) {
return mount(vnode);
}
function hm(...args) {
return mountDom(h(...args));
}
const memoizedPreloadJS = memoize((url) => {
document.head.append(
hm("link", {
rel: "preload",
as: "script",
href: url
})
);
});
const jsCache = {};
const cssCache = {};
async function loadJSItem(item, context) {
var _a;
const src = item.type === "script" && ((_a = item.data) == null ? void 0 : _a.src) || "";
item.loaded || (item.loaded = jsCache[src]);
if (!item.loaded) {
const deferred = defer();
item.loaded = deferred.promise;
if (item.type === "script") {
document.head.append(
hm("script", {
...item.data,
onLoad: () => deferred.resolve(),
onError: deferred.reject
})
);
if (!src) {
deferred.resolve();
} else {
jsCache[src] = item.loaded;
}
}
if (item.type === "iife") {
const { fn, getParams } = item.data;
fn(...(getParams == null ? void 0 : getParams(context)) || []);
deferred.resolve();
}
}
await item.loaded;
}
async function loadCSSItem(item) {
const url = item.type === "stylesheet" && item.data.href || "";
item.loaded || (item.loaded = cssCache[url]);
if (!item.loaded) {
const deferred = defer();
item.loaded = deferred.promise;
if (url) cssCache[url] = item.loaded;
if (item.type === "style") {
document.head.append(
hm("style", {
textContent: item.data
})
);
deferred.resolve();
} else if (url) {
document.head.append(
hm("link", {
rel: "stylesheet",
...item.data
})
);
fetch(url).then((res) => {
if (res.ok) return res.text();
throw res;
}).then(() => deferred.resolve(), deferred.reject);
}
}
await item.loaded;
}
async function loadJS(items, context) {
items.forEach((item) => {
var _a;
if (item.type === "script" && ((_a = item.data) == null ? void 0 : _a.src)) {
memoizedPreloadJS(item.data.src);
}
});
context = {
getMarkmap: () => window.markmap,
...context
};
for (const item of items) {
await loadJSItem(item, context);
}
}
async function loadCSS(items) {
await Promise.all(items.map((item) => loadCSSItem(item)));
}
const isMacintosh = typeof navigator !== "undefined" && navigator.userAgent.includes("Macintosh");
const defaultColorFn = d32.scaleOrdinal(d32.schemeCategory10);
const lineWidthFactory = (baseWidth = 1, deltaWidth = 3, k = 2) => (node) => baseWidth + deltaWidth / k ** node.state.depth;
const defaultOptions = {
autoFit: false,
duration: 500,
embedGlobalCSS: true,
fitRatio: 0.95,
maxInitialScale: 2,
scrollForPan: isMacintosh,
initialExpandLevel: -1,
zoom: true,
pan: true,
toggleRecursively: false,
color: (node) => {
var _a;
return defaultColorFn(`${((_a = node.state) == null ? void 0 : _a.path) || ""}`);
},
lineWidth: lineWidthFactory(),
maxWidth: 0,
nodeMinHeight: 16,
paddingX: 8,
spacingHorizontal: 80,
spacingVertical: 5
};
function deriveOptions(jsonOptions) {
const derivedOptions = {};
const options = { ...jsonOptions };
const { color, colorFreezeLevel, lineWidth } = options;
if ((color == null ? void 0 : color.length) === 1) {
const solidColor = color[0];
derivedOptions.color = () => solidColor;
} else if (color == null ? void 0 : color.length) {
const colorFn = d32.scaleOrdinal(color);
derivedOptions.color = (node) => colorFn(`${node.state.path}`);
}
if (colorFreezeLevel) {
const color2 = derivedOptions.color || defaultOptions.color;
derivedOptions.color = (node) => {
node = {
...node,
state: {
...node.state,
path: node.state.path.split(".").slice(0, colorFreezeLevel).join(".")
}
};
return color2(node);
};
}
if (lineWidth) {
const args = Array.isArray(lineWidth) ? lineWidth : [lineWidth, 0, 1];
derivedOptions.lineWidth = lineWidthFactory(
...args
);
}
const numberKeys = [
"duration",
"fitRatio",
"initialExpandLevel",
"maxInitialScale",
"maxWidth",
"nodeMinHeight",
"paddingX",
"spacingHorizontal",
"spacingVertical"
];
numberKeys.forEach((key) => {
const value = options[key];
if (typeof value === "number") derivedOptions[key] = value;
});
const booleanKeys = ["zoom", "pan"];
booleanKeys.forEach((key) => {
const value = options[key];
if (value != null) derivedOptions[key] = !!value;
});
return derivedOptions;
}
function simpleHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = (hash << 5) - hash + str.charCodeAt(i) | 0;
}
return (hash >>> 0).toString(36);
}
function childSelector(filter) {
if (typeof filter === "string") {
const selector = filter;
filter = (el) => el.matches(selector);
}
const filterFn = filter;
return function selector() {
let nodes = Array.from(this.childNodes);
if (filterFn) nodes = nodes.filter((node) => filterFn(node));
return nodes;
};
}
function count(node) {
var sum = 0, children = node.children, i = children && children.length;
if (!i) sum = 1;
else while (--i >= 0) sum += children[i].value;
node.value = sum;
}
function node_count() {
return this.eachAfter(count);
}
function node_each(callback) {
var node = this, current, next = [node], children, i, n;
do {
current = next.reverse(), next = [];
while (node = current.pop()) {
callback(node), children = node.children;
if (children) for (i = 0, n = children.length; i < n; ++i) {
next.push(children[i]);
}
}
} while (next.length);
return this;
}
function node_eachBefore(callback) {
var node = this, nodes = [node], children, i;
while (node = nodes.pop()) {
callback(node), children = node.children;
if (children) for (i = children.length - 1; i >= 0; --i) {
nodes.push(children[i]);
}
}
return this;
}
function node_eachAfter(callback) {
var node = this, nodes = [node], next = [], children, i, n;
while (node = nodes.pop()) {
next.push(node), children = node.children;
if (children) for (i = 0, n = children.length; i < n; ++i) {
nodes.push(children[i]);
}
}
while (node = next.pop()) {
callback(node);
}
return this;
}
function node_sum(value) {
return this.eachAfter(function(node) {
var sum = +value(node.data) || 0, children = node.children, i = children && children.length;
while (--i >= 0) sum += children[i].value;
node.value = sum;
});
}
function node_sort(compare) {
return this.eachBefore(function(node) {
if (node.children) {
node.children.sort(compare);
}
});
}
function node_path(end) {
var start = this, ancestor = leastCommonAncestor(start, end), nodes = [start];
while (start !== ancestor) {
start = start.parent;
nodes.push(start);
}
var k = nodes.length;
while (end !== ancestor) {
nodes.splice(k, 0, end);
end = end.parent;
}
return nodes;
}
function leastCommonAncestor(a, b) {
if (a === b) return a;
var aNodes = a.ancestors(), bNodes = b.ancestors(), c = null;
a = aNodes.pop();
b = bNodes.pop();
while (a === b) {
c = a;
a = aNodes.pop();
b = bNodes.pop();
}
return c;
}
function node_ancestors() {
var node = this, nodes = [node];
while (node = node.parent) {
nodes.push(node);
}
return nodes;
}
function node_descendants() {
var nodes = [];
this.each(function(node) {
nodes.push(node);
});
return nodes;
}
function node_leaves() {
var leaves = [];
this.eachBefore(function(node) {
if (!node.children) {
leaves.push(node);
}
});
return leaves;
}
function node_links() {
var root = this, links = [];
root.each(function(node) {
if (node !== root) {
links.push({ source: node.parent, target: node });
}
});
return links;
}
function hierarchy(data, children) {
var root = new Node$1(data), valued = +data.value && (root.value = data.value), node, nodes = [root], child, childs, i, n;
if (children == null) children = defaultChildren;
while (node = nodes.pop()) {
if (valued) node.value = +node.data.value;
if ((childs = children(node.data)) && (n = childs.length)) {
node.children = new Array(n);
for (i = n - 1; i >= 0; --i) {
nodes.push(child = node.children[i] = new Node$1(childs[i]));
child.parent = node;
child.depth = node.depth + 1;
}
}
}
return root.eachBefore(computeHeight);
}
function node_copy() {
return hierarchy(this).eachBefore(copyData);
}
function defaultChildren(d) {
return d.children;
}
function copyData(node) {
node.data = node.data.data;
}
function computeHeight(node) {
var height = 0;
do
node.height = height;
while ((node = node.parent) && node.height < ++height);
}
function Node$1(data) {
this.data = data;
this.depth = this.height = 0;
this.parent = null;
}
Node$1.prototype = hierarchy.prototype = {
constructor: Node$1,
count: node_count,
each: node_each,
eachAfter: node_eachAfter,
eachBefore: node_eachBefore,
sum: node_sum,
sort: node_sort,
path: node_path,
ancestors: node_ancestors,
descendants: node_descendants,
leaves: node_leaves,
links: node_links,
copy: node_copy
};
const name = "d3-flextree";
const version$1 = "2.1.2";
const main = "build/d3-flextree.js";
const module = "index";
const author = { "name": "Chris Maloney", "url": "http://chrismaloney.org" };
const description = "Flexible tree layout algorithm that allows for variable node sizes.";
const keywords = ["d3", "d3-module", "layout", "tree", "hierarchy", "d3-hierarchy", "plugin", "d3-plugin", "infovis", "visualization", "2d"];
const homepage = "https://github.com/klortho/d3-flextree";
const license = "WTFPL";
const repository = { "type": "git", "url": "https://github.com/klortho/d3-flextree.git" };
const scripts = { "clean": "rm -rf build demo test", "build:demo": "rollup -c --environment BUILD:demo", "build:dev": "rollup -c --environment BUILD:dev", "build:prod": "rollup -c --environment BUILD:prod", "build:test": "rollup -c --environment BUILD:test", "build": "rollup -c", "lint": "eslint index.js src", "test:main": "node test/bundle.js", "test:browser": "node test/browser-tests.js", "test": "npm-run-all test:*", "prepare": "npm-run-all clean build lint test" };
const dependencies = { "d3-hierarchy": "^1.1.5" };
const devDependencies = { "babel-plugin-external-helpers": "^6.22.0", "babel-preset-es2015-rollup": "^3.0.0", "d3": "^4.13.0", "d3-selection-multi": "^1.0.1", "eslint": "^4.19.1", "jsdom": "^11.6.2", "npm-run-all": "^4.1.2", "rollup": "^0.55.3", "rollup-plugin-babel": "^2.7.1", "rollup-plugin-commonjs": "^8.0.2", "rollup-plugin-copy": "^0.2.3", "rollup-plugin-json": "^2.3.0", "rollup-plugin-node-resolve": "^3.0.2", "rollup-plugin-uglify": "^3.0.0", "uglify-es": "^3.3.9" };
const packageInfo = {
name,
version: version$1,
main,
module,
"jsnext:main": "index",
author,
description,
keywords,
homepage,
license,
repository,
scripts,
dependencies,
devDependencies
};
const { version } = packageInfo;
const defaults = Object.freeze({
children: (data) => data.children,
nodeSize: (node) => node.data.size,
spacing: 0
});
function flextree(options) {
const opts = Object.assign({}, defaults, options);
function accessor(name2) {
const opt = opts[name2];
return typeof opt === "function" ? opt : () => opt;
}
function layout(tree) {
const wtree = wrap(getWrapper(), tree, (node) => node.children);
wtree.update();
return wtree.data;
}
function getFlexNode() {
const nodeSize = accessor("nodeSize");
const spacing = accessor("spacing");
return class FlexNode extends hierarchy.prototype.constructor {
constructor(data) {
super(data);
}
copy() {
const c = wrap(this.constructor, this, (node) => node.children);
c.each((node) => node.data = node.data.data);
return c;
}
get size() {
return nodeSize(this);
}
spacing(oNode) {
return spacing(this, oNode);
}
get nodes() {
return this.descendants();
}
get xSize() {
return this.size[0];
}
get ySize() {
return this.size[1];
}
get top() {
return this.y;
}
get bottom() {
return this.y + this.ySize;
}
get left() {
return this.x - this.xSize / 2;
}
get right() {
return this.x + this.xSize / 2;
}
get root() {
const ancs = this.ancestors();
return ancs[ancs.length - 1];
}
get numChildren() {
return this.hasChildren ? this.children.length : 0;
}
get hasChildren() {
return !this.noChildren;
}
get noChildren() {
return this.children === null;
}
get firstChild() {
return this.hasChildren ? this.children[0] : null;
}
get lastChild() {
return this.hasChildren ? this.children[this.numChildren - 1] : null;
}
get extents() {
return (this.children || []).reduce(
(acc, kid) => FlexNode.maxExtents(acc, kid.extents),
this.nodeExtents
);
}
get nodeExtents() {
return {
top: this.top,
bottom: this.bottom,
left: this.left,
right: this.right
};
}
static maxExtents(e0, e1) {
return {
top: Math.min(e0.top, e1.top),
bottom: Math.max(e0.bottom, e1.bottom),
left: Math.min(e0.left, e1.left),
right: Math.max(e0.right, e1.right)
};
}
};
}
function getWrapper() {
const FlexNode = getFlexNode();
const nodeSize = accessor("nodeSize");
const spacing = accessor("spacing");
return class extends FlexNode {
constructor(data) {
super(data);
Object.assign(this, {
x: 0,
y: 0,
relX: 0,
prelim: 0,
shift: 0,
change: 0,
lExt: this,
lExtRelX: 0,
lThr: null,
rExt: this,
rExtRelX: 0,
rThr: null
});
}
get size() {
return nodeSize(this.data);
}
spacing(oNode) {
return spacing(this.data, oNode.data);
}
get x() {
return this.data.x;
}
set x(v) {
this.data.x = v;
}
get y() {
return this.data.y;
}
set y(v) {
this.data.y = v;
}
update() {
layoutChildren(this);
resolveX(this);
return this;
}
};
}
function wrap(FlexClass, treeData, children) {
const _wrap = (data, parent) => {
const node = new FlexClass(data);
Object.assign(node, {
parent,
depth: parent === null ? 0 : parent.depth + 1,
height: 0,
length: 1
});
const kidsData = children(data) || [];
node.children = kidsData.length === 0 ? null : kidsData.map((kd) => _wrap(kd, node));
if (node.children) {
Object.assign(node, node.children.reduce(
(hl, kid) => ({
height: Math.max(hl.height, kid.height + 1),
length: hl.length + kid.length
}),
node
));
}
return node;
};
return _wrap(treeData, null);
}
Object.assign(layout, {
nodeSize(arg) {
return arguments.length ? (opts.nodeSize = arg, layout) : opts.nodeSize;
},
spacing(arg) {
return arguments.length ? (opts.spacing = arg, layout) : opts.spacing;
},
children(arg) {
return arguments.length ? (opts.children = arg, layout) : opts.children;
},
hierarchy(treeData, children) {
const kids = typeof children === "undefined" ? opts.children : children;
return wrap(getFlexNode(), treeData, kids);
},
dump(tree) {
const nodeSize = accessor("nodeSize");
const _dump = (i0) => (node) => {
const i1 = i0 + " ";
const i2 = i0 + " ";
const { x, y } = node;
const size = nodeSize(node);
const kids = node.children || [];
const kdumps = kids.length === 0 ? " " : `,${i1}children: [${i2}${kids.map(_dump(i2)).join(i2)}${i1}],${i0}`;
return `{ size: [${size.join(", ")}],${i1}x: ${x}, y: ${y}${kdumps}},`;
};
return _dump("\n")(tree);
}
});
return layout;
}
flextree.version = version;
const layoutChildren = (w, y = 0) => {
w.y = y;
(w.children || []).reduce((acc, kid) => {
const [i, lastLows] = acc;
layoutChildren(kid, w.y + w.ySize);
const lowY = (i === 0 ? kid.lExt : kid.rExt).bottom;
if (i !== 0) separate(w, i, lastLows);
const lows = updateLows(lowY, i, lastLows);
return [i + 1, lows];
}, [0, null]);
shiftChange(w);
positionRoot(w);
return w;
};
const resolveX = (w, prevSum, parentX) => {
if (typeof prevSum === "undefined") {
prevSum = -w.relX - w.prelim;
parentX = 0;
}
const sum = prevSum + w.relX;
w.relX = sum + w.prelim - parentX;
w.prelim = 0;
w.x = parentX + w.relX;
(w.children || []).forEach((k) => resolveX(k, sum, w.x));
return w;
};
const shiftChange = (w) => {
(w.children || []).reduce((acc, child) => {
const [lastShiftSum, lastChangeSum] = acc;
const shiftSum = lastShiftSum + child.shift;
const changeSum = lastChangeSum + shiftSum + child.change;
child.relX += changeSum;
return [shiftSum, changeSum];
}, [0, 0]);
};
const separate = (w, i, lows) => {
const lSib = w.children[i - 1];
const curSubtree = w.children[i];
let rContour = lSib;
let rSumMods = lSib.relX;
let lContour = curSubtree;
let lSumMods = curSubtree.relX;
let isFirst = true;
while (rContour && lContour) {
if (rContour.bottom > lows.lowY) lows = lows.next;
const dist = rSumMods + rContour.prelim - (lSumMods + lContour.prelim) + rContour.xSize / 2 + lContour.xSize / 2 + rContour.spacing(lContour);
if (dist > 0 || dist < 0 && isFirst) {
lSumMods += dist;
moveSubtree(curSubtree, dist);
distributeExtra(w, i, lows.index, dist);
}
isFirst = false;
const rightBottom = rContour.bottom;
const leftBottom = lContour.bottom;
if (rightBottom <= leftBottom) {
rContour = nextRContour(rContour);
if (rContour) rSumMods += rContour.relX;
}
if (rightBottom >= leftBottom) {
lContour = nextLContour(lContour);
if (lContour) lSumMods += lContour.relX;
}
}
if (!rContour && lContour) setLThr(w, i, lContour, lSumMods);
else if (rContour && !lContour) setRThr(w, i, rContour, rSumMods);
};
const moveSubtree = (subtree, distance) => {
subtree.relX += distance;
subtree.lExtRelX += distance;
subtree.rExtRelX += distance;
};
const distributeExtra = (w, curSubtreeI, leftSibI, dist) => {
const curSubtree = w.children[curSubtreeI];
const n = curSubtreeI - leftSibI;
if (n > 1) {
const delta = dist / n;
w.children[leftSibI + 1].shift += delta;
curSubtree.shift -= delta;
curSubtree.change -= dist - delta;
}
};
const nextLContour = (w) => {
return w.hasChildren ? w.firstChild : w.lThr;
};
const nextRContour = (w) => {
return w.hasChildren ? w.lastChild : w.rThr;
};
const setLThr = (w, i, lContour, lSumMods) => {
const firstChild = w.firstChild;
const lExt = firstChild.lExt;
const curSubtree = w.children[i];
lExt.lThr = lContour;
const diff = lSumMods - lContour.relX - firstChild.lExtRelX;
lExt.relX += diff;
lExt.prelim -= diff;
firstChild.lExt = curSubtree.lExt;
firstChild.lExtRelX = curSubtree.lExtRelX;
};
const setRThr = (w, i, rContour, rSumMods) => {
const curSubtree = w.children[i];
const rExt = curSubtree.rExt;
const lSib = w.children[i - 1];
rExt.rThr = rContour;
const diff = rSumMods - rContour.relX - curSubtree.rExtRelX;
rExt.relX += diff;
rExt.prelim -= diff;
curSubtree.rExt = lSib.rExt;
curSubtree.rExtRelX = lSib.rExtRelX;
};
const positionRoot = (w) => {
if (w.hasChildren) {
const k0 = w.firstChild;
const kf = w.lastChild;
const prelim = (k0.prelim + k0.relX - k0.xSize / 2 + kf.relX + kf.prelim + kf.xSize / 2) / 2;
Object.assign(w, {
prelim,
lExt: k0.lExt,
lExtRelX: k0.lExtRelX,
rExt: kf.rExt,
rExtRelX: kf.rExtRelX
});
}
};
const updateLows = (lowY, index, lastLows) => {
while (lastLows !== null && lowY >= lastLows.lowY)
lastLows = lastLows.next;
return {
lowY,
index,
next: lastLows
};
};
const css = ".markmap {\n --markmap-max-width: 9999px;\n --markmap-a-color: #0097e6;\n --markmap-a-hover-color: #00a8ff;\n --markmap-code-bg: #f0f0f0;\n --markmap-code-color: #555;\n --markmap-highlight-bg: #ffeaa7;\n --markmap-table-border: 1px solid currentColor;\n --markmap-font: 300 16px/20px sans-serif;\n --markmap-circle-open-bg: #fff;\n --markmap-text-color: #333;\n --markmap-highlight-node-bg: #ff02;\n\n font: var(--markmap-font);\n color: var(--markmap-text-color);\n}\n\n .markmap-link {\n fill: none;\n }\n\n .markmap-node > circle {\n cursor: pointer;\n }\n\n .markmap-foreign {\n display: inline-block;\n }\n\n .markmap-foreign p {\n margin: 0;\n }\n\n .markmap-foreign a {\n color: var(--markmap-a-color);\n }\n\n .markmap-foreign a:hover {\n color: var(--markmap-a-hover-color);\n }\n\n .markmap-foreign code {\n padding: 0.25em;\n font-size: calc(1em - 2px);\n color: var(--markmap-code-color);\n background-color: var(--markmap-code-bg);\n border-radius: 2px;\n }\n\n .markmap-foreign pre {\n margin: 0;\n }\n\n .markmap-foreign pre > code {\n display: block;\n }\n\n .markmap-foreign del {\n text-decoration: line-through;\n }\n\n .markmap-foreign em {\n font-style: italic;\n }\n\n .markmap-foreign strong {\n font-weight: bold;\n }\n\n .markmap-foreign mark {\n background: var(--markmap-highlight-bg);\n }\n\n .markmap-foreign table,\n .markmap-foreign th,\n .markmap-foreign td {\n border-collapse: collapse;\n border: var(--markmap-table-border);\n }\n\n .markmap-foreign img {\n display: inline-block;\n }\n\n .markmap-foreign svg {\n fill: currentColor;\n }\n\n .markmap-foreign > div {\n width: var(--markmap-max-width);\n text-align: left;\n }\n\n .markmap-foreign > div > div {\n display: inline-block;\n }\n\n .markmap-highlight rect {\n fill: var(--markmap-highlight-node-bg);\n }\n\n.markmap-dark .markmap {\n --markmap-code-bg: #1a1b26;\n --markmap-code-color: #ddd;\n --markmap-circle-open-bg: #444;\n --markmap-text-color: #eee;\n}\n";
const globalCSS = css;
const SELECTOR_NODE = "g.markmap-node";
const SELECTOR_LINK = "path.markmap-link";
const SELECTOR_HIGHLIGHT = "g.markmap-highlight";
const linkShape = d32.linkHorizontal();
function minBy(numbers, by) {
const index = d32.minIndex(numbers, by);
return numbers[index];
}
function stopPropagation(e) {
e.stopPropagation();
}
const refreshHook = new Hook();
class Markmap {
constructor(svg, opts) {
this.options = { ...defaultOptions };
this._disposeList = [];
this.handleZoom = (e) => {
const { transform } = e;
this.g.attr("transform", transform);
};
this.handlePan = (e) => {
e.preventDefault();
const transform = d32.zoomTransform(this.svg.node());
const newTransform = transform.translate(
-e.deltaX / transform.k,
-e.deltaY / transform.k
);
this.svg.call(this.zoom.transform, newTransform);
};
this.handleClick = (e, d) => {
let recursive = this.options.toggleRecursively;
if (isMacintosh ? e.metaKey : e.ctrlKey) recursive = !recursive;
this.toggleNode(d, recursive);
};
this.ensureView = this.ensureVisible;
this.svg = svg.datum ? svg : d32.select(svg);
this.styleNode = this.svg.append("style");
this.zoom = d32.zoom().filter((event) => {
if (this.options.scrollForPan) {
if (event.type === "wheel") return event.ctrlKey && !event.button;
}
return (!event.ctrlKey || event.type === "wheel") && !event.button;
}).on("zoom", this.handleZoom);
this.setOptions(opts);
this.state = {
id: this.options.id || this.svg.attr("id") || getId(),
rect: { x1: 0, y1: 0, x2: 0, y2: 0 }
};
this.g = this.svg.append("g");
this.g.append("g").attr("class", "markmap-highlight");
this._observer = new ResizeObserver(
debounce(() => {
this.renderData();
}, 100)
);
this._disposeList.push(
refreshHook.tap(() => {
this.setData();
}),
() => this._observer.disconnect()
);
}
getStyleContent() {
const { style } = this.options;
const { id } = this.state;
const styleText = typeof style === "function" ? style(id) : "";
return [this.options.embedGlobalCSS && css, styleText].filter(Boolean).join("\n");
}
updateStyle() {
this.svg.attr(
"class",
addClass(this.svg.attr("class"), "markmap", this.state.id)
);
const style = this.getStyleContent();
this.styleNode.text(style);
}
async toggleNode(data, recursive = false) {
var _a, _b;
const fold = ((_a = data.payload) == null ? void 0 : _a.fold) ? 0 : 1;
if (recursive) {
walkTree(data, (item, next) => {
item.payload = {
...item.payload,
fold
};
next();
});
} else {
data.payload = {
...data.payload,
fold: ((_b = data.payload) == null ? void 0 : _b.fold) ? 0 : 1
};
}
await this.renderData(data);
}
_initializeData(node) {
let nodeId = 0;
const { color, initialExpandLevel } = this.options;
let foldRecursively = 0;
let depth = 0;
walkTree(node, (item, next, parent) => {
var _a, _b, _c, _d;
depth += 1;
item.children = (_a = item.children) == null ? void 0 : _a.map((child) => ({ ...child }));
nodeId += 1;
item.state = {
...item.state,
depth,
id: nodeId,
rect: {
x: 0,
y: 0,
width: 0,
height: 0
},
size: [0, 0]
};
item.state.key = [(_b = parent == null ? void 0 : parent.state) == null ? void 0 : _b.id, item.state.id].filter(Boolean).join(".") + simpleHash(item.content);
item.state.path = [(_c = parent == null ? void 0 : parent.state) == null ? void 0 : _c.path, item.state.id].filter(Boolean).join(".");
color(item);
const isFoldRecursively = ((_d = item.payload) == null ? void 0 : _d.fold) === 2;
if (isFoldRecursively) {
foldRecursively += 1;
} else if (foldRecursively || initialExpandLevel >= 0 && item.state.depth >= initialExpandLevel) {
item.payload = { ...item.payload, fold: 1 };
}
next();
if (isFoldRecursively) foldRecursively -= 1;
depth -= 1;
});
return node;
}
_relayout() {
if (!this.state.data) return;
this.g.selectAll(childSelector(SELECTOR_NODE)).selectAll(
childSelector("foreignObject")
).each(function(d) {
var _a;
const el = (_a = this.firstChild) == null ? void 0 : _a.firstChild;
const newSize = [el.scrollWidth, el.scrollHeight];
d.state.size = newSize;
});
const { lineWidth, paddingX, spacingHorizontal, spacingVertical } = this.options;
const layout = flextree({}).children((d) => {
var _a;
if (!((_a = d.payload) == null ? void 0 : _a.fold)) return d.children;
}).nodeSize((node) => {
const [width, height] = node.data.state.size;
return [height, width + (width ? paddingX * 2 : 0) + spacingHorizontal];
}).spacing((a, b) => {
return (a.parent === b.parent ? spacingVertical : spacingVertical * 2) + lineWidth(a.data);
});
const tree = layout.hierarchy(this.state.data);
layout(tree);
const fnodes = tree.descendants();
fnodes.forEach((fnode) => {
const node = fnode.data;
node.state.rect = {
x: fnode.y,
y: fnode.x - fnode.xSize / 2,
width: fnode.ySize - spacingHorizontal,
height: fnode.xSize
};
});
this.state.rect = {
x1: d32.min(fnodes, (fnode) => fnode.data.state.rect.x) || 0,
y1: d32.min(fnodes, (fnode) => fnode.data.state.rect.y) || 0,
x2: d32.max(
fnodes,
(fnode) => fnode.data.state.rect.x + fnode.data.state.rect.width
) || 0,
y2: d32.max(
fnodes,
(fnode) => fnode.data.state.rect.y + fnode.data.state.rect.height
) || 0
};
}
setOptions(opts) {
this.options = {
...this.options,
...opts
};
if (this.options.zoom) {
this.svg.call(this.zoom);
} else {
this.svg.on(".zoom", null);
}
if (this.options.pan) {
this.svg.on("wheel", this.handlePan);
} else {
this.svg.on("wheel", null);
}
}
async setData(data, opts) {
if (opts) this.setOptions(opts);
if (data) this.state.data = this._initializeData(data);
if (!this.state.data) return;
this.updateStyle();
await this.renderData();
}
async setHighlight(node) {
this.state.highlight = node || void 0;
await this.renderData();
}
_getHighlightRect(highlight) {
const svgNode = this.svg.node();
const transform = d32.zoomTransform(svgNode);
const padding = 4 / transform.k;
const rect = {
...highlight.state.rect
};
rect.x -= padding;
rect.y -= padding;
rect.width += 2 * padding;
rect.height += 2 * padding;
return rect;
}
async renderData(originData) {
const { paddingX, autoFit, color, maxWidth, lineWidth } = this.options;
const rootNode = this.state.data;
if (!rootNode) return;
const nodeMap = {};
const parentMap = {};
const nodes = [];
walkTree(rootNode, (item, next, parent) => {
var _a;
if (!((_a = item.payload) == null ? void 0 : _a.fold)) next();
nodeMap[item.state.id] = item;
if (parent) parentMap[item.state.id] = parent.state.id;
nodes.push(item);
});
const originMap = {};
const sourceRectMap = {};
const setOriginNode = (originNode) => {
if (!originNode || originMap[originNode.state.id]) return;
walkTree(originNode, (item, next) => {
originMap[item.state.id] = originNode.state.id;
next();
});
};
const getOriginSourceRect = (node) => {
const rect = sourceRectMap[originMap[node.state.id]];
return rect || rootNode.state.rect;
};
const getOriginTargetRect = (node) => (nodeMap[originMap[node.state.id]] || rootNode).state.rect;
sourceRectMap[rootNode.state.id] = rootNode.state.rect;
if (originData) setOriginNode(originData);
let { highlight } = this.state;
if (highlight && !nodeMap[highlight.state.id]) highlight = void 0;
let highlightNodes = this.g.selectAll(childSelector(SELECTOR_HIGHLIGHT)).selectAll(childSelector("rect")).data(highlight ? [this._getHighlightRect(highlight)] : []).join("rect").attr("x", (d) => d.x).attr("y", (d) => d.y).attr("width", (d) => d.width).attr("height", (d) => d.height);
const mmG = this.g.selectAll(childSelector(SELECTOR_NODE)).each((d) => {
sourceRectMap[d.state.id] = d.state.rect;
}).data(nodes, (d) => d.state.key);
const mmGEnter = mmG.enter().append("g").attr("data-depth", (d) => d.state.depth).attr("data-path", (d) => d.state.path).each((d) => {
setOriginNode(nodeMap[parentMap[d.state.id]]);
});
const mmGExit = mmG.exit().each((d) => {
setOriginNode(nodeMap[parentMap[d.state.id]]);
});
const mmGMerge = mmG.merge(mmGEnter).attr(
"class",
(d) => {
var _a;
return ["markmap-node", ((_a = d.payload) == null ? void 0 : _a.fold) && "markmap-fold"].filter(Boolean).join(" ");
}
);
const mmLine = mmGMerge.selectAll(childSelector("line")).data(
(d) => [d],
(d) => d.state.key
);
const mmLineEnter = mmLine.enter().append("line").attr("stroke", (d) => color(d)).attr("stroke-width", 0);
const mmLineMerge = mmLine.merge(mmLineEnter);
const mmCircle = mmGMerge.selectAll(childSelector("circle")).data(
(d) => {
var _a;
return ((_a = d.children) == null ? void 0 : _a.length) ? [d] : [];
},
(d) => d.state.key
);
const mmCircleEnter = mmCircle.enter().append("circle").attr("stroke-width", 0).attr("r", 0).on("click", (e, d) => this.handleClick(e, d)).on("mousedown", stopPropagation);
const mmCircleMerge = mmCircleEnter.merge(mmCircle).attr("stroke", (d) => color(d)).attr(
"fill",
(d) => {
var _a;
return ((_a = d.payload) == null ? void 0 : _a.fold) && d.children ? color(d) : "var(--markmap-circle-open-bg)";
}
);
const observer = this._observer;
const mmFo = mmGMerge.selectAll(childSelector("foreignObject")).data(
(d) => [d],
(d) => d.state.key
);
const mmFoEnter = mmFo.enter().append("foreignObject").attr("class", "markmap-foreign").attr("x", paddingX).attr("y", 0).style("opacity", 0).on("mousedown", stopPropagation).on("dblclick", stopPropagation);
mmFoEnter.append("xhtml:div").append("xhtml:div").html((d) => d.content).attr("xmlns", "http://www.w3.org/1999/xhtml");
mmFoEnter.each(function() {
var _a;
const el = (_a = this.firstChild) == null ? void 0 : _a.firstChild;
observer.observe(el);
});
const mmFoExit = mmGExit.selectAll(
childSelector("foreignObject")
);
mmFoExit.each(function() {
var _a;
const el = (_a = this.firstChild) == null ? void 0 : _a.firstChild;
observer.unobserve(el);
});
const mmFoMerge = mmFoEnter.merge(mmFo);
const links = nodes.flatMap(
(node) => {
var _a;
return ((_a = node.payload) == null ? void 0 : _a.fold) ? [] : node.children.map((child) => ({ source: node, target: child }));
}
);
const mmPath = this.g.selectAll(childSelector(SELECTOR_LINK)).data(links, (d) => d.target.state.key);
const mmPathExit = mmPath.exit();
const mmPathEnter = mmPath.enter().insert("path", "g").attr("class", "markmap-link").attr("data-depth", (d) => d.target.state.depth).attr("data-path", (d) => d.target.state.path).attr("d", (d) => {
const originRect = getOriginSourceRect(d.target);
const pathOrigin = [
originRect.x + originRect.width,
originRect.y + originRect.height
];
return linkShape({ source: pathOrigin, target: pathOrigin });
}).attr("stroke-width", 0);
const mmPathMerge = mmPathEnter.merge(mmPath);
this.svg.style(
"--markmap-max-width",
maxWidth ? `${maxWidth}px` : null
);
await new Promise(requestAnimationFrame);
this._relayout();
highlightNodes = highlightNodes.data(highlight ? [this._getHighlightRect(highlight)] : []).join("rect");
this.transition(highlightNodes).attr("x", (d) => d.x).attr("y", (d) => d.y).attr("width", (d) => d.width).attr("height", (d) => d.height);
mmGEnter.attr("transform", (d) => {
const originRect = getOriginSourceRect(d);
return `translate(${originRect.x + originRect.width - d.state.rect.width},${originRect.y + originRect.height - d.state.rect.height})`;
});
this.transition(mmGExit).attr("transform", (d) => {
const targetRect = getOriginTargetRect(d);
const targetX = targetRect.x + targetRect.width - d.state.rect.width;
const targetY = targetRect.y + targetRect.height - d.state.rect.height;
return `translate(${targetX},${targetY})`;
}).remove();
this.transition(mmGMerge).attr(
"transform",
(d) => `translate(${d.state.rect.x},${d.state.rect.y})`
);
const mmLineExit = mmGExit.selectAll(
childSelector("line")
);
this.transition(mmLineExit).attr("x1", (d) => d.state.rect.width).attr("stroke-width", 0);
mmLineEnter.attr("x1", (d) => d.state.rect.width).attr("x2", (d) => d.state.rect.width);
mmLineMerge.attr("y1", (d) => d.state.rect.height + lineWidth(d) / 2).attr("y2", (d) => d.state.rect.height + lineWidth(d) / 2);
this.transition(mmLineMerge).attr("x1", -1).attr("x2", (d) => d.state.rect.width + 2).attr("stroke", (d) => color(d)).attr("stroke-width", lineWidth);
const mmCircleExit = mmGExit.selectAll(
childSelector("circle")
);
this.transition(mmCircleExit).attr("r", 0).attr("stroke-width", 0);
mmCircleMerge.attr("cx", (d) => d.state.rect.width).attr("cy", (d) => d.state.rect.height + lineWidth(d) / 2);
this.transition(mmCircleMerge).attr("r", 6).attr("stroke-width", "1.5");
this.transition(mmFoExit).style("opacity", 0);
mmFoMerge.attr("width", (d) => Math.max(0, d.state.rect.width - paddingX * 2)).attr("height", (d) => d.state.rect.height);
this.transition(mmFoMerge).style("opacity", 1);
this.transition(mmPathExit).attr("d", (d) => {
const targetRect = getOriginTargetRect(d.target);
const pathTarget = [
targetRect.x + targetRect.width,
targetRect.y + targetRect.height + lineWidth(d.target) / 2
];
return linkShape({ source: pathTarget, target: pathTarget });
}).attr("stroke-width", 0).remove();
this.transition(mmPathMerge).attr("stroke", (d) => color(d.target)).attr("stroke-width", (d) => lineWidth(d.target)).attr("d", (d) => {
const origSource = d.source;
const origTarget = d.target;
const source = [
origSource.state.rect.x + origSource.state.rect.width,
origSource.state.rect.y + origSource.state.rect.height + lineWidth(origSource) / 2
];
const target = [
origTarget.state.rect.x,
origTarget.state.rect.y + origTarget.state.rect.height + lineWidth(origTarget) / 2
];
return linkShape({ source, target });
});
if (autoFit) this.fit();
}
transition(sel) {
const { duration } = this.options;
return sel.transition().duration(duration);
}
/**
* Fit the content to the viewport.
*/
async fit(maxScale = this.options.maxInitialScale) {
const svgNode = this.svg.node();
const { width: offsetWidth, height: offsetHeight } = svgNode.getBoundingClientRect();
const { fitRatio } = this.options;
const { x1, y1, x2, y2 } = this.state.rect;
const naturalWidth = x2 - x1;
const naturalHeight = y2 - y1;
const scale = Math.min(
offsetWidth / naturalWidth * fitRatio,
offsetHeight / naturalHeight * fitRatio,
maxScale
);
const initialZoom = d32.zoomIdentity.translate(
(offsetWidth - naturalWidth * scale) / 2 - x1 * scale,
(offsetHeight - naturalHeight * scale) / 2 - y1 * scale
).scale(scale);
return this.transition(this.svg).call(this.zoom.transform, initialZoom).end().catch(noop);
}
findElement(node) {
let result;
this.g.selectAll(childSelector(SELECTOR_NODE)).each(function walk(d) {
if (d === node) {
result = {
data: d,
g: this
};
}
});
return result;
}
/**
* Pan the content to make the provided node visible in the viewport.
*/
async ensureVisible(node, padding) {
var _a;
const itemData = (_a = this.findElement(node)) == null ? void 0 : _a.data;
if (!itemData) return;
const svgNode = this.svg.node();
const relRect = svgNode.getBoundingClientRect();
const transform = d32.zoomTransform(svgNode);
const [left, right] = [
itemData.state.rect.x,
itemData.state.rect.x + itemData.state.rect.width + 2
].map((x) => x * transform.k + transform.x);
const [top, bottom] = [
itemData.state.rect.y,
itemData.state.rect.y + itemData.state.rect.height
].map((y) => y * transform.k + transform.y);
const pd = {
left: 0,
right: 0,
top: 0,
bottom: 0,
...padding
};
const dxs = [pd.left - left, relRect.width - pd.right - right];
const dys = [pd.top - top, relRect.height - pd.bottom - bottom];
const dx = dxs[0] * dxs[1] > 0 ? minBy(dxs, Math.abs) / transform.k : 0;
const dy = dys[0] * dys[1] > 0 ? minBy(dys, Math.abs) / transform.k : 0;
if (dx || dy) {
const newTransform = transform.translate(dx, dy);
return this.transition(this.svg).call(this.zoom.transform, newTransform).end().catch(noop);
}
}
async centerNode(node, padding) {
var _a;
const itemData = (_a = this.findElement(node)) == null ? void 0 : _a.data;
if (!itemData) return;
const svgNode = this.svg.node();
const relRect = svgNode.getBoundingClientRect();
const transform = d32.zoomTransform(svgNode);
const x = (itemData.state.rect.x + itemData.state.rect.width / 2) * transform.k + transform.x;
const y = (itemData.state.rect.y + itemData.state.rect.height / 2) * transform.k + transform.y;
const pd = {
left: 0,
right: 0,
top: 0,
bottom: 0,
...padding
};
const cx = (pd.left + rel