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.

835 lines
27 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.lastNodeOnLevel = {};
  23. this.hierarchicalParents = {};
  24. this.hierarchicalChildren = {};
  25. this.distributionOrdering = {};
  26. this.distributionOrderingPresence = {};
  27. this.bindEventListeners();
  28. }
  29. bindEventListeners() {
  30. this.body.emitter.on('_dataChanged', () => {
  31. this.setupHierarchicalLayout();
  32. });
  33. this.body.emitter.on('_dataLoaded', () => {
  34. this.layoutNetwork();
  35. });
  36. this.body.emitter.on('_resetHierarchicalLayout', () => {
  37. this.setupHierarchicalLayout();
  38. });
  39. }
  40. setOptions(options, allOptions) {
  41. if (options !== undefined) {
  42. let prevHierarchicalState = this.options.hierarchical.enabled;
  43. util.selectiveDeepExtend(["randomSeed", "improvedLayout"],this.options, options);
  44. util.mergeOptions(this.options, options, 'hierarchical');
  45. if (options.randomSeed !== undefined) {this.initialRandomSeed = options.randomSeed;}
  46. if (this.options.hierarchical.enabled === true) {
  47. if (prevHierarchicalState === true) {
  48. // refresh the overridden options for nodes and edges.
  49. this.body.emitter.emit('refresh', true);
  50. }
  51. // make sure the level separation is the right way up
  52. if (this.options.hierarchical.direction === 'RL' || this.options.hierarchical.direction === 'DU') {
  53. if (this.options.hierarchical.levelSeparation > 0) {
  54. this.options.hierarchical.levelSeparation *= -1;
  55. }
  56. }
  57. else {
  58. if (this.options.hierarchical.levelSeparation < 0) {
  59. this.options.hierarchical.levelSeparation *= -1;
  60. }
  61. }
  62. this.body.emitter.emit('_resetHierarchicalLayout');
  63. // because the hierarchical system needs it's own physics and smooth curve settings, we adapt the other options if needed.
  64. return this.adaptAllOptionsForHierarchicalLayout(allOptions);
  65. }
  66. else {
  67. if (prevHierarchicalState === true) {
  68. // refresh the overridden options for nodes and edges.
  69. this.body.emitter.emit('refresh');
  70. return util.deepExtend(allOptions,this.optionsBackup);
  71. }
  72. }
  73. }
  74. return allOptions;
  75. }
  76. adaptAllOptionsForHierarchicalLayout(allOptions) {
  77. if (this.options.hierarchical.enabled === true) {
  78. // set the physics
  79. if (allOptions.physics === undefined || allOptions.physics === true) {
  80. allOptions.physics = {solver: 'hierarchicalRepulsion'};
  81. this.optionsBackup.physics = {solver:'barnesHut'};
  82. }
  83. else if (typeof allOptions.physics === 'object') {
  84. this.optionsBackup.physics = {solver:'barnesHut'};
  85. if (allOptions.physics.solver !== undefined) {
  86. this.optionsBackup.physics = {solver:allOptions.physics.solver};
  87. }
  88. allOptions.physics['solver'] = 'hierarchicalRepulsion';
  89. }
  90. else if (allOptions.physics !== false) {
  91. this.optionsBackup.physics = {solver:'barnesHut'};
  92. allOptions.physics['solver'] = 'hierarchicalRepulsion';
  93. }
  94. // get the type of static smooth curve in case it is required
  95. let type = 'horizontal';
  96. if (this.options.hierarchical.direction === 'RL' || this.options.hierarchical.direction === 'LR') {
  97. type = 'vertical';
  98. }
  99. // disable smooth curves if nothing is defined. If smooth curves have been turned on, turn them into static smooth curves.
  100. if (allOptions.edges === undefined) {
  101. this.optionsBackup.edges = {smooth:{enabled:true, type:'dynamic'}};
  102. allOptions.edges = {smooth: false};
  103. }
  104. else if (allOptions.edges.smooth === undefined) {
  105. this.optionsBackup.edges = {smooth:{enabled:true, type:'dynamic'}};
  106. allOptions.edges.smooth = false;
  107. }
  108. else {
  109. if (typeof allOptions.edges.smooth === 'boolean') {
  110. this.optionsBackup.edges = {smooth:allOptions.edges.smooth};
  111. allOptions.edges.smooth = {enabled: allOptions.edges.smooth, type:type}
  112. }
  113. else {
  114. // allow custom types except for dynamic
  115. if (allOptions.edges.smooth.type !== undefined && allOptions.edges.smooth.type !== 'dynamic') {
  116. type = allOptions.edges.smooth.type;
  117. }
  118. this.optionsBackup.edges = {
  119. smooth: allOptions.edges.smooth.enabled === undefined ? true : allOptions.edges.smooth.enabled,
  120. type:allOptions.edges.smooth.type === undefined ? 'dynamic' : allOptions.edges.smooth.type,
  121. roundness: allOptions.edges.smooth.roundness === undefined ? 0.5 : allOptions.edges.smooth.roundness,
  122. forceDirection: allOptions.edges.smooth.forceDirection === undefined ? false : allOptions.edges.smooth.forceDirection
  123. };
  124. allOptions.edges.smooth = {
  125. enabled: allOptions.edges.smooth.enabled === undefined ? true : allOptions.edges.smooth.enabled,
  126. type:type,
  127. roundness: allOptions.edges.smooth.roundness === undefined ? 0.5 : allOptions.edges.smooth.roundness,
  128. forceDirection: allOptions.edges.smooth.forceDirection === undefined ? false : allOptions.edges.smooth.forceDirection
  129. }
  130. }
  131. }
  132. // force all edges into static smooth curves. Only applies to edges that do not use the global options for smooth.
  133. this.body.emitter.emit('_forceDisableDynamicCurves', type);
  134. }
  135. return allOptions;
  136. }
  137. seededRandom() {
  138. let x = Math.sin(this.randomSeed++) * 10000;
  139. return x - Math.floor(x);
  140. }
  141. positionInitially(nodesArray) {
  142. if (this.options.hierarchical.enabled !== true) {
  143. this.randomSeed = this.initialRandomSeed;
  144. for (let i = 0; i < nodesArray.length; i++) {
  145. let node = nodesArray[i];
  146. let radius = 10 * 0.1 * nodesArray.length + 10;
  147. let angle = 2 * Math.PI * this.seededRandom();
  148. if (node.x === undefined) {
  149. node.x = radius * Math.cos(angle);
  150. }
  151. if (node.y === undefined) {
  152. node.y = radius * Math.sin(angle);
  153. }
  154. }
  155. }
  156. }
  157. /**
  158. * Use Kamada Kawai to position nodes. This is quite a heavy algorithm so if there are a lot of nodes we
  159. * cluster them first to reduce the amount.
  160. */
  161. layoutNetwork() {
  162. if (this.options.hierarchical.enabled !== true && this.options.improvedLayout === true) {
  163. // first check if we should Kamada Kawai to layout. The threshold is if less than half of the visible
  164. // nodes have predefined positions we use this.
  165. let positionDefined = 0;
  166. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  167. let node = this.body.nodes[this.body.nodeIndices[i]];
  168. if (node.predefinedPosition === true) {
  169. positionDefined += 1;
  170. }
  171. }
  172. // if less than half of the nodes have a predefined position we continue
  173. if (positionDefined < 0.5 * this.body.nodeIndices.length) {
  174. let MAX_LEVELS = 10;
  175. let level = 0;
  176. let clusterThreshold = 100;
  177. // if there are a lot of nodes, we cluster before we run the algorithm.
  178. if (this.body.nodeIndices.length > clusterThreshold) {
  179. let startLength = this.body.nodeIndices.length;
  180. while (this.body.nodeIndices.length > clusterThreshold) {
  181. //console.time("clustering")
  182. level += 1;
  183. let before = this.body.nodeIndices.length;
  184. // if there are many nodes we do a hubsize cluster
  185. if (level % 3 === 0) {
  186. this.body.modules.clustering.clusterBridges();
  187. }
  188. else {
  189. this.body.modules.clustering.clusterOutliers();
  190. }
  191. let after = this.body.nodeIndices.length;
  192. if ((before == after && level % 3 !== 0) || level > MAX_LEVELS) {
  193. this._declusterAll();
  194. this.body.emitter.emit("_layoutFailed");
  195. console.info("This network could not be positioned by this version of the improved layout algorithm. Please disable improvedLayout for better performance.");
  196. return;
  197. }
  198. //console.timeEnd("clustering")
  199. //console.log(level,after)
  200. }
  201. // increase the size of the edges
  202. this.body.modules.kamadaKawai.setOptions({springLength: Math.max(150, 2 * startLength)})
  203. }
  204. // position the system for these nodes and edges
  205. this.body.modules.kamadaKawai.solve(this.body.nodeIndices, this.body.edgeIndices, true);
  206. // shift to center point
  207. this._shiftToCenter();
  208. // perturb the nodes a little bit to force the physics to kick in
  209. let offset = 70;
  210. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  211. this.body.nodes[this.body.nodeIndices[i]].x += (0.5 - this.seededRandom())*offset;
  212. this.body.nodes[this.body.nodeIndices[i]].y += (0.5 - this.seededRandom())*offset;
  213. }
  214. // uncluster all clusters
  215. this._declusterAll();
  216. // reposition all bezier nodes.
  217. this.body.emitter.emit("_repositionBezierNodes");
  218. }
  219. }
  220. }
  221. /**
  222. * Move all the nodes towards to the center so gravitational pull wil not move the nodes away from view
  223. * @private
  224. */
  225. _shiftToCenter() {
  226. let range = NetworkUtil.getRangeCore(this.body.nodes, this.body.nodeIndices);
  227. let center = NetworkUtil.findCenter(range);
  228. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  229. this.body.nodes[this.body.nodeIndices[i]].x -= center.x;
  230. this.body.nodes[this.body.nodeIndices[i]].y -= center.y;
  231. }
  232. }
  233. _declusterAll() {
  234. let clustersPresent = true;
  235. while (clustersPresent === true) {
  236. clustersPresent = false;
  237. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  238. if (this.body.nodes[this.body.nodeIndices[i]].isCluster === true) {
  239. clustersPresent = true;
  240. this.body.modules.clustering.openCluster(this.body.nodeIndices[i], {}, false);
  241. }
  242. }
  243. if (clustersPresent === true) {
  244. this.body.emitter.emit('_dataChanged');
  245. }
  246. }
  247. }
  248. getSeed() {
  249. return this.initialRandomSeed;
  250. }
  251. /**
  252. * This is the main function to layout the nodes in a hierarchical way.
  253. * It checks if the node details are supplied correctly
  254. *
  255. * @private
  256. */
  257. setupHierarchicalLayout() {
  258. if (this.options.hierarchical.enabled === true && this.body.nodeIndices.length > 0) {
  259. // get the size of the largest hubs and check if the user has defined a level for a node.
  260. let node, nodeId;
  261. let definedLevel = false;
  262. let undefinedLevel = false;
  263. this.hierarchicalLevels = {};
  264. this.nodeSpacing = 100;
  265. for (nodeId in this.body.nodes) {
  266. if (this.body.nodes.hasOwnProperty(nodeId)) {
  267. node = this.body.nodes[nodeId];
  268. if (node.options.level !== undefined) {
  269. definedLevel = true;
  270. this.hierarchicalLevels[nodeId] = node.options.level;
  271. }
  272. else {
  273. undefinedLevel = true;
  274. }
  275. }
  276. }
  277. // if the user defined some levels but not all, alert and run without hierarchical layout
  278. if (undefinedLevel === true && definedLevel === true) {
  279. throw new Error('To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.');
  280. return;
  281. }
  282. else {
  283. // define levels if undefined by the users. Based on hubsize.
  284. if (undefinedLevel === true) {
  285. if (this.options.hierarchical.sortMethod === 'hubsize') {
  286. this._determineLevelsByHubsize();
  287. }
  288. else if (this.options.hierarchical.sortMethod === 'directed') {
  289. this._determineLevelsDirected();
  290. }
  291. else if (this.options.hierarchical.sortMethod === 'custom') {
  292. this._determineLevelsCustomCallback();
  293. }
  294. }
  295. // check the distribution of the nodes per level.
  296. let distribution = this._getDistribution();
  297. // get the parent children relations.
  298. this._generateMap();
  299. // place the nodes on the canvas.
  300. this._placeNodesByHierarchy(distribution);
  301. // Todo: condense the whitespace.
  302. this._condenseHierarchy(distribution);
  303. // shift to center so gravity does not have to do much
  304. this._shiftToCenter();
  305. }
  306. }
  307. }
  308. /**
  309. * @private
  310. */
  311. _condenseHierarchy(distribution) {
  312. //console.log(this.distributionOrdering);
  313. //let iterations = 10;
  314. //for (let i = 0; i < iterations; i++) {
  315. //}
  316. }
  317. _removeWhiteSpace(distribution) {
  318. }
  319. /**
  320. * This function places the nodes on the canvas based on the hierarchial distribution.
  321. *
  322. * @param {Object} distribution | obtained by the function this._getDistribution()
  323. * @private
  324. */
  325. _placeNodesByHierarchy(distribution) {
  326. this.positionedNodes = {};
  327. // start placing all the level 0 nodes first. Then recursively position their branches.
  328. for (let level in distribution) {
  329. if (distribution.hasOwnProperty(level)) {
  330. // sort nodes in level by position:
  331. let nodeArray = Object.keys(distribution[level]);
  332. nodeArray = this._indexArrayToNodes(nodeArray);
  333. this._sortNodeArray(nodeArray);
  334. for (let i = 0; i < nodeArray.length; i++) {
  335. let node = nodeArray[i];
  336. if (this.positionedNodes[node.id] === undefined) {
  337. this._setPositionForHierarchy(node, this.nodeSpacing * i, level);
  338. this.positionedNodes[node.id] = true;
  339. this._placeBranchNodes(node.id, level);
  340. }
  341. }
  342. }
  343. }
  344. }
  345. /**
  346. * Receives an array with node indices and returns an array with the actual node references. Used for sorting based on
  347. * node properties.
  348. * @param idArray
  349. */
  350. _indexArrayToNodes(idArray) {
  351. let array = [];
  352. for (let i = 0; i < idArray.length; i++) {
  353. array.push(this.body.nodes[idArray[i]])
  354. }
  355. return array;
  356. }
  357. /**
  358. * This function get the distribution of levels based on hubsize
  359. *
  360. * @returns {Object}
  361. * @private
  362. */
  363. _getDistribution() {
  364. let distribution = {};
  365. let nodeId, node;
  366. // 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.
  367. // the fix of X is removed after the x value has been set.
  368. for (nodeId in this.body.nodes) {
  369. if (this.body.nodes.hasOwnProperty(nodeId)) {
  370. node = this.body.nodes[nodeId];
  371. let level = this.hierarchicalLevels[nodeId] === undefined ? 0 : this.hierarchicalLevels[nodeId];
  372. if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') {
  373. node.y = this.options.hierarchical.levelSeparation * level;
  374. node.options.fixed.y = true;
  375. }
  376. else {
  377. node.x = this.options.hierarchical.levelSeparation * level;
  378. node.options.fixed.x = true;
  379. }
  380. if (distribution[level] === undefined) {
  381. distribution[level] = {};
  382. }
  383. distribution[level][nodeId] = node;
  384. }
  385. }
  386. return distribution;
  387. }
  388. /**
  389. * Get the hubsize from all remaining unlevelled nodes.
  390. *
  391. * @returns {number}
  392. * @private
  393. */
  394. _getHubSize() {
  395. let hubSize = 0;
  396. for (let nodeId in this.body.nodes) {
  397. if (this.body.nodes.hasOwnProperty(nodeId)) {
  398. let node = this.body.nodes[nodeId];
  399. if (this.hierarchicalLevels[nodeId] === undefined) {
  400. hubSize = node.edges.length < hubSize ? hubSize : node.edges.length;
  401. }
  402. }
  403. }
  404. return hubSize;
  405. }
  406. /**
  407. * this function allocates nodes in levels based on the recursive branching from the largest hubs.
  408. *
  409. * @param hubsize
  410. * @private
  411. */
  412. _determineLevelsByHubsize() {
  413. let hubSize = 1;
  414. let levelDownstream = (nodeA, nodeB) => {
  415. if (this.hierarchicalLevels[nodeB.id] === undefined) {
  416. // set initial level
  417. if (this.hierarchicalLevels[nodeA.id] === undefined) {
  418. this.hierarchicalLevels[nodeA.id] = 0;
  419. }
  420. // set level
  421. this.hierarchicalLevels[nodeB.id] = this.hierarchicalLevels[nodeA.id] + 1;
  422. }
  423. };
  424. while (hubSize > 0) {
  425. // determine hubs
  426. hubSize = this._getHubSize();
  427. if (hubSize === 0)
  428. break;
  429. for (let nodeId in this.body.nodes) {
  430. if (this.body.nodes.hasOwnProperty(nodeId)) {
  431. let node = this.body.nodes[nodeId];
  432. if (node.edges.length === hubSize) {
  433. this._crawlNetwork(levelDownstream,nodeId);
  434. }
  435. }
  436. }
  437. }
  438. }
  439. /**
  440. * TODO: release feature
  441. * @private
  442. */
  443. _determineLevelsCustomCallback() {
  444. let minLevel = 100000;
  445. // TODO: this should come from options.
  446. let customCallback = function(nodeA, nodeB, edge) {
  447. };
  448. let levelByDirection = (nodeA, nodeB, edge) => {
  449. let levelA = this.hierarchicalLevels[nodeA.id];
  450. // set initial level
  451. if (levelA === undefined) {this.hierarchicalLevels[nodeA.id] = minLevel;}
  452. let diff = customCallback(
  453. NetworkUtil.cloneOptions(nodeA,'node'),
  454. NetworkUtil.cloneOptions(nodeB,'node'),
  455. NetworkUtil.cloneOptions(edge,'edge')
  456. );
  457. this.hierarchicalLevels[nodeB.id] = this.hierarchicalLevels[nodeA.id] + diff;
  458. };
  459. this._crawlNetwork(levelByDirection);
  460. this._setMinLevelToZero();
  461. }
  462. /**
  463. * this function allocates nodes in levels based on the direction of the edges
  464. *
  465. * @param hubsize
  466. * @private
  467. */
  468. _determineLevelsDirected() {
  469. let minLevel = 10000;
  470. let levelByDirection = (nodeA, nodeB, edge) => {
  471. let levelA = this.hierarchicalLevels[nodeA.id];
  472. // set initial level
  473. if (levelA === undefined) {this.hierarchicalLevels[nodeA.id] = minLevel;}
  474. if (edge.toId == nodeB.id) {
  475. this.hierarchicalLevels[nodeB.id] = this.hierarchicalLevels[nodeA.id] + 1;
  476. }
  477. else {
  478. this.hierarchicalLevels[nodeB.id] = this.hierarchicalLevels[nodeA.id] - 1;
  479. }
  480. };
  481. this._crawlNetwork(levelByDirection);
  482. this._setMinLevelToZero();
  483. }
  484. /**
  485. * Small util method to set the minimum levels of the nodes to zero.
  486. * @private
  487. */
  488. _setMinLevelToZero() {
  489. let minLevel = 1e9;
  490. // get the minimum level
  491. for (let nodeId in this.body.nodes) {
  492. if (this.body.nodes.hasOwnProperty(nodeId)) {
  493. minLevel = Math.min(this.hierarchicalLevels[nodeId], minLevel);
  494. }
  495. }
  496. // subtract the minimum from the set so we have a range starting from 0
  497. for (let nodeId in this.body.nodes) {
  498. if (this.body.nodes.hasOwnProperty(nodeId)) {
  499. this.hierarchicalLevels[nodeId] -= minLevel;
  500. }
  501. }
  502. }
  503. /**
  504. * Update the bookkeeping of parent and child.
  505. * @param parentNodeId
  506. * @param childNodeId
  507. * @private
  508. */
  509. _generateMap() {
  510. let fillInRelations = (parentNode, childNode) => {
  511. if (this.hierarchicalLevels[childNode.id] > this.hierarchicalLevels[parentNode.id]) {
  512. let parentNodeId = parentNode.id;
  513. let childNodeId = childNode.id;
  514. if (this.hierarchicalParents[parentNodeId] === undefined) {
  515. this.hierarchicalParents[parentNodeId] = {children: [], amount: 0};
  516. }
  517. this.hierarchicalParents[parentNodeId].children.push(childNodeId);
  518. if (this.hierarchicalChildren[childNodeId] === undefined) {
  519. this.hierarchicalChildren[childNodeId] = {parents: [], amount: 0};
  520. }
  521. this.hierarchicalChildren[childNodeId].parents.push(parentNodeId);
  522. }
  523. };
  524. this._crawlNetwork(fillInRelations);
  525. }
  526. /**
  527. * Crawl over the entire network and use a callback on each node couple that is connected to eachother.
  528. * @param callback | will receive nodeA nodeB and the connecting edge. A and B are unique.
  529. * @param startingNodeId
  530. * @private
  531. */
  532. _crawlNetwork(callback = function() {}, startingNodeId) {
  533. let progress = {};
  534. let crawler = (node) => {
  535. if (progress[node.id] === undefined) {
  536. progress[node.id] = true;
  537. let childNode;
  538. for (let i = 0; i < node.edges.length; i++) {
  539. if (node.edges[i].toId === node.id) {childNode = node.edges[i].from;}
  540. else {childNode = node.edges[i].to;}
  541. if (node.id !== childNode.id) {
  542. callback(node, childNode, node.edges[i]);
  543. crawler(childNode);
  544. }
  545. }
  546. }
  547. };
  548. // we can crawl from a specific node or over all nodes.
  549. if (startingNodeId === undefined) {
  550. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  551. let node = this.body.nodes[this.body.nodeIndices[i]];
  552. crawler(node);
  553. }
  554. }
  555. else {
  556. let node = this.body.nodes[startingNodeId];
  557. if (node === undefined) {
  558. console.error("Node not found:", startingNodeId);
  559. return;
  560. }
  561. crawler(node);
  562. }
  563. }
  564. /**
  565. * This is a recursively called function to enumerate the branches from the largest hubs and place the nodes
  566. * on a X position that ensures there will be no overlap.
  567. *
  568. * @param parentId
  569. * @param parentLevel
  570. * @private
  571. */
  572. _placeBranchNodes(parentId, parentLevel) {
  573. // if this is not a parent, cancel the placing. This can happen with multiple parents to one child.
  574. if (this.hierarchicalParents[parentId] === undefined) {
  575. return;
  576. }
  577. // get a list of childNodes
  578. let childNodes = [];
  579. for (let i = 0; i < this.hierarchicalParents[parentId].children.length; i++) {
  580. childNodes.push(this.body.nodes[this.hierarchicalParents[parentId].children[i]]);
  581. }
  582. // use the positions to order the nodes.
  583. this._sortNodeArray(childNodes);
  584. // position the childNodes
  585. for (let i = 0; i < childNodes.length; i++) {
  586. let childNode = childNodes[i];
  587. let childNodeLevel = this.hierarchicalLevels[childNode.id];
  588. // check if the child node is below the parent node and if it has already been positioned.
  589. if (childNodeLevel > parentLevel && this.positionedNodes[childNode.id] === undefined) {
  590. // get the amount of space required for this node. If parent the width is based on the amount of children.
  591. let pos;
  592. // 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
  593. if (i === 0) {pos = this._getPositionForHierarchy(this.body.nodes[parentId]);}
  594. else {pos = this._getPositionForHierarchy(childNodes[i-1]) + this.nodeSpacing;}
  595. this._setPositionForHierarchy(childNode, pos, childNodeLevel);
  596. // if overlap has been detected, we shift the branch
  597. if (this.lastNodeOnLevel[childNodeLevel] !== undefined) {
  598. let previousPos = this._getPositionForHierarchy(this.body.nodes[this.lastNodeOnLevel[childNodeLevel]]);
  599. if (pos - previousPos < this.nodeSpacing) {
  600. let diff = (previousPos + this.nodeSpacing) - pos;
  601. let sharedParent = this._findCommonParent(this.lastNodeOnLevel[childNodeLevel], childNode.id);
  602. this._shiftBlock(sharedParent.withChild, diff);
  603. }
  604. }
  605. // store change in position.
  606. this.lastNodeOnLevel[childNodeLevel] = childNode.id;
  607. this.positionedNodes[childNode.id] = true;
  608. this._placeBranchNodes(childNode.id, childNodeLevel);
  609. }
  610. else {
  611. return
  612. }
  613. }
  614. // center the parent nodes.
  615. let minPos = 1e9;
  616. let maxPos = -1e9;
  617. for (let i = 0; i < childNodes.length; i++) {
  618. let childNodeId = childNodes[i].id;
  619. minPos = Math.min(minPos, this._getPositionForHierarchy(this.body.nodes[childNodeId]));
  620. maxPos = Math.max(maxPos, this._getPositionForHierarchy(this.body.nodes[childNodeId]));
  621. }
  622. this._setPositionForHierarchy(this.body.nodes[parentId], 0.5 * (minPos + maxPos), parentLevel);
  623. }
  624. /**
  625. * Shift a branch a certain distance
  626. * @param parentId
  627. * @param diff
  628. * @private
  629. */
  630. _shiftBlock(parentId, diff) {
  631. if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') {
  632. this.body.nodes[parentId].x += diff;
  633. }
  634. else {
  635. this.body.nodes[parentId].y += diff;
  636. }
  637. if (this.hierarchicalParents[parentId] !== undefined) {
  638. for (let i = 0; i < this.hierarchicalParents[parentId].children.length; i++) {
  639. this._shiftBlock(this.hierarchicalParents[parentId].children[i], diff);
  640. }
  641. }
  642. }
  643. /**
  644. * Find a common parent between branches.
  645. * @param childA
  646. * @param childB
  647. * @returns {{foundParent, withChild}}
  648. * @private
  649. */
  650. _findCommonParent(childA,childB) {
  651. let parents = {};
  652. let iterateParents = (parents,child) => {
  653. if (this.hierarchicalChildren[child] !== undefined) {
  654. for (let i = 0; i < this.hierarchicalChildren[child].parents.length; i++) {
  655. let parent = this.hierarchicalChildren[child].parents[i];
  656. parents[parent] = true;
  657. iterateParents(parents, parent)
  658. }
  659. }
  660. };
  661. let findParent = (parents, child) => {
  662. if (this.hierarchicalChildren[child] !== undefined) {
  663. for (let i = 0; i < this.hierarchicalChildren[child].parents.length; i++) {
  664. let parent = this.hierarchicalChildren[child].parents[i];
  665. if (parents[parent] !== undefined) {
  666. return {foundParent:parent, withChild:child};
  667. }
  668. let branch = findParent(parents, parent);
  669. if (branch.foundParent !== null) {
  670. return branch;
  671. }
  672. }
  673. }
  674. return {foundParent:null, withChild:child};
  675. };
  676. iterateParents(parents, childA);
  677. return findParent(parents, childB);
  678. }
  679. /**
  680. * Abstract the getting of the position so we won't have to repeat the check for direction all the time
  681. * @param node
  682. * @param position
  683. * @param level
  684. * @private
  685. */
  686. _setPositionForHierarchy(node, position, level) {
  687. if (this.distributionOrdering[level] === undefined) {
  688. this.distributionOrdering[level] = [];
  689. this.distributionOrderingPresence[level] = {};
  690. }
  691. if (this.distributionOrderingPresence[level][node.id] === undefined) {
  692. this.distributionOrdering[level].push(node);
  693. }
  694. this.distributionOrderingPresence[level][node.id] = true;
  695. if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') {
  696. node.x = position;
  697. }
  698. else {
  699. node.y = position;
  700. }
  701. }
  702. /**
  703. * Abstract the getting of the position of a node so we do not have to repeat the direction check all the time.
  704. * @param node
  705. * @returns {number|*}
  706. * @private
  707. */
  708. _getPositionForHierarchy(node) {
  709. if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') {
  710. return node.x;
  711. }
  712. else {
  713. return node.y;
  714. }
  715. }
  716. /**
  717. * Use the x or y value to sort the array, allowing users to specify order.
  718. * @param nodeArray
  719. * @private
  720. */
  721. _sortNodeArray(nodeArray) {
  722. if (nodeArray.length > 1) {
  723. if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') {
  724. nodeArray.sort(function (a, b) {
  725. return a.x - b.x;
  726. })
  727. }
  728. else {
  729. nodeArray.sort(function (a, b) {
  730. return a.y - b.y;
  731. })
  732. }
  733. }
  734. }
  735. }
  736. export default LayoutEngine;