layoutHelper.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. var layout = require("../../util/layout");
  2. /**
  3. * @file The layout algorithm of node-link tree diagrams. Here we using Reingold-Tilford algorithm to drawing
  4. * the tree.
  5. * @see https://github.com/d3/d3-hierarchy
  6. */
  7. /**
  8. * Initialize all computational message for following algorithm
  9. * @param {module:echarts/data/Tree~TreeNode} root The virtual root of the tree
  10. */
  11. function init(root) {
  12. root.hierNode = {
  13. defaultAncestor: null,
  14. ancestor: root,
  15. prelim: 0,
  16. modifier: 0,
  17. change: 0,
  18. shift: 0,
  19. i: 0,
  20. thread: null
  21. };
  22. var nodes = [root];
  23. var node;
  24. var children;
  25. while (node = nodes.pop()) {
  26. // jshint ignore:line
  27. children = node.children;
  28. if (node.isExpand && children.length) {
  29. var n = children.length;
  30. for (var i = n - 1; i >= 0; i--) {
  31. var child = children[i];
  32. child.hierNode = {
  33. defaultAncestor: null,
  34. ancestor: child,
  35. prelim: 0,
  36. modifier: 0,
  37. change: 0,
  38. shift: 0,
  39. i: i,
  40. thread: null
  41. };
  42. nodes.push(child);
  43. }
  44. }
  45. }
  46. }
  47. /**
  48. * Computes a preliminary x coordinate for node. Before that, this function is
  49. * applied recursively to the children of node, as well as the function
  50. * apportion(). After spacing out the children by calling executeShifts(), the
  51. * node is placed to the midpoint of its outermost children.
  52. * @param {module:echarts/data/Tree~TreeNode} node
  53. * @param {Function} separation
  54. */
  55. function firstWalk(node, separation) {
  56. var children = node.isExpand ? node.children : [];
  57. var siblings = node.parentNode.children;
  58. var subtreeW = node.hierNode.i ? siblings[node.hierNode.i - 1] : null;
  59. if (children.length) {
  60. executeShifts(node);
  61. var midPoint = (children[0].hierNode.prelim + children[children.length - 1].hierNode.prelim) / 2;
  62. if (subtreeW) {
  63. node.hierNode.prelim = subtreeW.hierNode.prelim + separation(node, subtreeW);
  64. node.hierNode.modifier = node.hierNode.prelim - midPoint;
  65. } else {
  66. node.hierNode.prelim = midPoint;
  67. }
  68. } else if (subtreeW) {
  69. node.hierNode.prelim = subtreeW.hierNode.prelim + separation(node, subtreeW);
  70. }
  71. node.parentNode.hierNode.defaultAncestor = apportion(node, subtreeW, node.parentNode.hierNode.defaultAncestor || siblings[0], separation);
  72. }
  73. /**
  74. * Computes all real x-coordinates by summing up the modifiers recursively.
  75. * @param {module:echarts/data/Tree~TreeNode} node
  76. */
  77. function secondWalk(node) {
  78. var nodeX = node.hierNode.prelim + node.parentNode.hierNode.modifier;
  79. node.setLayout({
  80. x: nodeX
  81. }, true);
  82. node.hierNode.modifier += node.parentNode.hierNode.modifier;
  83. }
  84. function separation(cb) {
  85. return arguments.length ? cb : defaultSeparation;
  86. }
  87. /**
  88. * Transform the common coordinate to radial coordinate
  89. * @param {number} x
  90. * @param {number} y
  91. * @return {Object}
  92. */
  93. function radialCoordinate(x, y) {
  94. var radialCoor = {};
  95. x -= Math.PI / 2;
  96. radialCoor.x = y * Math.cos(x);
  97. radialCoor.y = y * Math.sin(x);
  98. return radialCoor;
  99. }
  100. /**
  101. * Get the layout position of the whole view
  102. * @param {module:echarts/model/Series} seriesModel the model object of sankey series
  103. * @param {module:echarts/ExtensionAPI} api provide the API list that the developer can call
  104. * @return {module:zrender/core/BoundingRect} size of rect to draw the sankey view
  105. */
  106. function getViewRect(seriesModel, api) {
  107. return layout.getLayoutRect(seriesModel.getBoxLayoutParams(), {
  108. width: api.getWidth(),
  109. height: api.getHeight()
  110. });
  111. }
  112. /**
  113. * All other shifts, applied to the smaller subtrees between w- and w+, are
  114. * performed by this function.
  115. * @param {module:echarts/data/Tree~TreeNode} node
  116. */
  117. function executeShifts(node) {
  118. var children = node.children;
  119. var n = children.length;
  120. var shift = 0;
  121. var change = 0;
  122. while (--n >= 0) {
  123. var child = children[n];
  124. child.hierNode.prelim += shift;
  125. child.hierNode.modifier += shift;
  126. change += child.hierNode.change;
  127. shift += child.hierNode.shift + change;
  128. }
  129. }
  130. /**
  131. * The core of the algorithm. Here, a new subtree is combined with the
  132. * previous subtrees. Threads are used to traverse the inside and outside
  133. * contours of the left and right subtree up to the highest common level.
  134. * Whenever two nodes of the inside contours conflict, we compute the left
  135. * one of the greatest uncommon ancestors using the function nextAncestor()
  136. * and call moveSubtree() to shift the subtree and prepare the shifts of
  137. * smaller subtrees. Finally, we add a new thread (if necessary).
  138. * @param {module:echarts/data/Tree~TreeNode} subtreeV
  139. * @param {module:echarts/data/Tree~TreeNode} subtreeW
  140. * @param {module:echarts/data/Tree~TreeNode} ancestor
  141. * @param {Function} separation
  142. * @return {module:echarts/data/Tree~TreeNode}
  143. */
  144. function apportion(subtreeV, subtreeW, ancestor, separation) {
  145. if (subtreeW) {
  146. var nodeOutRight = subtreeV;
  147. var nodeInRight = subtreeV;
  148. var nodeOutLeft = nodeInRight.parentNode.children[0];
  149. var nodeInLeft = subtreeW;
  150. var sumOutRight = nodeOutRight.hierNode.modifier;
  151. var sumInRight = nodeInRight.hierNode.modifier;
  152. var sumOutLeft = nodeOutLeft.hierNode.modifier;
  153. var sumInLeft = nodeInLeft.hierNode.modifier;
  154. while (nodeInLeft = nextRight(nodeInLeft), nodeInRight = nextLeft(nodeInRight), nodeInLeft && nodeInRight) {
  155. nodeOutRight = nextRight(nodeOutRight);
  156. nodeOutLeft = nextLeft(nodeOutLeft);
  157. nodeOutRight.hierNode.ancestor = subtreeV;
  158. var shift = nodeInLeft.hierNode.prelim + sumInLeft - nodeInRight.hierNode.prelim - sumInRight + separation(nodeInLeft, nodeInRight);
  159. if (shift > 0) {
  160. moveSubtree(nextAncestor(nodeInLeft, subtreeV, ancestor), subtreeV, shift);
  161. sumInRight += shift;
  162. sumOutRight += shift;
  163. }
  164. sumInLeft += nodeInLeft.hierNode.modifier;
  165. sumInRight += nodeInRight.hierNode.modifier;
  166. sumOutRight += nodeOutRight.hierNode.modifier;
  167. sumOutLeft += nodeOutLeft.hierNode.modifier;
  168. }
  169. if (nodeInLeft && !nextRight(nodeOutRight)) {
  170. nodeOutRight.hierNode.thread = nodeInLeft;
  171. nodeOutRight.hierNode.modifier += sumInLeft - sumOutRight;
  172. }
  173. if (nodeInRight && !nextLeft(nodeOutLeft)) {
  174. nodeOutLeft.hierNode.thread = nodeInRight;
  175. nodeOutLeft.hierNode.modifier += sumInRight - sumOutLeft;
  176. ancestor = subtreeV;
  177. }
  178. }
  179. return ancestor;
  180. }
  181. /**
  182. * This function is used to traverse the right contour of a subtree.
  183. * It returns the rightmost child of node or the thread of node. The function
  184. * returns null if and only if node is on the highest depth of its subtree.
  185. * @param {module:echarts/data/Tree~TreeNode} node
  186. * @return {module:echarts/data/Tree~TreeNode}
  187. */
  188. function nextRight(node) {
  189. var children = node.children;
  190. return children.length && node.isExpand ? children[children.length - 1] : node.hierNode.thread;
  191. }
  192. /**
  193. * This function is used to traverse the left contour of a subtree (or a subforest).
  194. * It returns the leftmost child of node or the thread of node. The function
  195. * returns null if and only if node is on the highest depth of its subtree.
  196. * @param {module:echarts/data/Tree~TreeNode} node
  197. * @return {module:echarts/data/Tree~TreeNode}
  198. */
  199. function nextLeft(node) {
  200. var children = node.children;
  201. return children.length && node.isExpand ? children[0] : node.hierNode.thread;
  202. }
  203. /**
  204. * If nodeInLeft’s ancestor is a sibling of node, returns nodeInLeft’s ancestor.
  205. * Otherwise, returns the specified ancestor.
  206. * @param {module:echarts/data/Tree~TreeNode} nodeInLeft
  207. * @param {module:echarts/data/Tree~TreeNode} node
  208. * @param {module:echarts/data/Tree~TreeNode} ancestor
  209. * @return {module:echarts/data/Tree~TreeNode}
  210. */
  211. function nextAncestor(nodeInLeft, node, ancestor) {
  212. return nodeInLeft.hierNode.ancestor.parentNode === node.parentNode ? nodeInLeft.hierNode.ancestor : ancestor;
  213. }
  214. /**
  215. * Shifts the current subtree rooted at wr. This is done by increasing prelim(w+) and modifier(w+) by shift.
  216. * @param {module:echarts/data/Tree~TreeNode} wl
  217. * @param {module:echarts/data/Tree~TreeNode} wr
  218. * @param {number} shift [description]
  219. */
  220. function moveSubtree(wl, wr, shift) {
  221. var change = shift / (wr.hierNode.i - wl.hierNode.i);
  222. wr.hierNode.change -= change;
  223. wr.hierNode.shift += shift;
  224. wr.hierNode.modifier += shift;
  225. wr.hierNode.prelim += shift;
  226. wl.hierNode.change += change;
  227. }
  228. function defaultSeparation(node1, node2) {
  229. return node1.parentNode === node2.parentNode ? 1 : 2;
  230. }
  231. exports.init = init;
  232. exports.firstWalk = firstWalk;
  233. exports.secondWalk = secondWalk;
  234. exports.separation = separation;
  235. exports.radialCoordinate = radialCoordinate;
  236. exports.getViewRect = getViewRect;