d3-flextree
Version:
Flexible tree layout algorithm that allows for variable node sizes.
794 lines (703 loc) • 21.3 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.d3 = global.d3 || {})));
}(this, (function (exports) { 'use strict';
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) { // Don’t include the root’s parent, if any.
links.push({source: node.parent, target: node});
}
});
return links;
}
function hierarchy(data, children) {
var root = new Node(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(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(data) {
this.data = data;
this.depth =
this.height = 0;
this.parent = null;
}
Node.prototype = hierarchy.prototype = {
constructor: Node,
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
};
var version = "2.1.1";
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var inherits = function (subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
};
var possibleConstructorReturn = function (self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
};
var slicedToArray = function () {
function sliceIterator(arr, i) {
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"]) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
return function (arr, i) {
if (Array.isArray(arr)) {
return arr;
} else if (Symbol.iterator in Object(arr)) {
return sliceIterator(arr, i);
} else {
throw new TypeError("Invalid attempt to destructure non-iterable instance");
}
};
}();
var defaults$1 = Object.freeze({
children: function children(data) {
return data.children;
},
nodeSize: function nodeSize(node) {
return node.data.size;
},
spacing: 0
});
// Create a layout function with customizable options. Per D3-style, the
// options can be set at any time using setter methods. The layout function
// will compute the tree node positions based on the options in effect at the
// time it is called.
function flextree(options) {
var opts = Object.assign({}, defaults$1, options);
function accessor(name$$1) {
var opt = opts[name$$1];
return typeof opt === 'function' ? opt : function () {
return opt;
};
}
function layout(tree) {
var wtree = wrap(getWrapper(), tree, function (node) {
return node.children;
});
wtree.update();
return wtree.data;
}
function getFlexNode() {
var nodeSize = accessor('nodeSize');
var _spacing = accessor('spacing');
return function (_hierarchy$prototype$) {
inherits(FlexNode, _hierarchy$prototype$);
function FlexNode(data) {
classCallCheck(this, FlexNode);
return possibleConstructorReturn(this, (FlexNode.__proto__ || Object.getPrototypeOf(FlexNode)).call(this, data));
}
createClass(FlexNode, [{
key: 'copy',
value: function copy() {
var c = wrap(this.constructor, this, function (node) {
return node.children;
});
c.each(function (node) {
return node.data = node.data.data;
});
return c;
}
}, {
key: 'spacing',
value: function spacing(oNode) {
return _spacing(this, oNode);
}
}, {
key: 'size',
get: function get$$1() {
return nodeSize(this);
}
}, {
key: 'nodes',
get: function get$$1() {
return this.descendants();
}
}, {
key: 'xSize',
get: function get$$1() {
return this.size[0];
}
}, {
key: 'ySize',
get: function get$$1() {
return this.size[1];
}
}, {
key: 'top',
get: function get$$1() {
return this.y;
}
}, {
key: 'bottom',
get: function get$$1() {
return this.y + this.ySize;
}
}, {
key: 'left',
get: function get$$1() {
return this.x - this.xSize / 2;
}
}, {
key: 'right',
get: function get$$1() {
return this.x + this.xSize / 2;
}
}, {
key: 'root',
get: function get$$1() {
var ancs = this.ancestors();
return ancs[ancs.length - 1];
}
}, {
key: 'numChildren',
get: function get$$1() {
return this.hasChildren ? this.children.length : 0;
}
}, {
key: 'hasChildren',
get: function get$$1() {
return !this.noChildren;
}
}, {
key: 'noChildren',
get: function get$$1() {
return this.children === null;
}
}, {
key: 'firstChild',
get: function get$$1() {
return this.hasChildren ? this.children[0] : null;
}
}, {
key: 'lastChild',
get: function get$$1() {
return this.hasChildren ? this.children[this.numChildren - 1] : null;
}
}, {
key: 'extents',
get: function get$$1() {
return (this.children || []).reduce(function (acc, kid) {
return FlexNode.maxExtents(acc, kid.extents);
}, this.nodeExtents);
}
}, {
key: 'nodeExtents',
get: function get$$1() {
return {
top: this.top,
bottom: this.bottom,
left: this.left,
right: this.right
};
}
}], [{
key: 'maxExtents',
value: function 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)
};
}
}]);
return FlexNode;
}(hierarchy.prototype.constructor);
}
function getWrapper() {
var FlexNode = getFlexNode();
var nodeSize = accessor('nodeSize');
var _spacing2 = accessor('spacing');
return function (_FlexNode) {
inherits(_class, _FlexNode);
function _class(data) {
classCallCheck(this, _class);
var _this2 = possibleConstructorReturn(this, (_class.__proto__ || Object.getPrototypeOf(_class)).call(this, data));
Object.assign(_this2, {
x: 0, y: 0,
relX: 0, prelim: 0, shift: 0, change: 0,
lExt: _this2, lExtRelX: 0, lThr: null,
rExt: _this2, rExtRelX: 0, rThr: null
});
return _this2;
}
createClass(_class, [{
key: 'spacing',
value: function spacing(oNode) {
return _spacing2(this.data, oNode.data);
}
}, {
key: 'update',
value: function update() {
layoutChildren(this);
resolveX(this);
return this;
}
}, {
key: 'size',
get: function get$$1() {
return nodeSize(this.data);
}
}, {
key: 'x',
get: function get$$1() {
return this.data.x;
},
set: function set$$1(v) {
this.data.x = v;
}
}, {
key: 'y',
get: function get$$1() {
return this.data.y;
},
set: function set$$1(v) {
this.data.y = v;
}
}]);
return _class;
}(FlexNode);
}
function wrap(FlexClass, treeData, children) {
var _wrap = function _wrap(data, parent) {
var node = new FlexClass(data);
Object.assign(node, {
parent: parent,
depth: parent === null ? 0 : parent.depth + 1,
height: 0,
length: 1
});
var kidsData = children(data) || [];
node.children = kidsData.length === 0 ? null : kidsData.map(function (kd) {
return _wrap(kd, node);
});
if (node.children) {
Object.assign(node, node.children.reduce(function (hl, kid) {
return {
height: Math.max(hl.height, kid.height + 1),
length: hl.length + kid.length
};
}, node));
}
return node;
};
return _wrap(treeData, null);
}
Object.assign(layout, {
nodeSize: function nodeSize(arg) {
return arguments.length ? (opts.nodeSize = arg, layout) : opts.nodeSize;
},
spacing: function spacing(arg) {
return arguments.length ? (opts.spacing = arg, layout) : opts.spacing;
},
children: function children(arg) {
return arguments.length ? (opts.children = arg, layout) : opts.children;
},
hierarchy: function hierarchy(treeData, children) {
var kids = typeof children === 'undefined' ? opts.children : children;
return wrap(getFlexNode(), treeData, kids);
},
dump: function dump(tree) {
var nodeSize = accessor('nodeSize');
var _dump = function _dump(i0) {
return function (node) {
var i1 = i0 + ' ';
var i2 = i0 + ' ';
var x = node.x,
y = node.y;
var size = nodeSize(node);
var kids = node.children || [];
var 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;
var layoutChildren = function layoutChildren(w) {
var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
w.y = y;
(w.children || []).reduce(function (acc, kid) {
var _acc = slicedToArray(acc, 2),
i = _acc[0],
lastLows = _acc[1];
layoutChildren(kid, w.y + w.ySize);
// The lowest vertical coordinate while extreme nodes still point
// in current subtree.
var lowY = (i === 0 ? kid.lExt : kid.rExt).bottom;
if (i !== 0) separate(w, i, lastLows);
var lows = updateLows(lowY, i, lastLows);
return [i + 1, lows];
}, [0, null]);
shiftChange(w);
positionRoot(w);
return w;
};
// Resolves the relative coordinate properties - relX and prelim --
// to set the final, absolute x coordinate for each node. This also sets
// `prelim` to 0, so that `relX` for each node is its x-coordinate relative
// to its parent.
var resolveX = function resolveX(w, prevSum, parentX) {
// A call to resolveX without arguments is assumed to be for the root of
// the tree. This will set the root's x-coord to zero.
if (typeof prevSum === 'undefined') {
prevSum = -w.relX - w.prelim;
parentX = 0;
}
var sum = prevSum + w.relX;
w.relX = sum + w.prelim - parentX;
w.prelim = 0;
w.x = parentX + w.relX;
(w.children || []).forEach(function (k) {
return resolveX(k, sum, w.x);
});
return w;
};
// Process shift and change for all children, to add intermediate spacing to
// each child's modifier.
var shiftChange = function shiftChange(w) {
(w.children || []).reduce(function (acc, child) {
var _acc2 = slicedToArray(acc, 2),
lastShiftSum = _acc2[0],
lastChangeSum = _acc2[1];
var shiftSum = lastShiftSum + child.shift;
var changeSum = lastChangeSum + shiftSum + child.change;
child.relX += changeSum;
return [shiftSum, changeSum];
}, [0, 0]);
};
// Separates the latest child from its previous sibling
/* eslint-disable complexity */
var separate = function separate(w, i, lows) {
var lSib = w.children[i - 1];
var curSubtree = w.children[i];
var rContour = lSib;
var rSumMods = lSib.relX;
var lContour = curSubtree;
var lSumMods = curSubtree.relX;
var isFirst = true;
while (rContour && lContour) {
if (rContour.bottom > lows.lowY) lows = lows.next;
// How far to the left of the right side of rContour is the left side
// of lContour? First compute the center-to-center distance, then add
// the "spacing"
var dist = rSumMods + rContour.prelim - (lSumMods + lContour.prelim) + rContour.xSize / 2 + lContour.xSize / 2 + rContour.spacing(lContour);
if (dist > 0 || dist < 0 && isFirst) {
lSumMods += dist;
// Move subtree by changing relX.
moveSubtree$1(curSubtree, dist);
distributeExtra(w, i, lows.index, dist);
}
isFirst = false;
// Advance highest node(s) and sum(s) of modifiers
var rightBottom = rContour.bottom;
var 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;
}
}
// Set threads and update extreme nodes. In the first case, the
// current subtree is taller than the left siblings.
if (!rContour && lContour) setLThr(w, i, lContour, lSumMods);
// In the next case, the left siblings are taller than the current subtree
else if (rContour && !lContour) setRThr(w, i, rContour, rSumMods);
};
/* eslint-enable complexity */
// Move subtree by changing relX.
var moveSubtree$1 = function moveSubtree(subtree, distance) {
subtree.relX += distance;
subtree.lExtRelX += distance;
subtree.rExtRelX += distance;
};
var distributeExtra = function distributeExtra(w, curSubtreeI, leftSibI, dist) {
var curSubtree = w.children[curSubtreeI];
var n = curSubtreeI - leftSibI;
// Are there intermediate children?
if (n > 1) {
var delta = dist / n;
w.children[leftSibI + 1].shift += delta;
curSubtree.shift -= delta;
curSubtree.change -= dist - delta;
}
};
var nextLContour = function nextLContour(w) {
return w.hasChildren ? w.firstChild : w.lThr;
};
var nextRContour = function nextRContour(w) {
return w.hasChildren ? w.lastChild : w.rThr;
};
var setLThr = function setLThr(w, i, lContour, lSumMods) {
var firstChild = w.firstChild;
var lExt = firstChild.lExt;
var curSubtree = w.children[i];
lExt.lThr = lContour;
// Change relX so that the sum of modifier after following thread is correct.
var diff = lSumMods - lContour.relX - firstChild.lExtRelX;
lExt.relX += diff;
// Change preliminary x coordinate so that the node does not move.
lExt.prelim -= diff;
// Update extreme node and its sum of modifiers.
firstChild.lExt = curSubtree.lExt;
firstChild.lExtRelX = curSubtree.lExtRelX;
};
// Mirror image of setLThr.
var setRThr = function setRThr(w, i, rContour, rSumMods) {
var curSubtree = w.children[i];
var rExt = curSubtree.rExt;
var lSib = w.children[i - 1];
rExt.rThr = rContour;
var diff = rSumMods - rContour.relX - curSubtree.rExtRelX;
rExt.relX += diff;
rExt.prelim -= diff;
curSubtree.rExt = lSib.rExt;
curSubtree.rExtRelX = lSib.rExtRelX;
};
// Position root between children, taking into account their modifiers
var positionRoot = function positionRoot(w) {
if (w.hasChildren) {
var k0 = w.firstChild;
var kf = w.lastChild;
var prelim = (k0.prelim + k0.relX - k0.xSize / 2 + kf.relX + kf.prelim + kf.xSize / 2) / 2;
Object.assign(w, {
prelim: prelim,
lExt: k0.lExt, lExtRelX: k0.lExtRelX,
rExt: kf.rExt, rExtRelX: kf.rExtRelX
});
}
};
// Make/maintain a linked list of the indexes of left siblings and their
// lowest vertical coordinate.
var updateLows = function updateLows(lowY, index, lastLows) {
// Remove siblings that are hidden by the new subtree.
while (lastLows !== null && lowY >= lastLows.lowY) {
lastLows = lastLows.next;
} // Prepend the new subtree.
return {
lowY: lowY,
index: index,
next: lastLows
};
};
exports.flextree = flextree;
Object.defineProperty(exports, '__esModule', { value: true });
})));
//# sourceMappingURL=d3-flextree.js.map