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.

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