import {Node} from "./hierarchy/index.js"; function defaultSeparation(a, b) { return a.parent === b.parent ? 1 : 2; } // function radialSeparation(a, b) { // return (a.parent === b.parent ? 1 : 2) / a.depth; // } // This function is used to traverse the left contour of a subtree (or // subforest). It returns the successor of v on this contour. This successor is // either given by the leftmost child of v or by the thread of v. The function // returns null if and only if v is on the highest level of its subtree. function nextLeft(v) { var children = v.children; return children ? children[0] : v.t; } // This function works analogously to nextLeft. function nextRight(v) { var children = v.children; return children ? children[children.length - 1] : v.t; } // Shifts the current subtree rooted at w+. This is done by increasing // prelim(w+) and mod(w+) by shift. function moveSubtree(wm, wp, shift) { var change = shift / (wp.i - wm.i); wp.c -= change; wp.s += shift; wm.c += change; wp.z += shift; wp.m += shift; } // All other shifts, applied to the smaller subtrees between w- and w+, are // performed by this function. To prepare the shifts, we have to adjust // change(w+), shift(w+), and change(w-). function executeShifts(v) { var shift = 0, change = 0, children = v.children, i = children.length, w; while (--i >= 0) { w = children[i]; w.z += shift; w.m += shift; shift += w.s + (change += w.c); } } // If vi-’s ancestor is a sibling of v, returns vi-’s ancestor. Otherwise, // returns the specified (default) ancestor. function nextAncestor(vim, v, ancestor) { return vim.a.parent === v.parent ? vim.a : ancestor; } function TreeNode(node, i) { this._ = node; this.parent = null; this.children = null; this.A = null; // default ancestor this.a = this; // ancestor this.z = 0; // prelim this.m = 0; // mod this.c = 0; // change this.s = 0; // shift this.t = null; // thread this.i = i; // number } TreeNode.prototype = Object.create(Node.prototype); function treeRoot(root) { var tree = new TreeNode(root, 0), node, nodes = [tree], child, children, i, n; while (node = nodes.pop()) { if (children = node._.children) { node.children = new Array(n = children.length); for (i = n - 1; i >= 0; --i) { nodes.push(child = node.children[i] = new TreeNode(children[i], i)); child.parent = node; } } } (tree.parent = new TreeNode(null, 0)).children = [tree]; return tree; } // Node-link tree diagram using the Reingold-Tilford "tidy" algorithm export default function() { var separation = defaultSeparation, dx = 1, dy = 1, nodeSize = null; function tree(root) { var t = treeRoot(root); // Compute the layout using Buchheim et al.’s algorithm. t.eachAfter(firstWalk), t.parent.m = -t.z; t.eachBefore(secondWalk); // If a fixed node size is specified, scale x and y. if (nodeSize) root.eachBefore(sizeNode); // If a fixed tree size is specified, scale x and y based on the extent. // Compute the left-most, right-most, and depth-most nodes for extents. else { var left = root, right = root, bottom = root; root.eachBefore(function(node) { if (node.x < left.x) left = node; if (node.x > right.x) right = node; if (node.depth > bottom.depth) bottom = node; }); var s = left === right ? 1 : separation(left, right) / 2, tx = s - left.x, kx = dx / (right.x + s + tx), ky = dy / (bottom.depth || 1); root.eachBefore(function(node) { node.x = (node.x + tx) * kx; node.y = node.depth * ky; }); } return root; } // Computes a preliminary x-coordinate for v. Before that, FIRST WALK is // applied recursively to the children of v, as well as the function // APPORTION. After spacing out the children by calling EXECUTE SHIFTS, the // node v is placed to the midpoint of its outermost children. function firstWalk(v) { var children = v.children, siblings = v.parent.children, w = v.i ? siblings[v.i - 1] : null; if (children) { executeShifts(v); var midpoint = (children[0].z + children[children.length - 1].z) / 2; if (w) { v.z = w.z + separation(v._, w._); v.m = v.z - midpoint; } else { v.z = midpoint; } } else if (w) { v.z = w.z + separation(v._, w._); } v.parent.A = apportion(v, w, v.parent.A || siblings[0]); } // Computes all real x-coordinates by summing up the modifiers recursively. function secondWalk(v) { v._.x = v.z + v.parent.m; v.m += v.parent.m; } // The core of the algorithm. Here, a new subtree is combined with the // previous subtrees. Threads are used to traverse the inside and outside // contours of the left and right subtree up to the highest common level. The // vertices used for the traversals are vi+, vi-, vo-, and vo+, where the // superscript o means outside and i means inside, the subscript - means left // subtree and + means right subtree. For summing up the modifiers along the // contour, we use respective variables si+, si-, so-, and so+. Whenever two // nodes of the inside contours conflict, we compute the left one of the // greatest uncommon ancestors using the function ANCESTOR and call MOVE // SUBTREE to shift the subtree and prepare the shifts of smaller subtrees. // Finally, we add a new thread (if necessary). function apportion(v, w, ancestor) { if (w) { var vip = v, vop = v, vim = w, vom = vip.parent.children[0], sip = vip.m, sop = vop.m, sim = vim.m, som = vom.m, shift; while (vim = nextRight(vim), vip = nextLeft(vip), vim && vip) { vom = nextLeft(vom); vop = nextRight(vop); vop.a = v; shift = vim.z + sim - vip.z - sip + separation(vim._, vip._); if (shift > 0) { moveSubtree(nextAncestor(vim, v, ancestor), v, shift); sip += shift; sop += shift; } sim += vim.m; sip += vip.m; som += vom.m; sop += vop.m; } if (vim && !nextRight(vop)) { vop.t = vim; vop.m += sim - sop; } if (vip && !nextLeft(vom)) { vom.t = vip; vom.m += sip - som; ancestor = v; } } return ancestor; } function sizeNode(node) { node.x *= dx; node.y = node.depth * dy; } tree.separation = function(x) { return arguments.length ? (separation = x, tree) : separation; }; tree.size = function(x) { return arguments.length ? (nodeSize = false, dx = +x[0], dy = +x[1], tree) : (nodeSize ? null : [dx, dy]); }; tree.nodeSize = function(x) { return arguments.length ? (nodeSize = true, dx = +x[0], dy = +x[1], tree) : (nodeSize ? [dx, dy] : null); }; return tree; }