vis.js is a dynamic, browser-based visualization library
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.

310 lines
9.3 KiB

  1. var HierarchicalLayoutMixin = {
  2. _resetLevels : function() {
  3. for (var nodeId in this.nodes) {
  4. if (this.nodes.hasOwnProperty(nodeId)) {
  5. var node = this.nodes[nodeId];
  6. if (node.preassignedLevel == false) {
  7. node.level = -1;
  8. }
  9. }
  10. }
  11. },
  12. /**
  13. * This is the main function to layout the nodes in a hierarchical way.
  14. * It checks if the node details are supplied correctly
  15. *
  16. * @private
  17. */
  18. _setupHierarchicalLayout : function() {
  19. if (this.constants.hierarchicalLayout.enabled == true && this.nodeIndices.length > 0) {
  20. if (this.constants.hierarchicalLayout.direction == "RL" || this.constants.hierarchicalLayout.direction == "DU") {
  21. this.constants.hierarchicalLayout.levelSeparation *= -1;
  22. }
  23. else {
  24. this.constants.hierarchicalLayout.levelSeparation = Math.abs(this.constants.hierarchicalLayout.levelSeparation);
  25. }
  26. // get the size of the largest hubs and check if the user has defined a level for a node.
  27. var hubsize = 0;
  28. var node, nodeId;
  29. var definedLevel = false;
  30. var undefinedLevel = false;
  31. for (nodeId in this.nodes) {
  32. if (this.nodes.hasOwnProperty(nodeId)) {
  33. node = this.nodes[nodeId];
  34. if (node.level != -1) {
  35. definedLevel = true;
  36. }
  37. else {
  38. undefinedLevel = true;
  39. }
  40. if (hubsize < node.edges.length) {
  41. hubsize = node.edges.length;
  42. }
  43. }
  44. }
  45. // if the user defined some levels but not all, alert and run without hierarchical layout
  46. if (undefinedLevel == true && definedLevel == true) {
  47. alert("To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.");
  48. this.zoomExtent(true,this.constants.clustering.enabled);
  49. if (!this.constants.clustering.enabled) {
  50. this.start();
  51. }
  52. }
  53. else {
  54. // setup the system to use hierarchical method.
  55. this._changeConstants();
  56. // define levels if undefined by the users. Based on hubsize
  57. if (undefinedLevel == true) {
  58. this._determineLevels(hubsize);
  59. }
  60. // check the distribution of the nodes per level.
  61. var distribution = this._getDistribution();
  62. // place the nodes on the canvas. This also stablilizes the system.
  63. this._placeNodesByHierarchy(distribution);
  64. // start the simulation.
  65. this.start();
  66. }
  67. }
  68. },
  69. /**
  70. * This function places the nodes on the canvas based on the hierarchial distribution.
  71. *
  72. * @param {Object} distribution | obtained by the function this._getDistribution()
  73. * @private
  74. */
  75. _placeNodesByHierarchy : function(distribution) {
  76. var nodeId, node;
  77. // start placing all the level 0 nodes first. Then recursively position their branches.
  78. for (nodeId in distribution[0].nodes) {
  79. if (distribution[0].nodes.hasOwnProperty(nodeId)) {
  80. node = distribution[0].nodes[nodeId];
  81. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  82. if (node.xFixed) {
  83. node.x = distribution[0].minPos;
  84. node.xFixed = false;
  85. distribution[0].minPos += distribution[0].nodeSpacing;
  86. }
  87. }
  88. else {
  89. if (node.yFixed) {
  90. node.y = distribution[0].minPos;
  91. node.yFixed = false;
  92. distribution[0].minPos += distribution[0].nodeSpacing;
  93. }
  94. }
  95. this._placeBranchNodes(node.edges,node.id,distribution,node.level);
  96. }
  97. }
  98. // stabilize the system after positioning. This function calls zoomExtent.
  99. this._stabilize();
  100. },
  101. /**
  102. * This function get the distribution of levels based on hubsize
  103. *
  104. * @returns {Object}
  105. * @private
  106. */
  107. _getDistribution : function() {
  108. var distribution = {};
  109. var nodeId, node, level;
  110. // we fix Y because the hierarchy is vertical, we fix X so we do not give a node an x position for a second time.
  111. // the fix of X is removed after the x value has been set.
  112. for (nodeId in this.nodes) {
  113. if (this.nodes.hasOwnProperty(nodeId)) {
  114. node = this.nodes[nodeId];
  115. node.xFixed = true;
  116. node.yFixed = true;
  117. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  118. node.y = this.constants.hierarchicalLayout.levelSeparation*node.level;
  119. }
  120. else {
  121. node.x = this.constants.hierarchicalLayout.levelSeparation*node.level;
  122. }
  123. if (!distribution.hasOwnProperty(node.level)) {
  124. distribution[node.level] = {amount: 0, nodes: {}, minPos:0, nodeSpacing:0};
  125. }
  126. distribution[node.level].amount += 1;
  127. distribution[node.level].nodes[node.id] = node;
  128. }
  129. }
  130. // determine the largest amount of nodes of all levels
  131. var maxCount = 0;
  132. for (level in distribution) {
  133. if (distribution.hasOwnProperty(level)) {
  134. if (maxCount < distribution[level].amount) {
  135. maxCount = distribution[level].amount;
  136. }
  137. }
  138. }
  139. // set the initial position and spacing of each nodes accordingly
  140. for (level in distribution) {
  141. if (distribution.hasOwnProperty(level)) {
  142. distribution[level].nodeSpacing = (maxCount + 1) * this.constants.hierarchicalLayout.nodeSpacing;
  143. distribution[level].nodeSpacing /= (distribution[level].amount + 1);
  144. distribution[level].minPos = distribution[level].nodeSpacing - (0.5 * (distribution[level].amount + 1) * distribution[level].nodeSpacing);
  145. }
  146. }
  147. return distribution;
  148. },
  149. /**
  150. * this function allocates nodes in levels based on the recursive branching from the largest hubs.
  151. *
  152. * @param hubsize
  153. * @private
  154. */
  155. _determineLevels : function(hubsize) {
  156. var nodeId, node;
  157. // determine hubs
  158. for (nodeId in this.nodes) {
  159. if (this.nodes.hasOwnProperty(nodeId)) {
  160. node = this.nodes[nodeId];
  161. if (node.edges.length == hubsize) {
  162. node.level = 0;
  163. }
  164. }
  165. }
  166. // branch from hubs
  167. for (nodeId in this.nodes) {
  168. if (this.nodes.hasOwnProperty(nodeId)) {
  169. node = this.nodes[nodeId];
  170. if (node.level == 0) {
  171. this._setLevel(1,node.edges,node.id);
  172. }
  173. }
  174. }
  175. },
  176. /**
  177. * Since hierarchical layout does not support:
  178. * - smooth curves (based on the physics),
  179. * - clustering (based on dynamic node counts)
  180. *
  181. * We disable both features so there will be no problems.
  182. *
  183. * @private
  184. */
  185. _changeConstants : function() {
  186. this.constants.clustering.enabled = false;
  187. this.constants.physics.barnesHut.enabled = false;
  188. this.constants.physics.hierarchicalRepulsion.enabled = true;
  189. this._loadSelectedForceSolver();
  190. this.constants.smoothCurves = false;
  191. this._configureSmoothCurves();
  192. },
  193. /**
  194. * This is a recursively called function to enumerate the branches from the largest hubs and place the nodes
  195. * on a X position that ensures there will be no overlap.
  196. *
  197. * @param edges
  198. * @param parentId
  199. * @param distribution
  200. * @param parentLevel
  201. * @private
  202. */
  203. _placeBranchNodes : function(edges, parentId, distribution, parentLevel) {
  204. for (var i = 0; i < edges.length; i++) {
  205. var childNode = null;
  206. if (edges[i].toId == parentId) {
  207. childNode = edges[i].from;
  208. }
  209. else {
  210. childNode = edges[i].to;
  211. }
  212. // if a node is conneceted to another node on the same level (or higher (means lower level))!, this is not handled here.
  213. var nodeMoved = false;
  214. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  215. if (childNode.xFixed && childNode.level > parentLevel) {
  216. childNode.xFixed = false;
  217. childNode.x = distribution[childNode.level].minPos;
  218. nodeMoved = true;
  219. }
  220. }
  221. else {
  222. if (childNode.yFixed && childNode.level > parentLevel) {
  223. childNode.yFixed = false;
  224. childNode.y = distribution[childNode.level].minPos;
  225. nodeMoved = true;
  226. }
  227. }
  228. if (nodeMoved == true) {
  229. distribution[childNode.level].minPos += distribution[childNode.level].nodeSpacing;
  230. if (childNode.edges.length > 1) {
  231. this._placeBranchNodes(childNode.edges,childNode.id,distribution,childNode.level);
  232. }
  233. }
  234. }
  235. },
  236. /**
  237. * this function is called recursively to enumerate the barnches of the largest hubs and give each node a level.
  238. *
  239. * @param level
  240. * @param edges
  241. * @param parentId
  242. * @private
  243. */
  244. _setLevel : function(level, edges, parentId) {
  245. for (var i = 0; i < edges.length; i++) {
  246. var childNode = null;
  247. if (edges[i].toId == parentId) {
  248. childNode = edges[i].from;
  249. }
  250. else {
  251. childNode = edges[i].to;
  252. }
  253. if (childNode.level == -1 || childNode.level > level) {
  254. childNode.level = level;
  255. if (edges.length > 1) {
  256. this._setLevel(level+1, childNode.edges, childNode.id);
  257. }
  258. }
  259. }
  260. },
  261. /**
  262. * Unfix nodes
  263. *
  264. * @private
  265. */
  266. _restoreNodes : function() {
  267. for (nodeId in this.nodes) {
  268. if (this.nodes.hasOwnProperty(nodeId)) {
  269. this.nodes[nodeId].xFixed = false;
  270. this.nodes[nodeId].yFixed = false;
  271. }
  272. }
  273. }
  274. };