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.

411 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 *= -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 recursive branching from the largest hubs.
  197. *
  198. * @param hubsize
  199. * @private
  200. */
  201. exports._determineLevelsDirected = function() {
  202. var nodeId, node;
  203. // set first node to source
  204. for (nodeId in this.nodes) {
  205. if (this.nodes.hasOwnProperty(nodeId)) {
  206. this.nodes[nodeId].level = 10000;
  207. break;
  208. }
  209. }
  210. // branch from hubs
  211. for (nodeId in this.nodes) {
  212. if (this.nodes.hasOwnProperty(nodeId)) {
  213. node = this.nodes[nodeId];
  214. if (node.level == 10000) {
  215. this._setLevelDirected(10000,node.edges,node.id);
  216. }
  217. }
  218. }
  219. // branch from hubs
  220. var minLevel = 10000;
  221. for (nodeId in this.nodes) {
  222. if (this.nodes.hasOwnProperty(nodeId)) {
  223. node = this.nodes[nodeId];
  224. minLevel = node.level < minLevel ? node.level : minLevel;
  225. }
  226. }
  227. // branch from hubs
  228. for (nodeId in this.nodes) {
  229. if (this.nodes.hasOwnProperty(nodeId)) {
  230. node = this.nodes[nodeId];
  231. node.level -= minLevel;
  232. }
  233. }
  234. };
  235. /**
  236. * Since hierarchical layout does not support:
  237. * - smooth curves (based on the physics),
  238. * - clustering (based on dynamic node counts)
  239. *
  240. * We disable both features so there will be no problems.
  241. *
  242. * @private
  243. */
  244. exports._changeConstants = function() {
  245. this.constants.clustering.enabled = false;
  246. this.constants.physics.barnesHut.enabled = false;
  247. this.constants.physics.hierarchicalRepulsion.enabled = true;
  248. this._loadSelectedForceSolver();
  249. if (this.constants.smoothCurves.enabled == true) {
  250. this.constants.smoothCurves.dynamic = false;
  251. }
  252. this._configureSmoothCurves();
  253. };
  254. /**
  255. * This is a recursively called function to enumerate the branches from the largest hubs and place the nodes
  256. * on a X position that ensures there will be no overlap.
  257. *
  258. * @param edges
  259. * @param parentId
  260. * @param distribution
  261. * @param parentLevel
  262. * @private
  263. */
  264. exports._placeBranchNodes = function(edges, parentId, distribution, parentLevel) {
  265. for (var i = 0; i < edges.length; i++) {
  266. var childNode = null;
  267. if (edges[i].toId == parentId) {
  268. childNode = edges[i].from;
  269. }
  270. else {
  271. childNode = edges[i].to;
  272. }
  273. // if a node is conneceted to another node on the same level (or higher (means lower level))!, this is not handled here.
  274. var nodeMoved = false;
  275. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  276. if (childNode.xFixed && childNode.level > parentLevel) {
  277. childNode.xFixed = false;
  278. childNode.x = distribution[childNode.level].minPos;
  279. nodeMoved = true;
  280. }
  281. }
  282. else {
  283. if (childNode.yFixed && childNode.level > parentLevel) {
  284. childNode.yFixed = false;
  285. childNode.y = distribution[childNode.level].minPos;
  286. nodeMoved = true;
  287. }
  288. }
  289. if (nodeMoved == true) {
  290. distribution[childNode.level].minPos += distribution[childNode.level].nodeSpacing;
  291. if (childNode.edges.length > 1) {
  292. this._placeBranchNodes(childNode.edges,childNode.id,distribution,childNode.level);
  293. }
  294. }
  295. }
  296. };
  297. /**
  298. * this function is called recursively to enumerate the barnches of the largest hubs and give each node a level.
  299. *
  300. * @param level
  301. * @param edges
  302. * @param parentId
  303. * @private
  304. */
  305. exports._setLevel = function(level, edges, parentId) {
  306. for (var i = 0; i < edges.length; i++) {
  307. var childNode = null;
  308. if (edges[i].toId == parentId) {
  309. childNode = edges[i].from;
  310. }
  311. else {
  312. childNode = edges[i].to;
  313. }
  314. if (childNode.level == -1 || childNode.level > level) {
  315. childNode.level = level;
  316. if (childNode.edges.length > 1) {
  317. this._setLevel(level+1, childNode.edges, childNode.id);
  318. }
  319. }
  320. }
  321. };
  322. /**
  323. * this function is called recursively to enumerate the barnches of the largest hubs and give each node a level.
  324. *
  325. * @param level
  326. * @param edges
  327. * @param parentId
  328. * @private
  329. */
  330. exports._setLevelDirected = function(level, edges, parentId) {
  331. this.nodes[parentId].hierarchyEnumerated = true;
  332. for (var i = 0; i < edges.length; i++) {
  333. var childNode = null;
  334. var direction = 1;
  335. if (edges[i].toId == parentId) {
  336. childNode = edges[i].from;
  337. direction = -1;
  338. }
  339. else {
  340. childNode = edges[i].to;
  341. }
  342. if (childNode.level == -1) {
  343. childNode.level = level + direction;
  344. }
  345. }
  346. for (var i = 0; i < edges.length; i++) {
  347. var childNode = null;
  348. if (edges[i].toId == parentId) {childNode = edges[i].from;}
  349. else {childNode = edges[i].to;}
  350. if (childNode.edges.length > 1 && childNode.hierarchyEnumerated === false) {
  351. this._setLevelDirected(childNode.level, childNode.edges, childNode.id);
  352. }
  353. }
  354. };
  355. /**
  356. * Unfix nodes
  357. *
  358. * @private
  359. */
  360. exports._restoreNodes = function() {
  361. for (var nodeId in this.nodes) {
  362. if (this.nodes.hasOwnProperty(nodeId)) {
  363. this.nodes[nodeId].xFixed = false;
  364. this.nodes[nodeId].yFixed = false;
  365. }
  366. }
  367. };