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.

322 lines
9.5 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. throw new Error("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 (var level in distribution) {
  88. if (distribution.hasOwnProperty(level)) {
  89. for (nodeId in distribution[level].nodes) {
  90. if (distribution[level].nodes.hasOwnProperty(nodeId)) {
  91. node = distribution[level].nodes[nodeId];
  92. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  93. if (node.xFixed) {
  94. node.x = distribution[level].minPos;
  95. node.xFixed = false;
  96. distribution[level].minPos += distribution[level].nodeSpacing;
  97. }
  98. }
  99. else {
  100. if (node.yFixed) {
  101. node.y = distribution[level].minPos;
  102. node.yFixed = false;
  103. distribution[level].minPos += distribution[level].nodeSpacing;
  104. }
  105. }
  106. this._placeBranchNodes(node.edges,node.id,distribution,node.level);
  107. }
  108. }
  109. }
  110. }
  111. // stabilize the system after positioning. This function calls zoomExtent.
  112. this._stabilize();
  113. };
  114. /**
  115. * This function get the distribution of levels based on hubsize
  116. *
  117. * @returns {Object}
  118. * @private
  119. */
  120. exports._getDistribution = function() {
  121. var distribution = {};
  122. var nodeId, node, level;
  123. // 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.
  124. // the fix of X is removed after the x value has been set.
  125. for (nodeId in this.nodes) {
  126. if (this.nodes.hasOwnProperty(nodeId)) {
  127. node = this.nodes[nodeId];
  128. node.xFixed = true;
  129. node.yFixed = true;
  130. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  131. node.y = this.constants.hierarchicalLayout.levelSeparation*node.level;
  132. }
  133. else {
  134. node.x = this.constants.hierarchicalLayout.levelSeparation*node.level;
  135. }
  136. if (distribution[node.level] === undefined) {
  137. distribution[node.level] = {amount: 0, nodes: {}, minPos:0, nodeSpacing:0};
  138. }
  139. distribution[node.level].amount += 1;
  140. distribution[node.level].nodes[nodeId] = node;
  141. }
  142. }
  143. // determine the largest amount of nodes of all levels
  144. var maxCount = 0;
  145. for (level in distribution) {
  146. if (distribution.hasOwnProperty(level)) {
  147. if (maxCount < distribution[level].amount) {
  148. maxCount = distribution[level].amount;
  149. }
  150. }
  151. }
  152. // set the initial position and spacing of each nodes accordingly
  153. for (level in distribution) {
  154. if (distribution.hasOwnProperty(level)) {
  155. distribution[level].nodeSpacing = (maxCount + 1) * this.constants.hierarchicalLayout.nodeSpacing;
  156. distribution[level].nodeSpacing /= (distribution[level].amount + 1);
  157. distribution[level].minPos = distribution[level].nodeSpacing - (0.5 * (distribution[level].amount + 1) * distribution[level].nodeSpacing);
  158. }
  159. }
  160. return distribution;
  161. };
  162. /**
  163. * this function allocates nodes in levels based on the recursive branching from the largest hubs.
  164. *
  165. * @param hubsize
  166. * @private
  167. */
  168. exports._determineLevels = function(hubsize) {
  169. var nodeId, node;
  170. // determine hubs
  171. for (nodeId in this.nodes) {
  172. if (this.nodes.hasOwnProperty(nodeId)) {
  173. node = this.nodes[nodeId];
  174. if (node.edges.length == hubsize) {
  175. node.level = 0;
  176. }
  177. }
  178. }
  179. // branch from hubs
  180. for (nodeId in this.nodes) {
  181. if (this.nodes.hasOwnProperty(nodeId)) {
  182. node = this.nodes[nodeId];
  183. if (node.level == 0) {
  184. this._setLevel(1,node.edges,node.id);
  185. }
  186. }
  187. }
  188. };
  189. /**
  190. * Since hierarchical layout does not support:
  191. * - smooth curves (based on the physics),
  192. * - clustering (based on dynamic node counts)
  193. *
  194. * We disable both features so there will be no problems.
  195. *
  196. * @private
  197. */
  198. exports._changeConstants = function() {
  199. this.constants.clustering.enabled = false;
  200. this.constants.physics.barnesHut.enabled = false;
  201. this.constants.physics.hierarchicalRepulsion.enabled = true;
  202. this._loadSelectedForceSolver();
  203. if (this.constants.smoothCurves.enabled == true) {
  204. this.constants.smoothCurves.dynamic = false;
  205. }
  206. this._configureSmoothCurves();
  207. };
  208. /**
  209. * This is a recursively called function to enumerate the branches from the largest hubs and place the nodes
  210. * on a X position that ensures there will be no overlap.
  211. *
  212. * @param edges
  213. * @param parentId
  214. * @param distribution
  215. * @param parentLevel
  216. * @private
  217. */
  218. exports._placeBranchNodes = function(edges, parentId, distribution, parentLevel) {
  219. for (var i = 0; i < edges.length; i++) {
  220. var childNode = null;
  221. if (edges[i].toId == parentId) {
  222. childNode = edges[i].from;
  223. }
  224. else {
  225. childNode = edges[i].to;
  226. }
  227. // if a node is conneceted to another node on the same level (or higher (means lower level))!, this is not handled here.
  228. var nodeMoved = false;
  229. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  230. if (childNode.xFixed && childNode.level > parentLevel) {
  231. childNode.xFixed = false;
  232. childNode.x = distribution[childNode.level].minPos;
  233. nodeMoved = true;
  234. }
  235. }
  236. else {
  237. if (childNode.yFixed && childNode.level > parentLevel) {
  238. childNode.yFixed = false;
  239. childNode.y = distribution[childNode.level].minPos;
  240. nodeMoved = true;
  241. }
  242. }
  243. if (nodeMoved == true) {
  244. distribution[childNode.level].minPos += distribution[childNode.level].nodeSpacing;
  245. if (childNode.edges.length > 1) {
  246. this._placeBranchNodes(childNode.edges,childNode.id,distribution,childNode.level);
  247. }
  248. }
  249. }
  250. };
  251. /**
  252. * this function is called recursively to enumerate the barnches of the largest hubs and give each node a level.
  253. *
  254. * @param level
  255. * @param edges
  256. * @param parentId
  257. * @private
  258. */
  259. exports._setLevel = function(level, edges, parentId) {
  260. for (var i = 0; i < edges.length; i++) {
  261. var childNode = null;
  262. if (edges[i].toId == parentId) {
  263. childNode = edges[i].from;
  264. }
  265. else {
  266. childNode = edges[i].to;
  267. }
  268. if (childNode.level == -1 || childNode.level > level) {
  269. childNode.level = level;
  270. if (edges.length > 1) {
  271. this._setLevel(level+1, childNode.edges, childNode.id);
  272. }
  273. }
  274. }
  275. };
  276. /**
  277. * Unfix nodes
  278. *
  279. * @private
  280. */
  281. exports._restoreNodes = function() {
  282. for (var nodeId in this.nodes) {
  283. if (this.nodes.hasOwnProperty(nodeId)) {
  284. this.nodes[nodeId].xFixed = false;
  285. this.nodes[nodeId].yFixed = false;
  286. }
  287. }
  288. };