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.

1178 lines
40 KiB

9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  1. 'use strict';
  2. let util = require('../../util');
  3. import NetworkUtil from '../NetworkUtil';
  4. class LayoutEngine {
  5. constructor(body) {
  6. this.body = body;
  7. this.initialRandomSeed = Math.round(Math.random() * 1000000);
  8. this.randomSeed = this.initialRandomSeed;
  9. this.options = {};
  10. this.optionsBackup = {};
  11. this.defaultOptions = {
  12. randomSeed: undefined,
  13. improvedLayout: true,
  14. hierarchical: {
  15. enabled:false,
  16. levelSeparation: 150,
  17. direction: 'UD', // UD, DU, LR, RL
  18. sortMethod: 'hubsize' // hubsize, directed
  19. }
  20. };
  21. util.extend(this.options, this.defaultOptions);
  22. this.bindEventListeners();
  23. }
  24. bindEventListeners() {
  25. this.body.emitter.on('_dataChanged', () => {
  26. this.setupHierarchicalLayout();
  27. });
  28. this.body.emitter.on('_dataLoaded', () => {
  29. this.layoutNetwork();
  30. });
  31. this.body.emitter.on('_resetHierarchicalLayout', () => {
  32. this.setupHierarchicalLayout();
  33. });
  34. }
  35. setOptions(options, allOptions) {
  36. if (options !== undefined) {
  37. let prevHierarchicalState = this.options.hierarchical.enabled;
  38. util.selectiveDeepExtend(["randomSeed", "improvedLayout"],this.options, options);
  39. util.mergeOptions(this.options, options, 'hierarchical');
  40. if (options.randomSeed !== undefined) {this.initialRandomSeed = options.randomSeed;}
  41. if (this.options.hierarchical.enabled === true) {
  42. if (prevHierarchicalState === true) {
  43. // refresh the overridden options for nodes and edges.
  44. this.body.emitter.emit('refresh', true);
  45. }
  46. // make sure the level separation is the right way up
  47. if (this.options.hierarchical.direction === 'RL' || this.options.hierarchical.direction === 'DU') {
  48. if (this.options.hierarchical.levelSeparation > 0) {
  49. this.options.hierarchical.levelSeparation *= -1;
  50. }
  51. }
  52. else {
  53. if (this.options.hierarchical.levelSeparation < 0) {
  54. this.options.hierarchical.levelSeparation *= -1;
  55. }
  56. }
  57. this.body.emitter.emit('_resetHierarchicalLayout');
  58. // because the hierarchical system needs it's own physics and smooth curve settings, we adapt the other options if needed.
  59. return this.adaptAllOptionsForHierarchicalLayout(allOptions);
  60. }
  61. else {
  62. if (prevHierarchicalState === true) {
  63. // refresh the overridden options for nodes and edges.
  64. this.body.emitter.emit('refresh');
  65. return util.deepExtend(allOptions,this.optionsBackup);
  66. }
  67. }
  68. }
  69. return allOptions;
  70. }
  71. adaptAllOptionsForHierarchicalLayout(allOptions) {
  72. if (this.options.hierarchical.enabled === true) {
  73. // set the physics
  74. if (allOptions.physics === undefined || allOptions.physics === true) {
  75. allOptions.physics = {solver: 'hierarchicalRepulsion'};
  76. this.optionsBackup.physics = {solver:'barnesHut'};
  77. }
  78. else if (typeof allOptions.physics === 'object') {
  79. this.optionsBackup.physics = {solver:'barnesHut'};
  80. if (allOptions.physics.solver !== undefined) {
  81. this.optionsBackup.physics = {solver:allOptions.physics.solver};
  82. }
  83. allOptions.physics['solver'] = 'hierarchicalRepulsion';
  84. }
  85. else if (allOptions.physics !== false) {
  86. this.optionsBackup.physics = {solver:'barnesHut'};
  87. allOptions.physics['solver'] = 'hierarchicalRepulsion';
  88. }
  89. // get the type of static smooth curve in case it is required
  90. let type = 'horizontal';
  91. if (this.options.hierarchical.direction === 'RL' || this.options.hierarchical.direction === 'LR') {
  92. type = 'vertical';
  93. }
  94. // disable smooth curves if nothing is defined. If smooth curves have been turned on, turn them into static smooth curves.
  95. if (allOptions.edges === undefined) {
  96. this.optionsBackup.edges = {smooth:{enabled:true, type:'dynamic'}};
  97. allOptions.edges = {smooth: false};
  98. }
  99. else if (allOptions.edges.smooth === undefined) {
  100. this.optionsBackup.edges = {smooth:{enabled:true, type:'dynamic'}};
  101. allOptions.edges.smooth = false;
  102. }
  103. else {
  104. if (typeof allOptions.edges.smooth === 'boolean') {
  105. this.optionsBackup.edges = {smooth:allOptions.edges.smooth};
  106. allOptions.edges.smooth = {enabled: allOptions.edges.smooth, type:type}
  107. }
  108. else {
  109. // allow custom types except for dynamic
  110. if (allOptions.edges.smooth.type !== undefined && allOptions.edges.smooth.type !== 'dynamic') {
  111. type = allOptions.edges.smooth.type;
  112. }
  113. this.optionsBackup.edges = {
  114. smooth: allOptions.edges.smooth.enabled === undefined ? true : allOptions.edges.smooth.enabled,
  115. type:allOptions.edges.smooth.type === undefined ? 'dynamic' : allOptions.edges.smooth.type,
  116. roundness: allOptions.edges.smooth.roundness === undefined ? 0.5 : allOptions.edges.smooth.roundness,
  117. forceDirection: allOptions.edges.smooth.forceDirection === undefined ? false : allOptions.edges.smooth.forceDirection
  118. };
  119. allOptions.edges.smooth = {
  120. enabled: allOptions.edges.smooth.enabled === undefined ? true : allOptions.edges.smooth.enabled,
  121. type:type,
  122. roundness: allOptions.edges.smooth.roundness === undefined ? 0.5 : allOptions.edges.smooth.roundness,
  123. forceDirection: allOptions.edges.smooth.forceDirection === undefined ? false : allOptions.edges.smooth.forceDirection
  124. }
  125. }
  126. }
  127. // force all edges into static smooth curves. Only applies to edges that do not use the global options for smooth.
  128. this.body.emitter.emit('_forceDisableDynamicCurves', type);
  129. }
  130. return allOptions;
  131. }
  132. seededRandom() {
  133. let x = Math.sin(this.randomSeed++) * 10000;
  134. return x - Math.floor(x);
  135. }
  136. positionInitially(nodesArray) {
  137. if (this.options.hierarchical.enabled !== true) {
  138. this.randomSeed = this.initialRandomSeed;
  139. for (let i = 0; i < nodesArray.length; i++) {
  140. let node = nodesArray[i];
  141. let radius = 10 * 0.1 * nodesArray.length + 10;
  142. let angle = 2 * Math.PI * this.seededRandom();
  143. if (node.x === undefined) {
  144. node.x = radius * Math.cos(angle);
  145. }
  146. if (node.y === undefined) {
  147. node.y = radius * Math.sin(angle);
  148. }
  149. }
  150. }
  151. }
  152. /**
  153. * Use Kamada Kawai to position nodes. This is quite a heavy algorithm so if there are a lot of nodes we
  154. * cluster them first to reduce the amount.
  155. */
  156. layoutNetwork() {
  157. if (this.options.hierarchical.enabled !== true && this.options.improvedLayout === true) {
  158. // first check if we should Kamada Kawai to layout. The threshold is if less than half of the visible
  159. // nodes have predefined positions we use this.
  160. let positionDefined = 0;
  161. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  162. let node = this.body.nodes[this.body.nodeIndices[i]];
  163. if (node.predefinedPosition === true) {
  164. positionDefined += 1;
  165. }
  166. }
  167. // if less than half of the nodes have a predefined position we continue
  168. if (positionDefined < 0.5 * this.body.nodeIndices.length) {
  169. let MAX_LEVELS = 10;
  170. let level = 0;
  171. let clusterThreshold = 100;
  172. // if there are a lot of nodes, we cluster before we run the algorithm.
  173. if (this.body.nodeIndices.length > clusterThreshold) {
  174. let startLength = this.body.nodeIndices.length;
  175. while (this.body.nodeIndices.length > clusterThreshold) {
  176. //console.time("clustering")
  177. level += 1;
  178. let before = this.body.nodeIndices.length;
  179. // if there are many nodes we do a hubsize cluster
  180. if (level % 3 === 0) {
  181. this.body.modules.clustering.clusterBridges();
  182. }
  183. else {
  184. this.body.modules.clustering.clusterOutliers();
  185. }
  186. let after = this.body.nodeIndices.length;
  187. if ((before == after && level % 3 !== 0) || level > MAX_LEVELS) {
  188. this._declusterAll();
  189. this.body.emitter.emit("_layoutFailed");
  190. console.info("This network could not be positioned by this version of the improved layout algorithm. Please disable improvedLayout for better performance.");
  191. return;
  192. }
  193. //console.timeEnd("clustering")
  194. //console.log(level,after)
  195. }
  196. // increase the size of the edges
  197. this.body.modules.kamadaKawai.setOptions({springLength: Math.max(150, 2 * startLength)})
  198. }
  199. // position the system for these nodes and edges
  200. this.body.modules.kamadaKawai.solve(this.body.nodeIndices, this.body.edgeIndices, true);
  201. // shift to center point
  202. this._shiftToCenter();
  203. // perturb the nodes a little bit to force the physics to kick in
  204. let offset = 70;
  205. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  206. this.body.nodes[this.body.nodeIndices[i]].x += (0.5 - this.seededRandom())*offset;
  207. this.body.nodes[this.body.nodeIndices[i]].y += (0.5 - this.seededRandom())*offset;
  208. }
  209. // uncluster all clusters
  210. this._declusterAll();
  211. // reposition all bezier nodes.
  212. this.body.emitter.emit("_repositionBezierNodes");
  213. }
  214. }
  215. }
  216. /**
  217. * Move all the nodes towards to the center so gravitational pull wil not move the nodes away from view
  218. * @private
  219. */
  220. _shiftToCenter() {
  221. let range = NetworkUtil.getRangeCore(this.body.nodes, this.body.nodeIndices);
  222. let center = NetworkUtil.findCenter(range);
  223. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  224. this.body.nodes[this.body.nodeIndices[i]].x -= center.x;
  225. this.body.nodes[this.body.nodeIndices[i]].y -= center.y;
  226. }
  227. }
  228. _declusterAll() {
  229. let clustersPresent = true;
  230. while (clustersPresent === true) {
  231. clustersPresent = false;
  232. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  233. if (this.body.nodes[this.body.nodeIndices[i]].isCluster === true) {
  234. clustersPresent = true;
  235. this.body.modules.clustering.openCluster(this.body.nodeIndices[i], {}, false);
  236. }
  237. }
  238. if (clustersPresent === true) {
  239. this.body.emitter.emit('_dataChanged');
  240. }
  241. }
  242. }
  243. getSeed() {
  244. return this.initialRandomSeed;
  245. }
  246. /**
  247. * This is the main function to layout the nodes in a hierarchical way.
  248. * It checks if the node details are supplied correctly
  249. *
  250. * @private
  251. */
  252. setupHierarchicalLayout() {
  253. if (this.options.hierarchical.enabled === true && this.body.nodeIndices.length > 0) {
  254. // get the size of the largest hubs and check if the user has defined a level for a node.
  255. let node, nodeId;
  256. let definedLevel = false;
  257. let undefinedLevel = false;
  258. this.hierarchicalLevels = {};
  259. this.lastNodeOnLevel = {};
  260. this.hierarchicalParents = {};
  261. this.hierarchicalChildren = {};
  262. this.hierarchicalTrees = {};
  263. this.treeIndex = -1;
  264. this.whiteSpaceReductionFactor = 0.5;
  265. this.nodeSpacing = 100;
  266. this.treeSpacing = 2 * this.nodeSpacing;
  267. this.distributionOrdering = {};
  268. this.distributionIndex = {};
  269. this.distributionOrderingPresence = {};
  270. for (nodeId in this.body.nodes) {
  271. if (this.body.nodes.hasOwnProperty(nodeId)) {
  272. node = this.body.nodes[nodeId];
  273. if (node.options.level !== undefined) {
  274. definedLevel = true;
  275. this.hierarchicalLevels[nodeId] = node.options.level;
  276. }
  277. else {
  278. undefinedLevel = true;
  279. }
  280. }
  281. }
  282. // if the user defined some levels but not all, alert and run without hierarchical layout
  283. if (undefinedLevel === true && definedLevel === true) {
  284. throw new Error('To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.');
  285. return;
  286. }
  287. else {
  288. // define levels if undefined by the users. Based on hubsize.
  289. if (undefinedLevel === true) {
  290. if (this.options.hierarchical.sortMethod === 'hubsize') {
  291. this._determineLevelsByHubsize();
  292. }
  293. else if (this.options.hierarchical.sortMethod === 'directed') {
  294. this._determineLevelsDirected();
  295. }
  296. else if (this.options.hierarchical.sortMethod === 'custom') {
  297. this._determineLevelsCustomCallback();
  298. }
  299. }
  300. // check the distribution of the nodes per level.
  301. let distribution = this._getDistribution();
  302. // get the parent children relations.
  303. this._generateMap();
  304. // place the nodes on the canvas.
  305. this._placeNodesByHierarchy(distribution);
  306. // condense the whitespace.
  307. this._condenseHierarchy(distribution);
  308. // shift to center so gravity does not have to do much
  309. this._shiftToCenter();
  310. }
  311. }
  312. }
  313. /**
  314. * @private
  315. */
  316. _condenseHierarchy(distribution) {
  317. // first we have some methods to help shifting trees around.
  318. // the main method to shift the trees
  319. let shiftTrees = () => {
  320. let treeSizes = getTreeSizes();
  321. for (let i = 0; i < treeSizes.length - 1; i++) {
  322. let diff = treeSizes[i].max - treeSizes[i+1].min;
  323. if (diff !== this.treeSpacing) {
  324. shiftTree(i + 1, diff - this.treeSpacing);
  325. }
  326. }
  327. };
  328. // shift a single tree by an offset
  329. let shiftTree = (index, offset) => {
  330. for (let nodeId in this.hierarchicalTrees) {
  331. if (this.hierarchicalTrees.hasOwnProperty(nodeId)) {
  332. if (this.hierarchicalTrees[nodeId] === index) {
  333. this._setPositionForHierarchy(this.body.nodes[nodeId], offset, undefined, true);
  334. }
  335. }
  336. }
  337. };
  338. // get the width of a tree
  339. let getTreeSize = (index) => {
  340. let min = 1e9;
  341. let max = -1e9;
  342. for (let nodeId in this.hierarchicalTrees) {
  343. if (this.hierarchicalTrees.hasOwnProperty(nodeId)) {
  344. if (this.hierarchicalTrees[nodeId] === index) {
  345. let pos = this._getPositionForHierarchy(this.body.nodes[nodeId]);
  346. min = Math.min(pos, min);
  347. max = Math.max(pos, max);
  348. }
  349. }
  350. }
  351. return [min, max];
  352. };
  353. // get the width of all trees
  354. let getTreeSizes = () => {
  355. let treeWidths = [];
  356. for (let i = 0; i < this.treeIndex; i++) {
  357. treeWidths.push(getTreeSize(i));
  358. }
  359. return treeWidths;
  360. };
  361. // get a map of all nodes in this branch
  362. let getBranchNodes = (source, map) => {
  363. map[source.id] = true;
  364. if (this.hierarchicalParents[source.id]) {
  365. let children = this.hierarchicalParents[source.id].children;
  366. if (children.length > 0) {
  367. for (let i = 0; i < children.length; i++) {
  368. getBranchNodes(this.body.nodes[children[i]], map);
  369. }
  370. }
  371. }
  372. };
  373. // get a min max width as well as the maximum movement space it has on either sides
  374. // we use min max terminology because width and height can interchange depending on the direction of the layout
  375. let getBranchBoundary = (branchMap, maxLevel = 1e9) => {
  376. let minSpace = 1e9;
  377. let maxSpace = -1e9;
  378. let min = 1e9;
  379. let max = -1e9;
  380. for (let branchNode in branchMap) {
  381. if (branchMap.hasOwnProperty(branchNode)) {
  382. let node = this.body.nodes[branchNode];
  383. let level = this.hierarchicalLevels[node.id];
  384. let index = this.distributionIndex[node.id];
  385. let position = this._getPositionForHierarchy(this.body.nodes[node.id]);
  386. // if this is the node at the side, there is no previous node
  387. if (index != 0) {
  388. let prevNode = this.distributionOrdering[level][index - 1];
  389. if (branchMap[prevNode.id] === undefined) {
  390. let prevPos = this._getPositionForHierarchy(prevNode);
  391. minSpace = Math.min(minSpace, position - prevPos);
  392. }
  393. }
  394. // if this is the node at the end there is no next node
  395. if (index != this.distributionOrdering[level].length - 1) {
  396. let nextNode = this.distributionOrdering[level][index + 1];
  397. if (branchMap[nextNode.id] === undefined) {
  398. let nextPos = this._getPositionForHierarchy(nextNode);
  399. maxSpace = Math.max(maxSpace, nextPos - position);
  400. }
  401. }
  402. // the width is only relevant for the levels two nodes have in common. This is why we filter on this.
  403. if (level <= maxLevel) {
  404. min = Math.min(position, min);
  405. max = Math.max(position, max);
  406. }
  407. }
  408. }
  409. // if there was no next node, the max space is infinite (1e9 ~ close enough)
  410. maxSpace = maxSpace < 0 ? 1e9 : maxSpace;
  411. return [min, max, minSpace, maxSpace];
  412. };
  413. // get the maximum level of a branch.
  414. let getMaxLevel = (nodeId) => {
  415. let level = this.hierarchicalLevels[nodeId];
  416. if (this.hierarchicalParents[nodeId]) {
  417. let children = this.hierarchicalParents[nodeId].children;
  418. if (children.length > 0) {
  419. for (let i = 0; i < children.length; i++) {
  420. level = Math.max(level,getMaxLevel(children[i]));
  421. }
  422. }
  423. }
  424. return level;
  425. };
  426. // check what the maximum level is these nodes have in common.
  427. let getCollisionLevel = (node1, node2) => {
  428. let maxLevel1 = getMaxLevel(node1.id);
  429. let maxLevel2 = getMaxLevel(node2.id);
  430. return Math.min(maxLevel1, maxLevel2);
  431. };
  432. // check if two nodes have the same parent(s)
  433. let hasSameParent = (node1, node2) => {
  434. let parents1 = this.hierarchicalChildren[node1.id];
  435. let parents2 = this.hierarchicalChildren[node2.id];
  436. if (parents1 === undefined || parents2 === undefined) {
  437. return false;
  438. }
  439. parents1 = parents1.parents;
  440. parents2 = parents2.parents;
  441. for (let i = 0; i < parents1.length; i++) {
  442. for (let j = 0; j < parents2.length; j++) {
  443. if (parents1[i] == parents2[j]) {
  444. return true;
  445. }
  446. }
  447. }
  448. return false;
  449. };
  450. // condense elements. These can be nodes or branches depending on the callback.
  451. let shiftElementsCloser = (callback, levels, centerParents) => {
  452. for (let i = 0; i < levels.length; i++) {
  453. let level = levels[i];
  454. let levelNodes = this.distributionOrdering[level];
  455. if (levelNodes.length > 1) {
  456. for (let i = 0; i < levelNodes.length - 1; i++) {
  457. if (hasSameParent(levelNodes[i],levelNodes[i+1]) === true) {
  458. if (this.hierarchicalTrees[levelNodes[i].id] === this.hierarchicalTrees[levelNodes[i+1].id]) {
  459. callback(levelNodes[i],levelNodes[i+1], centerParents);
  460. }
  461. }}
  462. }
  463. }
  464. };
  465. // Global var in this scope to define when the movement has stopped.
  466. let stillShifting = false;
  467. // callback for shifting branches
  468. let branchShiftCallback = (node1, node2, centerParent = false) => {
  469. //window.CALLBACKS.push(() => {
  470. let pos1 = this._getPositionForHierarchy(node1);
  471. let pos2 = this._getPositionForHierarchy(node2);
  472. let diffAbs = Math.abs(pos2 - pos1);
  473. //console.log("NOW CHEcKING:", node1.id, node2.id, diffAbs);
  474. if (diffAbs > this.nodeSpacing) {
  475. let branchNodes1 = {}; branchNodes1[node1.id] = true;
  476. let branchNodes2 = {}; branchNodes2[node2.id] = true;
  477. getBranchNodes(node1, branchNodes1);
  478. getBranchNodes(node2, branchNodes2);
  479. // check the largest distance between the branches
  480. let maxLevel = getCollisionLevel(node1, node2);
  481. let [min1,max1, minSpace1, maxSpace1] = getBranchBoundary(branchNodes1, maxLevel);
  482. let [min2,max2, minSpace2, maxSpace2] = getBranchBoundary(branchNodes2, maxLevel);
  483. //console.log(node1.id, getBranchBoundary(branchNodes1, maxLevel), node2.id, getBranchBoundary(branchNodes2, maxLevel), maxLevel);
  484. let diffBranch = Math.abs(max1 - min2);
  485. if (diffBranch > this.nodeSpacing) {
  486. let offset = max1 - min2 + this.nodeSpacing;
  487. if (offset < -minSpace2 + this.nodeSpacing) {
  488. offset = -minSpace2 + this.nodeSpacing;
  489. //console.log("RESETTING OFFSET", max1 - min2 + this.nodeSpacing, -minSpace2, offset);
  490. }
  491. if (offset < 0) {
  492. //console.log("SHIFTING", node2.id, offset);
  493. this._shiftBlock(node2.id, offset);
  494. stillShifting = true;
  495. if (centerParent === true)
  496. this._centerParent(node2);
  497. }
  498. }
  499. }
  500. //this.body.emitter.emit("_redraw");})
  501. };
  502. // callback for shifting individual nodes
  503. let unitShiftCallback = (node1, node2, centerParent) => {
  504. let pos1 = this._getPositionForHierarchy(node1);
  505. let pos2 = this._getPositionForHierarchy(node2);
  506. let diffAbs = Math.abs(pos2 - pos1);
  507. //console.log("NOW CHEcKING:", node1.id, node2.id, diffAbs);
  508. if (diffAbs > this.nodeSpacing) {
  509. let diff = (pos1 + this.nodeSpacing - pos2) * this.whiteSpaceReductionFactor;
  510. if (diff != 0) {
  511. stillShifting = true;
  512. }
  513. let factor = node2.edges.length / (node1.edges.length + node2.edges.length);
  514. this._setPositionForHierarchy(node2, pos2 + factor * diff, undefined, true);
  515. this._setPositionForHierarchy(node1, pos1 - (1-factor) * diff, undefined, true);
  516. if (centerParent === true) {
  517. this._centerParent(node2);
  518. }
  519. }
  520. };
  521. // method to shift all nodes closer together iteratively
  522. let shiftUnitsCloser = (iterations) => {
  523. let levels = Object.keys(this.distributionOrdering);
  524. for (let i = 0; i < iterations; i++) {
  525. stillShifting = false;
  526. shiftElementsCloser(unitShiftCallback, levels, false);
  527. if (stillShifting !== true) {
  528. //console.log("FINISHED shiftUnitsCloser IN " + i);
  529. break;
  530. }
  531. }
  532. //console.log("FINISHED shiftUnitsCloser IN " + iterations);
  533. };
  534. // method to remove whitespace between branches. Because we do bottom up, we can center the parents.
  535. let shiftBranchesCloserBottomUp = (iterations) => {
  536. let levels = Object.keys(this.distributionOrdering);
  537. levels = levels.reverse();
  538. for (let i = 0; i < iterations; i++) {
  539. stillShifting = false;
  540. shiftElementsCloser(branchShiftCallback, levels, true);
  541. if (stillShifting !== true) {
  542. //console.log("FINISHED shiftBranchesCloserBottomUp IN " + i);
  543. break;
  544. }
  545. }
  546. };
  547. // center all parents
  548. let centerAllParents = () => {
  549. for (let node in this.body.nodes) {
  550. this._centerParent(this.body.nodes[node]);
  551. }
  552. };
  553. // the actual work is done here.
  554. shiftBranchesCloserBottomUp(5);
  555. centerAllParents();
  556. shiftUnitsCloser(2);
  557. shiftTrees();
  558. }
  559. /**
  560. * We use this method to center a parent node and check if it does not cross other nodes when it does.
  561. * @param node
  562. * @private
  563. */
  564. _centerParent(node) {
  565. if (this.hierarchicalChildren[node.id]) {
  566. let parents = this.hierarchicalChildren[node.id].parents;
  567. for (var i = 0; i < parents.length; i++) {
  568. let parentId = parents[i];
  569. let parentNode = this.body.nodes[parentId];
  570. if (this.hierarchicalParents[parentId]) {
  571. // get the range of the children
  572. let minPos = 1e9;
  573. let maxPos = -1e9;
  574. let children = this.hierarchicalParents[parentId].children;
  575. if (children.length > 0) {
  576. for (let i = 0; i < children.length; i++) {
  577. let childNode = this.body.nodes[children[i]];
  578. minPos = Math.min(minPos, this._getPositionForHierarchy(childNode));
  579. maxPos = Math.max(maxPos, this._getPositionForHierarchy(childNode));
  580. }
  581. }
  582. let level = this.hierarchicalLevels[parentId];
  583. let index = this.distributionIndex[parentId];
  584. let position = this._getPositionForHierarchy(parentNode);
  585. let minSpace = 1e9;
  586. let maxSpace = 1e9;
  587. if (index != 0) {
  588. let prevNode = this.distributionOrdering[level][index - 1];
  589. let prevPos = this._getPositionForHierarchy(prevNode);
  590. minSpace = position - prevPos;
  591. }
  592. if (index != this.distributionOrdering[level].length - 1) {
  593. let nextNode = this.distributionOrdering[level][index + 1];
  594. let nextPos = this._getPositionForHierarchy(nextNode);
  595. maxSpace = Math.min(maxSpace, nextPos - position);
  596. }
  597. let newPosition = 0.5 * (minPos + maxPos);
  598. if (newPosition < position + maxSpace && newPosition > position - minSpace) {
  599. this._setPositionForHierarchy(parentNode, newPosition, undefined, true);
  600. }
  601. }
  602. }
  603. }
  604. }
  605. /**
  606. * This function places the nodes on the canvas based on the hierarchial distribution.
  607. *
  608. * @param {Object} distribution | obtained by the function this._getDistribution()
  609. * @private
  610. */
  611. _placeNodesByHierarchy(distribution) {
  612. this.positionedNodes = {};
  613. // start placing all the level 0 nodes first. Then recursively position their branches.
  614. for (let level in distribution) {
  615. if (distribution.hasOwnProperty(level)) {
  616. // sort nodes in level by position:
  617. let nodeArray = Object.keys(distribution[level]);
  618. nodeArray = this._indexArrayToNodes(nodeArray);
  619. this._sortNodeArray(nodeArray);
  620. for (let i = 0; i < nodeArray.length; i++) {
  621. let node = nodeArray[i];
  622. if (this.positionedNodes[node.id] === undefined) {
  623. this._setPositionForHierarchy(node, this.nodeSpacing * i, level);
  624. this.positionedNodes[node.id] = true;
  625. this._placeBranchNodes(node.id, level);
  626. }
  627. }
  628. }
  629. }
  630. }
  631. /**
  632. * Receives an array with node indices and returns an array with the actual node references. Used for sorting based on
  633. * node properties.
  634. * @param idArray
  635. */
  636. _indexArrayToNodes(idArray) {
  637. let array = [];
  638. for (let i = 0; i < idArray.length; i++) {
  639. array.push(this.body.nodes[idArray[i]])
  640. }
  641. return array;
  642. }
  643. /**
  644. * This function get the distribution of levels based on hubsize
  645. *
  646. * @returns {Object}
  647. * @private
  648. */
  649. _getDistribution() {
  650. let distribution = {};
  651. let nodeId, node;
  652. // 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.
  653. // the fix of X is removed after the x value has been set.
  654. for (nodeId in this.body.nodes) {
  655. if (this.body.nodes.hasOwnProperty(nodeId)) {
  656. node = this.body.nodes[nodeId];
  657. let level = this.hierarchicalLevels[nodeId] === undefined ? 0 : this.hierarchicalLevels[nodeId];
  658. if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') {
  659. node.y = this.options.hierarchical.levelSeparation * level;
  660. node.options.fixed.y = true;
  661. }
  662. else {
  663. node.x = this.options.hierarchical.levelSeparation * level;
  664. node.options.fixed.x = true;
  665. }
  666. if (distribution[level] === undefined) {
  667. distribution[level] = {};
  668. }
  669. distribution[level][nodeId] = node;
  670. }
  671. }
  672. return distribution;
  673. }
  674. /**
  675. * Get the hubsize from all remaining unlevelled nodes.
  676. *
  677. * @returns {number}
  678. * @private
  679. */
  680. _getHubSize() {
  681. let hubSize = 0;
  682. for (let nodeId in this.body.nodes) {
  683. if (this.body.nodes.hasOwnProperty(nodeId)) {
  684. let node = this.body.nodes[nodeId];
  685. if (this.hierarchicalLevels[nodeId] === undefined) {
  686. hubSize = node.edges.length < hubSize ? hubSize : node.edges.length;
  687. }
  688. }
  689. }
  690. return hubSize;
  691. }
  692. /**
  693. * this function allocates nodes in levels based on the recursive branching from the largest hubs.
  694. *
  695. * @param hubsize
  696. * @private
  697. */
  698. _determineLevelsByHubsize() {
  699. let hubSize = 1;
  700. let levelDownstream = (nodeA, nodeB) => {
  701. if (this.hierarchicalLevels[nodeB.id] === undefined) {
  702. // set initial level
  703. if (this.hierarchicalLevels[nodeA.id] === undefined) {
  704. this.hierarchicalLevels[nodeA.id] = 0;
  705. }
  706. // set level
  707. this.hierarchicalLevels[nodeB.id] = this.hierarchicalLevels[nodeA.id] + 1;
  708. }
  709. };
  710. while (hubSize > 0) {
  711. // determine hubs
  712. hubSize = this._getHubSize();
  713. if (hubSize === 0)
  714. break;
  715. for (let nodeId in this.body.nodes) {
  716. if (this.body.nodes.hasOwnProperty(nodeId)) {
  717. let node = this.body.nodes[nodeId];
  718. if (node.edges.length === hubSize) {
  719. this._crawlNetwork(levelDownstream,nodeId);
  720. }
  721. }
  722. }
  723. }
  724. }
  725. /**
  726. * TODO: release feature
  727. * @private
  728. */
  729. _determineLevelsCustomCallback() {
  730. let minLevel = 100000;
  731. // TODO: this should come from options.
  732. let customCallback = function(nodeA, nodeB, edge) {
  733. };
  734. let levelByDirection = (nodeA, nodeB, edge) => {
  735. let levelA = this.hierarchicalLevels[nodeA.id];
  736. // set initial level
  737. if (levelA === undefined) {this.hierarchicalLevels[nodeA.id] = minLevel;}
  738. let diff = customCallback(
  739. NetworkUtil.cloneOptions(nodeA,'node'),
  740. NetworkUtil.cloneOptions(nodeB,'node'),
  741. NetworkUtil.cloneOptions(edge,'edge')
  742. );
  743. this.hierarchicalLevels[nodeB.id] = this.hierarchicalLevels[nodeA.id] + diff;
  744. };
  745. this._crawlNetwork(levelByDirection);
  746. this._setMinLevelToZero();
  747. }
  748. /**
  749. * this function allocates nodes in levels based on the direction of the edges
  750. *
  751. * @param hubsize
  752. * @private
  753. */
  754. _determineLevelsDirected() {
  755. let minLevel = 10000;
  756. let levelByDirection = (nodeA, nodeB, edge) => {
  757. let levelA = this.hierarchicalLevels[nodeA.id];
  758. // set initial level
  759. if (levelA === undefined) {this.hierarchicalLevels[nodeA.id] = minLevel;}
  760. if (edge.toId == nodeB.id) {
  761. this.hierarchicalLevels[nodeB.id] = this.hierarchicalLevels[nodeA.id] + 1;
  762. }
  763. else {
  764. this.hierarchicalLevels[nodeB.id] = this.hierarchicalLevels[nodeA.id] - 1;
  765. }
  766. };
  767. this._crawlNetwork(levelByDirection);
  768. this._setMinLevelToZero();
  769. }
  770. /**
  771. * Small util method to set the minimum levels of the nodes to zero.
  772. * @private
  773. */
  774. _setMinLevelToZero() {
  775. let minLevel = 1e9;
  776. // get the minimum level
  777. for (let nodeId in this.body.nodes) {
  778. if (this.body.nodes.hasOwnProperty(nodeId)) {
  779. minLevel = Math.min(this.hierarchicalLevels[nodeId], minLevel);
  780. }
  781. }
  782. // subtract the minimum from the set so we have a range starting from 0
  783. for (let nodeId in this.body.nodes) {
  784. if (this.body.nodes.hasOwnProperty(nodeId)) {
  785. this.hierarchicalLevels[nodeId] -= minLevel;
  786. }
  787. }
  788. }
  789. /**
  790. * Update the bookkeeping of parent and child.
  791. * @param parentNodeId
  792. * @param childNodeId
  793. * @private
  794. */
  795. _generateMap() {
  796. let fillInRelations = (parentNode, childNode) => {
  797. if (this.hierarchicalLevels[childNode.id] > this.hierarchicalLevels[parentNode.id]) {
  798. let parentNodeId = parentNode.id;
  799. let childNodeId = childNode.id;
  800. if (this.hierarchicalParents[parentNodeId] === undefined) {
  801. this.hierarchicalParents[parentNodeId] = {children: [], amount: 0};
  802. }
  803. this.hierarchicalParents[parentNodeId].children.push(childNodeId);
  804. if (this.hierarchicalChildren[childNodeId] === undefined) {
  805. this.hierarchicalChildren[childNodeId] = {parents: [], amount: 0};
  806. }
  807. this.hierarchicalChildren[childNodeId].parents.push(parentNodeId);
  808. }
  809. };
  810. this._crawlNetwork(fillInRelations);
  811. }
  812. /**
  813. * Crawl over the entire network and use a callback on each node couple that is connected to each other.
  814. * @param callback | will receive nodeA nodeB and the connecting edge. A and B are unique.
  815. * @param startingNodeId
  816. * @private
  817. */
  818. _crawlNetwork(callback = function() {}, startingNodeId) {
  819. let progress = {};
  820. let crawler = (node) => {
  821. if (progress[node.id] === undefined) {
  822. progress[node.id] = true;
  823. let childNode;
  824. for (let i = 0; i < node.edges.length; i++) {
  825. if (node.edges[i].toId === node.id) {childNode = node.edges[i].from;}
  826. else {childNode = node.edges[i].to;}
  827. if (node.id !== childNode.id) {
  828. callback(node, childNode, node.edges[i]);
  829. crawler(childNode);
  830. }
  831. }
  832. }
  833. };
  834. // we can crawl from a specific node or over all nodes.
  835. if (startingNodeId === undefined) {
  836. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  837. let node = this.body.nodes[this.body.nodeIndices[i]];
  838. crawler(node);
  839. }
  840. }
  841. else {
  842. let node = this.body.nodes[startingNodeId];
  843. if (node === undefined) {
  844. console.error("Node not found:", startingNodeId);
  845. return;
  846. }
  847. crawler(node);
  848. }
  849. }
  850. /**
  851. * This is a recursively called function to enumerate the branches from the largest hubs and place the nodes
  852. * on a X position that ensures there will be no overlap.
  853. *
  854. * @param parentId
  855. * @param parentLevel
  856. * @private
  857. */
  858. _placeBranchNodes(parentId, parentLevel) {
  859. // if this is not a parent, cancel the placing. This can happen with multiple parents to one child.
  860. if (this.hierarchicalParents[parentId] === undefined) {
  861. return;
  862. }
  863. // get a list of childNodes
  864. let childNodes = [];
  865. for (let i = 0; i < this.hierarchicalParents[parentId].children.length; i++) {
  866. childNodes.push(this.body.nodes[this.hierarchicalParents[parentId].children[i]]);
  867. }
  868. // use the positions to order the nodes.
  869. this._sortNodeArray(childNodes);
  870. // position the childNodes
  871. for (let i = 0; i < childNodes.length; i++) {
  872. let childNode = childNodes[i];
  873. let childNodeLevel = this.hierarchicalLevels[childNode.id];
  874. // check if the child node is below the parent node and if it has already been positioned.
  875. if (childNodeLevel > parentLevel && this.positionedNodes[childNode.id] === undefined) {
  876. // get the amount of space required for this node. If parent the width is based on the amount of children.
  877. let pos;
  878. // we get the X or Y values we need and store them in pos and previousPos. The get and set make sure we get X or Y
  879. if (i === 0) {pos = this._getPositionForHierarchy(this.body.nodes[parentId]);}
  880. else {pos = this._getPositionForHierarchy(childNodes[i-1]) + this.nodeSpacing;}
  881. this._setPositionForHierarchy(childNode, pos, childNodeLevel);
  882. // if overlap has been detected, we shift the branch
  883. if (this.lastNodeOnLevel[childNodeLevel] !== undefined) {
  884. let previousPos = this._getPositionForHierarchy(this.body.nodes[this.lastNodeOnLevel[childNodeLevel]]);
  885. if (pos - previousPos < this.nodeSpacing) {
  886. let diff = (previousPos + this.nodeSpacing) - pos;
  887. let sharedParent = this._findCommonParent(this.lastNodeOnLevel[childNodeLevel], childNode.id);
  888. this._shiftBlock(sharedParent.withChild, diff);
  889. }
  890. }
  891. // store change in position.
  892. this.lastNodeOnLevel[childNodeLevel] = childNode.id;
  893. this.positionedNodes[childNode.id] = true;
  894. this._placeBranchNodes(childNode.id, childNodeLevel);
  895. }
  896. else {
  897. return;
  898. }
  899. }
  900. // center the parent nodes.
  901. let minPos = 1e9;
  902. let maxPos = -1e9;
  903. for (let i = 0; i < childNodes.length; i++) {
  904. let childNodeId = childNodes[i].id;
  905. minPos = Math.min(minPos, this._getPositionForHierarchy(this.body.nodes[childNodeId]));
  906. maxPos = Math.max(maxPos, this._getPositionForHierarchy(this.body.nodes[childNodeId]));
  907. }
  908. this._setPositionForHierarchy(this.body.nodes[parentId], 0.5 * (minPos + maxPos), parentLevel);
  909. }
  910. /**
  911. * Shift a branch a certain distance
  912. * @param parentId
  913. * @param diff
  914. * @private
  915. */
  916. _shiftBlock(parentId, diff) {
  917. if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') {
  918. this.body.nodes[parentId].x += diff;
  919. }
  920. else {
  921. this.body.nodes[parentId].y += diff;
  922. }
  923. if (this.hierarchicalParents[parentId] !== undefined) {
  924. for (let i = 0; i < this.hierarchicalParents[parentId].children.length; i++) {
  925. this._shiftBlock(this.hierarchicalParents[parentId].children[i], diff);
  926. }
  927. }
  928. }
  929. /**
  930. * Find a common parent between branches.
  931. * @param childA
  932. * @param childB
  933. * @returns {{foundParent, withChild}}
  934. * @private
  935. */
  936. _findCommonParent(childA,childB) {
  937. let parents = {};
  938. let iterateParents = (parents,child) => {
  939. if (this.hierarchicalChildren[child] !== undefined) {
  940. for (let i = 0; i < this.hierarchicalChildren[child].parents.length; i++) {
  941. let parent = this.hierarchicalChildren[child].parents[i];
  942. parents[parent] = true;
  943. iterateParents(parents, parent)
  944. }
  945. }
  946. };
  947. let findParent = (parents, child) => {
  948. if (this.hierarchicalChildren[child] !== undefined) {
  949. for (let i = 0; i < this.hierarchicalChildren[child].parents.length; i++) {
  950. let parent = this.hierarchicalChildren[child].parents[i];
  951. if (parents[parent] !== undefined) {
  952. return {foundParent:parent, withChild:child};
  953. }
  954. let branch = findParent(parents, parent);
  955. if (branch.foundParent !== null) {
  956. return branch;
  957. }
  958. }
  959. }
  960. return {foundParent:null, withChild:child};
  961. };
  962. iterateParents(parents, childA);
  963. return findParent(parents, childB);
  964. }
  965. /**
  966. * Abstract the getting of the position so we won't have to repeat the check for direction all the time
  967. * @param node
  968. * @param position
  969. * @param level
  970. * @private
  971. */
  972. _setPositionForHierarchy(node, position, level, doNotUpdate = false) {
  973. if (doNotUpdate !== true) {
  974. if (this.distributionOrdering[level] === undefined) {
  975. this.distributionOrdering[level] = [];
  976. this.distributionOrderingPresence[level] = {};
  977. }
  978. if (this.distributionOrderingPresence[level][node.id] === undefined) {
  979. this.distributionOrdering[level].push(node);
  980. this.distributionIndex[node.id] = this.distributionOrdering[level].length - 1;
  981. }
  982. this.distributionOrderingPresence[level][node.id] = true;
  983. if (this.hierarchicalTrees[node.id] === undefined) {
  984. if (this.hierarchicalChildren[node.id] !== undefined) {
  985. let tree = 1;
  986. // get the lowest tree denominator.
  987. for (let i = 0; i < this.hierarchicalChildren[node.id].parents.length; i++) {
  988. let parentId = this.hierarchicalChildren[node.id].parents[i];
  989. if (this.hierarchicalTrees[parentId] !== undefined) {
  990. //tree = Math.min(tree,this.hierarchicalTrees[parentId]);
  991. tree = this.hierarchicalTrees[parentId];
  992. }
  993. }
  994. //for (let i = 0; i < this.hierarchicalChildren.parents.length; i++) {
  995. // let parentId = this.hierarchicalChildren.parents[i];
  996. // this.hierarchicalTrees[parentId] = tree;
  997. //}
  998. this.hierarchicalTrees[node.id] = tree;
  999. }
  1000. else {
  1001. this.hierarchicalTrees[node.id] = ++this.treeIndex;
  1002. }
  1003. }
  1004. }
  1005. if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') {
  1006. node.x = position;
  1007. }
  1008. else {
  1009. node.y = position;
  1010. }
  1011. }
  1012. /**
  1013. * Abstract the getting of the position of a node so we do not have to repeat the direction check all the time.
  1014. * @param node
  1015. * @returns {number|*}
  1016. * @private
  1017. */
  1018. _getPositionForHierarchy(node) {
  1019. if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') {
  1020. return node.x;
  1021. }
  1022. else {
  1023. return node.y;
  1024. }
  1025. }
  1026. /**
  1027. * Use the x or y value to sort the array, allowing users to specify order.
  1028. * @param nodeArray
  1029. * @private
  1030. */
  1031. _sortNodeArray(nodeArray) {
  1032. if (nodeArray.length > 1) {
  1033. if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') {
  1034. nodeArray.sort(function (a, b) {
  1035. return a.x - b.x;
  1036. })
  1037. }
  1038. else {
  1039. nodeArray.sort(function (a, b) {
  1040. return a.y - b.y;
  1041. })
  1042. }
  1043. }
  1044. }
  1045. }
  1046. export default LayoutEngine;