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.

424 lines
12 KiB

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