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.

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