tree.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. import {Node} from "./hierarchy/index.js";
  2. function defaultSeparation(a, b) {
  3. return a.parent === b.parent ? 1 : 2;
  4. }
  5. // function radialSeparation(a, b) {
  6. // return (a.parent === b.parent ? 1 : 2) / a.depth;
  7. // }
  8. // This function is used to traverse the left contour of a subtree (or
  9. // subforest). It returns the successor of v on this contour. This successor is
  10. // either given by the leftmost child of v or by the thread of v. The function
  11. // returns null if and only if v is on the highest level of its subtree.
  12. function nextLeft(v) {
  13. var children = v.children;
  14. return children ? children[0] : v.t;
  15. }
  16. // This function works analogously to nextLeft.
  17. function nextRight(v) {
  18. var children = v.children;
  19. return children ? children[children.length - 1] : v.t;
  20. }
  21. // Shifts the current subtree rooted at w+. This is done by increasing
  22. // prelim(w+) and mod(w+) by shift.
  23. function moveSubtree(wm, wp, shift) {
  24. var change = shift / (wp.i - wm.i);
  25. wp.c -= change;
  26. wp.s += shift;
  27. wm.c += change;
  28. wp.z += shift;
  29. wp.m += shift;
  30. }
  31. // All other shifts, applied to the smaller subtrees between w- and w+, are
  32. // performed by this function. To prepare the shifts, we have to adjust
  33. // change(w+), shift(w+), and change(w-).
  34. function executeShifts(v) {
  35. var shift = 0,
  36. change = 0,
  37. children = v.children,
  38. i = children.length,
  39. w;
  40. while (--i >= 0) {
  41. w = children[i];
  42. w.z += shift;
  43. w.m += shift;
  44. shift += w.s + (change += w.c);
  45. }
  46. }
  47. // If vi-’s ancestor is a sibling of v, returns vi-’s ancestor. Otherwise,
  48. // returns the specified (default) ancestor.
  49. function nextAncestor(vim, v, ancestor) {
  50. return vim.a.parent === v.parent ? vim.a : ancestor;
  51. }
  52. function TreeNode(node, i) {
  53. this._ = node;
  54. this.parent = null;
  55. this.children = null;
  56. this.A = null; // default ancestor
  57. this.a = this; // ancestor
  58. this.z = 0; // prelim
  59. this.m = 0; // mod
  60. this.c = 0; // change
  61. this.s = 0; // shift
  62. this.t = null; // thread
  63. this.i = i; // number
  64. }
  65. TreeNode.prototype = Object.create(Node.prototype);
  66. function treeRoot(root) {
  67. var tree = new TreeNode(root, 0),
  68. node,
  69. nodes = [tree],
  70. child,
  71. children,
  72. i,
  73. n;
  74. while (node = nodes.pop()) {
  75. if (children = node._.children) {
  76. node.children = new Array(n = children.length);
  77. for (i = n - 1; i >= 0; --i) {
  78. nodes.push(child = node.children[i] = new TreeNode(children[i], i));
  79. child.parent = node;
  80. }
  81. }
  82. }
  83. (tree.parent = new TreeNode(null, 0)).children = [tree];
  84. return tree;
  85. }
  86. // Node-link tree diagram using the Reingold-Tilford "tidy" algorithm
  87. export default function() {
  88. var separation = defaultSeparation,
  89. dx = 1,
  90. dy = 1,
  91. nodeSize = null;
  92. function tree(root) {
  93. var t = treeRoot(root);
  94. // Compute the layout using Buchheim et al.’s algorithm.
  95. t.eachAfter(firstWalk), t.parent.m = -t.z;
  96. t.eachBefore(secondWalk);
  97. // If a fixed node size is specified, scale x and y.
  98. if (nodeSize) root.eachBefore(sizeNode);
  99. // If a fixed tree size is specified, scale x and y based on the extent.
  100. // Compute the left-most, right-most, and depth-most nodes for extents.
  101. else {
  102. var left = root,
  103. right = root,
  104. bottom = root;
  105. root.eachBefore(function(node) {
  106. if (node.x < left.x) left = node;
  107. if (node.x > right.x) right = node;
  108. if (node.depth > bottom.depth) bottom = node;
  109. });
  110. var s = left === right ? 1 : separation(left, right) / 2,
  111. tx = s - left.x,
  112. kx = dx / (right.x + s + tx),
  113. ky = dy / (bottom.depth || 1);
  114. root.eachBefore(function(node) {
  115. node.x = (node.x + tx) * kx;
  116. node.y = node.depth * ky;
  117. });
  118. }
  119. return root;
  120. }
  121. // Computes a preliminary x-coordinate for v. Before that, FIRST WALK is
  122. // applied recursively to the children of v, as well as the function
  123. // APPORTION. After spacing out the children by calling EXECUTE SHIFTS, the
  124. // node v is placed to the midpoint of its outermost children.
  125. function firstWalk(v) {
  126. var children = v.children,
  127. siblings = v.parent.children,
  128. w = v.i ? siblings[v.i - 1] : null;
  129. if (children) {
  130. executeShifts(v);
  131. var midpoint = (children[0].z + children[children.length - 1].z) / 2;
  132. if (w) {
  133. v.z = w.z + separation(v._, w._);
  134. v.m = v.z - midpoint;
  135. } else {
  136. v.z = midpoint;
  137. }
  138. } else if (w) {
  139. v.z = w.z + separation(v._, w._);
  140. }
  141. v.parent.A = apportion(v, w, v.parent.A || siblings[0]);
  142. }
  143. // Computes all real x-coordinates by summing up the modifiers recursively.
  144. function secondWalk(v) {
  145. v._.x = v.z + v.parent.m;
  146. v.m += v.parent.m;
  147. }
  148. // The core of the algorithm. Here, a new subtree is combined with the
  149. // previous subtrees. Threads are used to traverse the inside and outside
  150. // contours of the left and right subtree up to the highest common level. The
  151. // vertices used for the traversals are vi+, vi-, vo-, and vo+, where the
  152. // superscript o means outside and i means inside, the subscript - means left
  153. // subtree and + means right subtree. For summing up the modifiers along the
  154. // contour, we use respective variables si+, si-, so-, and so+. Whenever two
  155. // nodes of the inside contours conflict, we compute the left one of the
  156. // greatest uncommon ancestors using the function ANCESTOR and call MOVE
  157. // SUBTREE to shift the subtree and prepare the shifts of smaller subtrees.
  158. // Finally, we add a new thread (if necessary).
  159. function apportion(v, w, ancestor) {
  160. if (w) {
  161. var vip = v,
  162. vop = v,
  163. vim = w,
  164. vom = vip.parent.children[0],
  165. sip = vip.m,
  166. sop = vop.m,
  167. sim = vim.m,
  168. som = vom.m,
  169. shift;
  170. while (vim = nextRight(vim), vip = nextLeft(vip), vim && vip) {
  171. vom = nextLeft(vom);
  172. vop = nextRight(vop);
  173. vop.a = v;
  174. shift = vim.z + sim - vip.z - sip + separation(vim._, vip._);
  175. if (shift > 0) {
  176. moveSubtree(nextAncestor(vim, v, ancestor), v, shift);
  177. sip += shift;
  178. sop += shift;
  179. }
  180. sim += vim.m;
  181. sip += vip.m;
  182. som += vom.m;
  183. sop += vop.m;
  184. }
  185. if (vim && !nextRight(vop)) {
  186. vop.t = vim;
  187. vop.m += sim - sop;
  188. }
  189. if (vip && !nextLeft(vom)) {
  190. vom.t = vip;
  191. vom.m += sip - som;
  192. ancestor = v;
  193. }
  194. }
  195. return ancestor;
  196. }
  197. function sizeNode(node) {
  198. node.x *= dx;
  199. node.y = node.depth * dy;
  200. }
  201. tree.separation = function(x) {
  202. return arguments.length ? (separation = x, tree) : separation;
  203. };
  204. tree.size = function(x) {
  205. return arguments.length ? (nodeSize = false, dx = +x[0], dy = +x[1], tree) : (nodeSize ? null : [dx, dy]);
  206. };
  207. tree.nodeSize = function(x) {
  208. return arguments.length ? (nodeSize = true, dx = +x[0], dy = +x[1], tree) : (nodeSize ? [dx, dy] : null);
  209. };
  210. return tree;
  211. }