layoutHelper.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. var layout = require("../../util/layout");
  20. /*
  21. * Licensed to the Apache Software Foundation (ASF) under one
  22. * or more contributor license agreements. See the NOTICE file
  23. * distributed with this work for additional information
  24. * regarding copyright ownership. The ASF licenses this file
  25. * to you under the Apache License, Version 2.0 (the
  26. * "License"); you may not use this file except in compliance
  27. * with the License. You may obtain a copy of the License at
  28. *
  29. * http://www.apache.org/licenses/LICENSE-2.0
  30. *
  31. * Unless required by applicable law or agreed to in writing,
  32. * software distributed under the License is distributed on an
  33. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  34. * KIND, either express or implied. See the License for the
  35. * specific language governing permissions and limitations
  36. * under the License.
  37. */
  38. /*
  39. * A third-party license is embeded for some of the code in this file:
  40. * The tree layoutHelper implementation was originally copied from
  41. * "d3.js"(https://github.com/d3/d3-hierarchy) with
  42. * some modifications made for this project.
  43. * (see more details in the comment of the specific method below.)
  44. * The use of the source code of this file is also subject to the terms
  45. * and consitions of the licence of "d3.js" (BSD-3Clause, see
  46. * </licenses/LICENSE-d3>).
  47. */
  48. /**
  49. * @file The layout algorithm of node-link tree diagrams. Here we using Reingold-Tilford algorithm to drawing
  50. * the tree.
  51. */
  52. /**
  53. * Initialize all computational message for following algorithm.
  54. *
  55. * @param {module:echarts/data/Tree~TreeNode} root The virtual root of the tree.
  56. */
  57. function init(root) {
  58. root.hierNode = {
  59. defaultAncestor: null,
  60. ancestor: root,
  61. prelim: 0,
  62. modifier: 0,
  63. change: 0,
  64. shift: 0,
  65. i: 0,
  66. thread: null
  67. };
  68. var nodes = [root];
  69. var node;
  70. var children;
  71. while (node = nodes.pop()) {
  72. // jshint ignore:line
  73. children = node.children;
  74. if (node.isExpand && children.length) {
  75. var n = children.length;
  76. for (var i = n - 1; i >= 0; i--) {
  77. var child = children[i];
  78. child.hierNode = {
  79. defaultAncestor: null,
  80. ancestor: child,
  81. prelim: 0,
  82. modifier: 0,
  83. change: 0,
  84. shift: 0,
  85. i: i,
  86. thread: null
  87. };
  88. nodes.push(child);
  89. }
  90. }
  91. }
  92. }
  93. /**
  94. * The implementation of this function was originally copied from "d3.js"
  95. * <https://github.com/d3/d3-hierarchy/blob/4c1f038f2725d6eae2e49b61d01456400694bac4/src/tree.js>
  96. * with some modifications made for this program.
  97. * See the license statement at the head of this file.
  98. *
  99. * Computes a preliminary x coordinate for node. Before that, this function is
  100. * applied recursively to the children of node, as well as the function
  101. * apportion(). After spacing out the children by calling executeShifts(), the
  102. * node is placed to the midpoint of its outermost children.
  103. *
  104. * @param {module:echarts/data/Tree~TreeNode} node
  105. * @param {Function} separation
  106. */
  107. function firstWalk(node, separation) {
  108. var children = node.isExpand ? node.children : [];
  109. var siblings = node.parentNode.children;
  110. var subtreeW = node.hierNode.i ? siblings[node.hierNode.i - 1] : null;
  111. if (children.length) {
  112. executeShifts(node);
  113. var midPoint = (children[0].hierNode.prelim + children[children.length - 1].hierNode.prelim) / 2;
  114. if (subtreeW) {
  115. node.hierNode.prelim = subtreeW.hierNode.prelim + separation(node, subtreeW);
  116. node.hierNode.modifier = node.hierNode.prelim - midPoint;
  117. } else {
  118. node.hierNode.prelim = midPoint;
  119. }
  120. } else if (subtreeW) {
  121. node.hierNode.prelim = subtreeW.hierNode.prelim + separation(node, subtreeW);
  122. }
  123. node.parentNode.hierNode.defaultAncestor = apportion(node, subtreeW, node.parentNode.hierNode.defaultAncestor || siblings[0], separation);
  124. }
  125. /**
  126. * The implementation of this function was originally copied from "d3.js"
  127. * <https://github.com/d3/d3-hierarchy/blob/4c1f038f2725d6eae2e49b61d01456400694bac4/src/tree.js>
  128. * with some modifications made for this program.
  129. * See the license statement at the head of this file.
  130. *
  131. * Computes all real x-coordinates by summing up the modifiers recursively.
  132. *
  133. * @param {module:echarts/data/Tree~TreeNode} node
  134. */
  135. function secondWalk(node) {
  136. var nodeX = node.hierNode.prelim + node.parentNode.hierNode.modifier;
  137. node.setLayout({
  138. x: nodeX
  139. }, true);
  140. node.hierNode.modifier += node.parentNode.hierNode.modifier;
  141. }
  142. function separation(cb) {
  143. return arguments.length ? cb : defaultSeparation;
  144. }
  145. /**
  146. * Transform the common coordinate to radial coordinate.
  147. *
  148. * @param {number} x
  149. * @param {number} y
  150. * @return {Object}
  151. */
  152. function radialCoordinate(x, y) {
  153. var radialCoor = {};
  154. x -= Math.PI / 2;
  155. radialCoor.x = y * Math.cos(x);
  156. radialCoor.y = y * Math.sin(x);
  157. return radialCoor;
  158. }
  159. /**
  160. * Get the layout position of the whole view.
  161. *
  162. * @param {module:echarts/model/Series} seriesModel the model object of sankey series
  163. * @param {module:echarts/ExtensionAPI} api provide the API list that the developer can call
  164. * @return {module:zrender/core/BoundingRect} size of rect to draw the sankey view
  165. */
  166. function getViewRect(seriesModel, api) {
  167. return layout.getLayoutRect(seriesModel.getBoxLayoutParams(), {
  168. width: api.getWidth(),
  169. height: api.getHeight()
  170. });
  171. }
  172. /**
  173. * All other shifts, applied to the smaller subtrees between w- and w+, are
  174. * performed by this function.
  175. *
  176. * The implementation of this function was originally copied from "d3.js"
  177. * <https://github.com/d3/d3-hierarchy/blob/4c1f038f2725d6eae2e49b61d01456400694bac4/src/tree.js>
  178. * with some modifications made for this program.
  179. * See the license statement at the head of this file.
  180. *
  181. * @param {module:echarts/data/Tree~TreeNode} node
  182. */
  183. function executeShifts(node) {
  184. var children = node.children;
  185. var n = children.length;
  186. var shift = 0;
  187. var change = 0;
  188. while (--n >= 0) {
  189. var child = children[n];
  190. child.hierNode.prelim += shift;
  191. child.hierNode.modifier += shift;
  192. change += child.hierNode.change;
  193. shift += child.hierNode.shift + change;
  194. }
  195. }
  196. /**
  197. * The implementation of this function was originally copied from "d3.js"
  198. * <https://github.com/d3/d3-hierarchy/blob/4c1f038f2725d6eae2e49b61d01456400694bac4/src/tree.js>
  199. * with some modifications made for this program.
  200. * See the license statement at the head of this file.
  201. *
  202. * The core of the algorithm. Here, a new subtree is combined with the
  203. * previous subtrees. Threads are used to traverse the inside and outside
  204. * contours of the left and right subtree up to the highest common level.
  205. * Whenever two nodes of the inside contours conflict, we compute the left
  206. * one of the greatest uncommon ancestors using the function nextAncestor()
  207. * and call moveSubtree() to shift the subtree and prepare the shifts of
  208. * smaller subtrees. Finally, we add a new thread (if necessary).
  209. *
  210. * @param {module:echarts/data/Tree~TreeNode} subtreeV
  211. * @param {module:echarts/data/Tree~TreeNode} subtreeW
  212. * @param {module:echarts/data/Tree~TreeNode} ancestor
  213. * @param {Function} separation
  214. * @return {module:echarts/data/Tree~TreeNode}
  215. */
  216. function apportion(subtreeV, subtreeW, ancestor, separation) {
  217. if (subtreeW) {
  218. var nodeOutRight = subtreeV;
  219. var nodeInRight = subtreeV;
  220. var nodeOutLeft = nodeInRight.parentNode.children[0];
  221. var nodeInLeft = subtreeW;
  222. var sumOutRight = nodeOutRight.hierNode.modifier;
  223. var sumInRight = nodeInRight.hierNode.modifier;
  224. var sumOutLeft = nodeOutLeft.hierNode.modifier;
  225. var sumInLeft = nodeInLeft.hierNode.modifier;
  226. while (nodeInLeft = nextRight(nodeInLeft), nodeInRight = nextLeft(nodeInRight), nodeInLeft && nodeInRight) {
  227. nodeOutRight = nextRight(nodeOutRight);
  228. nodeOutLeft = nextLeft(nodeOutLeft);
  229. nodeOutRight.hierNode.ancestor = subtreeV;
  230. var shift = nodeInLeft.hierNode.prelim + sumInLeft - nodeInRight.hierNode.prelim - sumInRight + separation(nodeInLeft, nodeInRight);
  231. if (shift > 0) {
  232. moveSubtree(nextAncestor(nodeInLeft, subtreeV, ancestor), subtreeV, shift);
  233. sumInRight += shift;
  234. sumOutRight += shift;
  235. }
  236. sumInLeft += nodeInLeft.hierNode.modifier;
  237. sumInRight += nodeInRight.hierNode.modifier;
  238. sumOutRight += nodeOutRight.hierNode.modifier;
  239. sumOutLeft += nodeOutLeft.hierNode.modifier;
  240. }
  241. if (nodeInLeft && !nextRight(nodeOutRight)) {
  242. nodeOutRight.hierNode.thread = nodeInLeft;
  243. nodeOutRight.hierNode.modifier += sumInLeft - sumOutRight;
  244. }
  245. if (nodeInRight && !nextLeft(nodeOutLeft)) {
  246. nodeOutLeft.hierNode.thread = nodeInRight;
  247. nodeOutLeft.hierNode.modifier += sumInRight - sumOutLeft;
  248. ancestor = subtreeV;
  249. }
  250. }
  251. return ancestor;
  252. }
  253. /**
  254. * This function is used to traverse the right contour of a subtree.
  255. * It returns the rightmost child of node or the thread of node. The function
  256. * returns null if and only if node is on the highest depth of its subtree.
  257. *
  258. * @param {module:echarts/data/Tree~TreeNode} node
  259. * @return {module:echarts/data/Tree~TreeNode}
  260. */
  261. function nextRight(node) {
  262. var children = node.children;
  263. return children.length && node.isExpand ? children[children.length - 1] : node.hierNode.thread;
  264. }
  265. /**
  266. * This function is used to traverse the left contour of a subtree (or a subforest).
  267. * It returns the leftmost child of node or the thread of node. The function
  268. * returns null if and only if node is on the highest depth of its subtree.
  269. *
  270. * @param {module:echarts/data/Tree~TreeNode} node
  271. * @return {module:echarts/data/Tree~TreeNode}
  272. */
  273. function nextLeft(node) {
  274. var children = node.children;
  275. return children.length && node.isExpand ? children[0] : node.hierNode.thread;
  276. }
  277. /**
  278. * If nodeInLeft’s ancestor is a sibling of node, returns nodeInLeft’s ancestor.
  279. * Otherwise, returns the specified ancestor.
  280. *
  281. * @param {module:echarts/data/Tree~TreeNode} nodeInLeft
  282. * @param {module:echarts/data/Tree~TreeNode} node
  283. * @param {module:echarts/data/Tree~TreeNode} ancestor
  284. * @return {module:echarts/data/Tree~TreeNode}
  285. */
  286. function nextAncestor(nodeInLeft, node, ancestor) {
  287. return nodeInLeft.hierNode.ancestor.parentNode === node.parentNode ? nodeInLeft.hierNode.ancestor : ancestor;
  288. }
  289. /**
  290. * The implementation of this function was originally copied from "d3.js"
  291. * <https://github.com/d3/d3-hierarchy/blob/4c1f038f2725d6eae2e49b61d01456400694bac4/src/tree.js>
  292. * with some modifications made for this program.
  293. * See the license statement at the head of this file.
  294. *
  295. * Shifts the current subtree rooted at wr.
  296. * This is done by increasing prelim(w+) and modifier(w+) by shift.
  297. *
  298. * @param {module:echarts/data/Tree~TreeNode} wl
  299. * @param {module:echarts/data/Tree~TreeNode} wr
  300. * @param {number} shift [description]
  301. */
  302. function moveSubtree(wl, wr, shift) {
  303. var change = shift / (wr.hierNode.i - wl.hierNode.i);
  304. wr.hierNode.change -= change;
  305. wr.hierNode.shift += shift;
  306. wr.hierNode.modifier += shift;
  307. wr.hierNode.prelim += shift;
  308. wl.hierNode.change += change;
  309. }
  310. /**
  311. * The implementation of this function was originally copied from "d3.js"
  312. * <https://github.com/d3/d3-hierarchy/blob/4c1f038f2725d6eae2e49b61d01456400694bac4/src/tree.js>
  313. * with some modifications made for this program.
  314. * See the license statement at the head of this file.
  315. */
  316. function defaultSeparation(node1, node2) {
  317. return node1.parentNode === node2.parentNode ? 1 : 2;
  318. }
  319. exports.init = init;
  320. exports.firstWalk = firstWalk;
  321. exports.secondWalk = secondWalk;
  322. exports.separation = separation;
  323. exports.radialCoordinate = radialCoordinate;
  324. exports.getViewRect = getViewRect;