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