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.

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