You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
237 lines
6.9 KiB
237 lines
6.9 KiB
import {Node} from "./hierarchy/index"; |
|
|
|
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; |
|
}
|
|
|