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.

304 lines
8.8 KiB

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