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.

1731 lines
54 KiB

9 years ago
9 years ago
9 years ago
  1. 'use strict';
  2. /**
  3. * There's a mix-up with terms in the code. Following are the formal definitions:
  4. *
  5. * tree - a strict hierarchical network, i.e. every node has at most one parent
  6. * forest - a collection of trees. These distinct trees are thus not connected.
  7. *
  8. * So:
  9. * - in a network that is not a tree, there exist nodes with multiple parents.
  10. * - a network consisting of unconnected sub-networks, of which at least one
  11. * is not a tree, is not a forest.
  12. *
  13. * In the code, the definitions are:
  14. *
  15. * tree - any disconnected sub-network, strict hierarchical or not.
  16. * forest - a bunch of these sub-networks
  17. *
  18. * The difference between tree and not-tree is important in the code, notably within
  19. * to the block-shifting algorithm. The algorithm assumes formal trees and fails
  20. * for not-trees, often in a spectacular manner (search for 'exploding network' in the issues).
  21. *
  22. * In order to distinguish the definitions in the following code, the adjective 'formal' is
  23. * used. If 'formal' is absent, you must assume the non-formal definition.
  24. *
  25. * ----------------------------------------------------------------------------------
  26. * NOTES
  27. * =====
  28. *
  29. * A hierarchical layout is a different thing from a hierarchical network.
  30. * The layout is a way to arrange the nodes in the view; this can be done
  31. * on non-hierarchical networks as well. The converse is also possible.
  32. */
  33. let util = require('../../util');
  34. var NetworkUtil = require('../NetworkUtil').default;
  35. /**
  36. * Container for derived data on current network, relating to hierarchy.
  37. *
  38. * Local, private class.
  39. */
  40. class HierarchicalStatus {
  41. constructor() {
  42. this.childrenReference = {}; // child id's per node id
  43. this.parentReference = {}; // parent id's per node id
  44. this.trees = {}; // tree id per node id; i.e. to which tree does given node id belong
  45. this.distributionOrdering = {}; // The nodes per level, in the display order
  46. this.levels = {}; // hierarchy level per node id
  47. this.distributionIndex = {}; // The position of the node in the level sorting order, per node id.
  48. this.isTree = false; // True if current network is a formal tree
  49. this.treeIndex = -1; // Highest tree id in current network.
  50. }
  51. /**
  52. * Add the relation between given nodes to the current state.
  53. */
  54. addRelation(parentNodeId, childNodeId) {
  55. if (this.childrenReference[parentNodeId] === undefined) {
  56. this.childrenReference[parentNodeId] = [];
  57. }
  58. this.childrenReference[parentNodeId].push(childNodeId);
  59. if (this.parentReference[childNodeId] === undefined) {
  60. this.parentReference[childNodeId] = [];
  61. }
  62. this.parentReference[childNodeId].push(parentNodeId);
  63. }
  64. /**
  65. * Check if the current state is for a formal tree or formal forest.
  66. *
  67. * This is the case if every node has at most one parent.
  68. *
  69. * Pre: parentReference init'ed properly for current network
  70. */
  71. checkIfTree() {
  72. for (let i in this.parentReference) {
  73. if (this.parentReference[i].length > 1) {
  74. this.isTree = false;
  75. return;
  76. }
  77. }
  78. this.isTree = true;
  79. }
  80. /**
  81. * Return the number of separate trees in the current network.
  82. */
  83. numTrees() {
  84. return (this.treeIndex + 1); // This assumes the indexes are assigned consecitively
  85. }
  86. /**
  87. * Assign a tree id to a node
  88. */
  89. setTreeIndex(node, treeId) {
  90. if (this.trees[node.id] === undefined) {
  91. this.trees[node.id] = treeId;
  92. this.treeIndex = Math.max(treeId, this.treeIndex);
  93. }
  94. }
  95. /**
  96. * Ensure level for given id is defined.
  97. *
  98. * Sets level to zero for given node id if not already present
  99. */
  100. ensureLevel(nodeId) {
  101. if (this.levels[nodeId] === undefined) {
  102. this.levels[nodeId] = 0;
  103. }
  104. }
  105. /**
  106. * get the maximum level of a branch.
  107. *
  108. * TODO: Never entered; find a test case to test this!
  109. */
  110. getMaxLevel(nodeId) {
  111. let accumulator = {};
  112. let _getMaxLevel = (nodeId) => {
  113. if (accumulator[nodeId] !== undefined) {
  114. return accumulator[nodeId];
  115. }
  116. let level = this.levels[nodeId];
  117. if (this.childrenReference[nodeId]) {
  118. let children = this.childrenReference[nodeId];
  119. if (children.length > 0) {
  120. for (let i = 0; i < children.length; i++) {
  121. level = Math.max(level,_getMaxLevel(children[i]));
  122. }
  123. }
  124. }
  125. accumulator[nodeId] = level;
  126. return level;
  127. };
  128. return _getMaxLevel(nodeId);
  129. }
  130. levelDownstream(nodeA, nodeB) {
  131. if (this.levels[nodeB.id] === undefined) {
  132. // set initial level
  133. if (this.levels[nodeA.id] === undefined) {
  134. this.levels[nodeA.id] = 0;
  135. }
  136. // set level
  137. this.levels[nodeB.id] = this.levels[nodeA.id] + 1;
  138. }
  139. }
  140. /**
  141. * Small util method to set the minimum levels of the nodes to zero.
  142. */
  143. setMinLevelToZero(nodes) {
  144. let minLevel = 1e9;
  145. // get the minimum level
  146. for (let nodeId in nodes) {
  147. if (nodes.hasOwnProperty(nodeId)) {
  148. if (this.levels[nodeId] !== undefined) {
  149. minLevel = Math.min(this.levels[nodeId], minLevel);
  150. }
  151. }
  152. }
  153. // subtract the minimum from the set so we have a range starting from 0
  154. for (let nodeId in nodes) {
  155. if (nodes.hasOwnProperty(nodeId)) {
  156. if (this.levels[nodeId] !== undefined) {
  157. this.levels[nodeId] -= minLevel;
  158. }
  159. }
  160. }
  161. }
  162. /**
  163. * Get the min and max xy-coordinates of a given tree
  164. */
  165. getTreeSize(nodes, index) {
  166. let min_x = 1e9;
  167. let max_x = -1e9;
  168. let min_y = 1e9;
  169. let max_y = -1e9;
  170. for (let nodeId in this.trees) {
  171. if (this.trees.hasOwnProperty(nodeId)) {
  172. if (this.trees[nodeId] === index) {
  173. let node = nodes[nodeId];
  174. min_x = Math.min(node.x, min_x);
  175. max_x = Math.max(node.x, max_x);
  176. min_y = Math.min(node.y, min_y);
  177. max_y = Math.max(node.y, max_y);
  178. }
  179. }
  180. }
  181. return {
  182. min_x: min_x,
  183. max_x: max_x,
  184. min_y: min_y,
  185. max_y: max_y
  186. };
  187. }
  188. /**
  189. * Check if two nodes have the same parent(s)
  190. *
  191. * @return true if the two nodes have a same ancestor node, false otherwise
  192. */
  193. hasSameParent(node1, node2) {
  194. let parents1 = this.parentReference[node1.id];
  195. let parents2 = this.parentReference[node2.id];
  196. if (parents1 === undefined || parents2 === undefined) {
  197. return false;
  198. }
  199. for (let i = 0; i < parents1.length; i++) {
  200. for (let j = 0; j < parents2.length; j++) {
  201. if (parents1[i] == parents2[j]) {
  202. return true;
  203. }
  204. }
  205. }
  206. return false;
  207. }
  208. /**
  209. * Check if two nodes are in the same tree.
  210. *
  211. * @return true if this is so, false otherwise
  212. */
  213. inSameSubNetwork(node1, node2) {
  214. return (this.trees[node1.id] === this.trees[node2.id]);
  215. }
  216. /**
  217. * Get a list of the distinct levels in the current network
  218. */
  219. getLevels() {
  220. return Object.keys(this.distributionOrdering);
  221. }
  222. /**
  223. * Add a node to the ordering per level
  224. */
  225. addToOrdering(node, level) {
  226. if (this.distributionOrdering[level] === undefined) {
  227. this.distributionOrdering[level] = [];
  228. }
  229. var isPresent = false;
  230. var curLevel = this.distributionOrdering[level];
  231. for (var n in curLevel) {
  232. //if (curLevel[n].id === node.id) {
  233. if (curLevel[n] === node) {
  234. isPresent = true;
  235. break;
  236. }
  237. }
  238. if (!isPresent) {
  239. this.distributionOrdering[level].push(node);
  240. this.distributionIndex[node.id] = this.distributionOrdering[level].length - 1;
  241. }
  242. }
  243. }
  244. class LayoutEngine {
  245. constructor(body) {
  246. this.body = body;
  247. this.initialRandomSeed = Math.round(Math.random() * 1000000);
  248. this.randomSeed = this.initialRandomSeed;
  249. this.setPhysics = false;
  250. this.options = {};
  251. this.optionsBackup = {physics:{}};
  252. this.defaultOptions = {
  253. randomSeed: undefined,
  254. improvedLayout: true,
  255. hierarchical: {
  256. enabled:false,
  257. levelSeparation: 150,
  258. nodeSpacing: 100,
  259. treeSpacing: 200,
  260. blockShifting: true,
  261. edgeMinimization: true,
  262. parentCentralization: true,
  263. direction: 'UD', // UD, DU, LR, RL
  264. sortMethod: 'hubsize' // hubsize, directed
  265. }
  266. };
  267. util.extend(this.options, this.defaultOptions);
  268. this.bindEventListeners();
  269. }
  270. bindEventListeners() {
  271. this.body.emitter.on('_dataChanged', () => {
  272. this.setupHierarchicalLayout();
  273. });
  274. this.body.emitter.on('_dataLoaded', () => {
  275. this.layoutNetwork();
  276. });
  277. this.body.emitter.on('_resetHierarchicalLayout', () => {
  278. this.setupHierarchicalLayout();
  279. });
  280. this.body.emitter.on('_adjustEdgesForHierarchicalLayout', () => {
  281. if (this.options.hierarchical.enabled !== true) {
  282. return;
  283. }
  284. // get the type of static smooth curve in case it is required
  285. let type = this.getStaticType();
  286. // force all edges into static smooth curves.
  287. this.body.emitter.emit('_forceDisableDynamicCurves', type, false);
  288. });
  289. }
  290. setOptions(options, allOptions) {
  291. if (options !== undefined) {
  292. let hierarchical = this.options.hierarchical;
  293. let prevHierarchicalState = hierarchical.enabled;
  294. util.selectiveDeepExtend(["randomSeed", "improvedLayout"],this.options, options);
  295. util.mergeOptions(this.options, options, 'hierarchical');
  296. if (options.randomSeed !== undefined) {this.initialRandomSeed = options.randomSeed;}
  297. if (hierarchical.enabled === true) {
  298. if (prevHierarchicalState === true) {
  299. // refresh the overridden options for nodes and edges.
  300. this.body.emitter.emit('refresh', true);
  301. }
  302. // make sure the level separation is the right way up
  303. if (hierarchical.direction === 'RL' || hierarchical.direction === 'DU') {
  304. if (hierarchical.levelSeparation > 0) {
  305. hierarchical.levelSeparation *= -1;
  306. }
  307. }
  308. else {
  309. if (hierarchical.levelSeparation < 0) {
  310. hierarchical.levelSeparation *= -1;
  311. }
  312. }
  313. this.body.emitter.emit('_resetHierarchicalLayout');
  314. // because the hierarchical system needs it's own physics and smooth curve settings,
  315. // we adapt the other options if needed.
  316. return this.adaptAllOptionsForHierarchicalLayout(allOptions);
  317. }
  318. else {
  319. if (prevHierarchicalState === true) {
  320. // refresh the overridden options for nodes and edges.
  321. this.body.emitter.emit('refresh');
  322. return util.deepExtend(allOptions,this.optionsBackup);
  323. }
  324. }
  325. }
  326. return allOptions;
  327. }
  328. adaptAllOptionsForHierarchicalLayout(allOptions) {
  329. if (this.options.hierarchical.enabled === true) {
  330. let backupPhysics = this.optionsBackup.physics;
  331. // set the physics
  332. if (allOptions.physics === undefined || allOptions.physics === true) {
  333. allOptions.physics = {
  334. enabled: backupPhysics.enabled === undefined ? true : backupPhysics.enabled,
  335. solver :'hierarchicalRepulsion'
  336. };
  337. backupPhysics.enabled = backupPhysics.enabled === undefined ? true : backupPhysics.enabled;
  338. backupPhysics.solver = backupPhysics.solver || 'barnesHut';
  339. }
  340. else if (typeof allOptions.physics === 'object') {
  341. backupPhysics.enabled = allOptions.physics.enabled === undefined ? true : allOptions.physics.enabled;
  342. backupPhysics.solver = allOptions.physics.solver || 'barnesHut';
  343. allOptions.physics.solver = 'hierarchicalRepulsion';
  344. }
  345. else if (allOptions.physics !== false) {
  346. backupPhysics.solver ='barnesHut';
  347. allOptions.physics = {solver:'hierarchicalRepulsion'};
  348. }
  349. // get the type of static smooth curve in case it is required
  350. let type = this.getStaticType();
  351. // disable smooth curves if nothing is defined. If smooth curves have been turned on,
  352. // turn them into static smooth curves.
  353. if (allOptions.edges === undefined) {
  354. this.optionsBackup.edges = {smooth:{enabled:true, type:'dynamic'}};
  355. allOptions.edges = {smooth: false};
  356. }
  357. else if (allOptions.edges.smooth === undefined) {
  358. this.optionsBackup.edges = {smooth:{enabled:true, type:'dynamic'}};
  359. allOptions.edges.smooth = false;
  360. }
  361. else {
  362. if (typeof allOptions.edges.smooth === 'boolean') {
  363. this.optionsBackup.edges = {smooth:allOptions.edges.smooth};
  364. allOptions.edges.smooth = {enabled: allOptions.edges.smooth, type:type}
  365. }
  366. else {
  367. let smooth = allOptions.edges.smooth;
  368. // allow custom types except for dynamic
  369. if (smooth.type !== undefined && smooth.type !== 'dynamic') {
  370. type = smooth.type;
  371. }
  372. // TODO: this is options merging; see if the standard routines can be used here.
  373. this.optionsBackup.edges = {
  374. smooth : smooth.enabled === undefined ? true : smooth.enabled,
  375. type : smooth.type === undefined ? 'dynamic': smooth.type,
  376. roundness : smooth.roundness === undefined ? 0.5 : smooth.roundness,
  377. forceDirection: smooth.forceDirection === undefined ? false : smooth.forceDirection
  378. };
  379. // NOTE: Copying an object to self; this is basically setting defaults for undefined variables
  380. allOptions.edges.smooth = {
  381. enabled : smooth.enabled === undefined ? true : smooth.enabled,
  382. type : type,
  383. roundness : smooth.roundness === undefined ? 0.5 : smooth.roundness,
  384. forceDirection: smooth.forceDirection === undefined ? false: smooth.forceDirection
  385. }
  386. }
  387. }
  388. // Force all edges into static smooth curves.
  389. // Only applies to edges that do not use the global options for smooth.
  390. this.body.emitter.emit('_forceDisableDynamicCurves', type);
  391. }
  392. return allOptions;
  393. }
  394. seededRandom() {
  395. let x = Math.sin(this.randomSeed++) * 10000;
  396. return x - Math.floor(x);
  397. }
  398. positionInitially(nodesArray) {
  399. if (this.options.hierarchical.enabled !== true) {
  400. this.randomSeed = this.initialRandomSeed;
  401. let radius = nodesArray.length + 50;
  402. for (let i = 0; i < nodesArray.length; i++) {
  403. let node = nodesArray[i];
  404. let angle = 2 * Math.PI * this.seededRandom();
  405. if (node.x === undefined) {
  406. node.x = radius * Math.cos(angle);
  407. }
  408. if (node.y === undefined) {
  409. node.y = radius * Math.sin(angle);
  410. }
  411. }
  412. }
  413. }
  414. /**
  415. * Use Kamada Kawai to position nodes. This is quite a heavy algorithm so if there are a lot of nodes we
  416. * cluster them first to reduce the amount.
  417. */
  418. layoutNetwork() {
  419. if (this.options.hierarchical.enabled !== true && this.options.improvedLayout === true) {
  420. let indices = this.body.nodeIndices;
  421. // first check if we should Kamada Kawai to layout. The threshold is if less than half of the visible
  422. // nodes have predefined positions we use this.
  423. let positionDefined = 0;
  424. for (let i = 0; i < indices.length; i++) {
  425. let node = this.body.nodes[indices[i]];
  426. if (node.predefinedPosition === true) {
  427. positionDefined += 1;
  428. }
  429. }
  430. // if less than half of the nodes have a predefined position we continue
  431. if (positionDefined < 0.5 * indices.length) {
  432. let MAX_LEVELS = 10;
  433. let level = 0;
  434. let clusterThreshold = 150;
  435. // Performance enhancement, during clustering edges need only be simple straight lines.
  436. // These options don't propagate outside the clustering phase.
  437. let clusterOptions = {
  438. clusterEdgeProperties:{
  439. smooth: {
  440. enabled: false
  441. }
  442. }
  443. };
  444. // if there are a lot of nodes, we cluster before we run the algorithm.
  445. // NOTE: this part fails to find clusters for large scale-free networks, which should
  446. // be easily clusterable.
  447. // TODO: examine why this is so
  448. if (indices.length > clusterThreshold) {
  449. let startLength = indices.length;
  450. while (indices.length > clusterThreshold && level <= MAX_LEVELS) {
  451. //console.time("clustering")
  452. level += 1;
  453. let before = indices.length;
  454. // if there are many nodes we do a hubsize cluster
  455. if (level % 3 === 0) {
  456. this.body.modules.clustering.clusterBridges(clusterOptions);
  457. }
  458. else {
  459. this.body.modules.clustering.clusterOutliers(clusterOptions);
  460. }
  461. let after = indices.length;
  462. if (before == after && level % 3 !== 0) {
  463. this._declusterAll();
  464. this.body.emitter.emit("_layoutFailed");
  465. console.info("This network could not be positioned by this version of the improved layout algorithm."
  466. + " Please disable improvedLayout for better performance.");
  467. return;
  468. }
  469. //console.timeEnd("clustering")
  470. //console.log(before,level,after);
  471. }
  472. // increase the size of the edges
  473. this.body.modules.kamadaKawai.setOptions({springLength: Math.max(150, 2 * startLength)})
  474. }
  475. if (level > MAX_LEVELS){
  476. console.info("The clustering didn't succeed within the amount of interations allowed,"
  477. + " progressing with partial result.");
  478. }
  479. // position the system for these nodes and edges
  480. this.body.modules.kamadaKawai.solve(indices, this.body.edgeIndices, true);
  481. // shift to center point
  482. this._shiftToCenter();
  483. // perturb the nodes a little bit to force the physics to kick in
  484. let offset = 70;
  485. for (let i = 0; i < indices.length; i++) {
  486. // Only perturb the nodes that aren't fixed
  487. let node = this.body.nodes[indices[i]];
  488. if (node.predefinedPosition === false) {
  489. node.x += (0.5 - this.seededRandom())*offset;
  490. node.y += (0.5 - this.seededRandom())*offset;
  491. }
  492. }
  493. // uncluster all clusters
  494. this._declusterAll();
  495. // reposition all bezier nodes.
  496. this.body.emitter.emit("_repositionBezierNodes");
  497. }
  498. }
  499. }
  500. /**
  501. * Move all the nodes towards to the center so gravitational pull wil not move the nodes away from view
  502. * @private
  503. */
  504. _shiftToCenter() {
  505. let range = NetworkUtil.getRangeCore(this.body.nodes, this.body.nodeIndices);
  506. let center = NetworkUtil.findCenter(range);
  507. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  508. let node = this.body.nodes[this.body.nodeIndices[i]];
  509. node.x -= center.x;
  510. node.y -= center.y;
  511. }
  512. }
  513. _declusterAll() {
  514. let clustersPresent = true;
  515. while (clustersPresent === true) {
  516. clustersPresent = false;
  517. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  518. if (this.body.nodes[this.body.nodeIndices[i]].isCluster === true) {
  519. clustersPresent = true;
  520. this.body.modules.clustering.openCluster(this.body.nodeIndices[i], {}, false);
  521. }
  522. }
  523. if (clustersPresent === true) {
  524. this.body.emitter.emit('_dataChanged');
  525. }
  526. }
  527. }
  528. getSeed() {
  529. return this.initialRandomSeed;
  530. }
  531. /**
  532. * This is the main function to layout the nodes in a hierarchical way.
  533. * It checks if the node details are supplied correctly
  534. *
  535. * @private
  536. */
  537. setupHierarchicalLayout() {
  538. if (this.options.hierarchical.enabled === true && this.body.nodeIndices.length > 0) {
  539. // get the size of the largest hubs and check if the user has defined a level for a node.
  540. let node, nodeId;
  541. let definedLevel = false;
  542. let undefinedLevel = false;
  543. this.lastNodeOnLevel = {};
  544. this.hierarchical = new HierarchicalStatus();
  545. for (nodeId in this.body.nodes) {
  546. if (this.body.nodes.hasOwnProperty(nodeId)) {
  547. node = this.body.nodes[nodeId];
  548. if (node.options.level !== undefined) {
  549. definedLevel = true;
  550. this.hierarchical.levels[nodeId] = node.options.level;
  551. }
  552. else {
  553. undefinedLevel = true;
  554. }
  555. }
  556. }
  557. // if the user defined some levels but not all, alert and run without hierarchical layout
  558. if (undefinedLevel === true && definedLevel === true) {
  559. throw new Error('To use the hierarchical layout, nodes require either no predefined levels'
  560. + ' or levels have to be defined for all nodes.');
  561. }
  562. else {
  563. // define levels if undefined by the users. Based on hubsize.
  564. if (undefinedLevel === true) {
  565. let sortMethod = this.options.hierarchical.sortMethod;
  566. if (sortMethod === 'hubsize') {
  567. this._determineLevelsByHubsize();
  568. }
  569. else if (sortMethod === 'directed') {
  570. this._determineLevelsDirected();
  571. }
  572. else if (sortMethod === 'custom') {
  573. this._determineLevelsCustomCallback();
  574. }
  575. }
  576. // fallback for cases where there are nodes but no edges
  577. for (let nodeId in this.body.nodes) {
  578. if (this.body.nodes.hasOwnProperty(nodeId)) {
  579. this.hierarchical.ensureLevel(nodeId);
  580. }
  581. }
  582. // check the distribution of the nodes per level.
  583. let distribution = this._getDistribution();
  584. // get the parent children relations.
  585. this._generateMap();
  586. // place the nodes on the canvas.
  587. this._placeNodesByHierarchy(distribution);
  588. // condense the whitespace.
  589. this._condenseHierarchy();
  590. // shift to center so gravity does not have to do much
  591. this._shiftToCenter();
  592. }
  593. }
  594. }
  595. /**
  596. * @private
  597. */
  598. _condenseHierarchy() {
  599. // Global var in this scope to define when the movement has stopped.
  600. let stillShifting = false;
  601. let branches = {};
  602. // first we have some methods to help shifting trees around.
  603. // the main method to shift the trees
  604. let shiftTrees = () => {
  605. let treeSizes = getTreeSizes();
  606. let shiftBy = 0;
  607. for (let i = 0; i < treeSizes.length - 1; i++) {
  608. let diff = treeSizes[i].max - treeSizes[i+1].min;
  609. shiftBy += diff + this.options.hierarchical.treeSpacing;
  610. shiftTree(i + 1, shiftBy);
  611. }
  612. };
  613. // shift a single tree by an offset
  614. let shiftTree = (index, offset) => {
  615. let trees = this.hierarchical.trees;
  616. for (let nodeId in trees) {
  617. if (trees.hasOwnProperty(nodeId)) {
  618. if (trees[nodeId] === index) {
  619. let node = this.body.nodes[nodeId];
  620. let pos = this._getPositionForHierarchy(node);
  621. this._setPositionForHierarchy(node, pos + offset, undefined, true);
  622. }
  623. }
  624. }
  625. };
  626. // get the width of a tree
  627. let getTreeSize = (index) => {
  628. let res = this.hierarchical.getTreeSize(this.body.nodes, index);
  629. if (this._isVertical()) {
  630. return {min: res.min_x, max: res.max_x};
  631. } else {
  632. return {min: res.min_y, max: res.max_y};
  633. }
  634. };
  635. // get the width of all trees
  636. let getTreeSizes = () => {
  637. let treeWidths = [];
  638. for (let i = 0; i <= this.hierarchical.numTrees(); i++) {
  639. treeWidths.push(getTreeSize(i));
  640. }
  641. return treeWidths;
  642. };
  643. // get a map of all nodes in this branch
  644. let getBranchNodes = (source, map) => {
  645. if (map[source.id]) {
  646. return;
  647. }
  648. map[source.id] = true;
  649. if (this.hierarchical.childrenReference[source.id]) {
  650. let children = this.hierarchical.childrenReference[source.id];
  651. if (children.length > 0) {
  652. for (let i = 0; i < children.length; i++) {
  653. getBranchNodes(this.body.nodes[children[i]], map);
  654. }
  655. }
  656. }
  657. };
  658. // get a min max width as well as the maximum movement space it has on either sides
  659. // we use min max terminology because width and height can interchange depending on the direction of the layout
  660. let getBranchBoundary = (branchMap, maxLevel = 1e9) => {
  661. let minSpace = 1e9;
  662. let maxSpace = 1e9;
  663. let min = 1e9;
  664. let max = -1e9;
  665. for (let branchNode in branchMap) {
  666. if (branchMap.hasOwnProperty(branchNode)) {
  667. let node = this.body.nodes[branchNode];
  668. let level = this.hierarchical.levels[node.id];
  669. let position = this._getPositionForHierarchy(node);
  670. // get the space around the node.
  671. let [minSpaceNode, maxSpaceNode] = this._getSpaceAroundNode(node,branchMap);
  672. minSpace = Math.min(minSpaceNode, minSpace);
  673. maxSpace = Math.min(maxSpaceNode, maxSpace);
  674. // the width is only relevant for the levels two nodes have in common. This is why we filter on this.
  675. if (level <= maxLevel) {
  676. min = Math.min(position, min);
  677. max = Math.max(position, max);
  678. }
  679. }
  680. }
  681. return [min, max, minSpace, maxSpace];
  682. }
  683. // check what the maximum level is these nodes have in common.
  684. let getCollisionLevel = (node1, node2) => {
  685. let maxLevel1 = this.hierarchical.getMaxLevel(node1.id);
  686. let maxLevel2 = this.hierarchical.getMaxLevel(node2.id);
  687. return Math.min(maxLevel1, maxLevel2);
  688. };
  689. /**
  690. * Condense elements. These can be nodes or branches depending on the callback.
  691. */
  692. let shiftElementsCloser = (callback, levels, centerParents) => {
  693. let hier = this.hierarchical;
  694. for (let i = 0; i < levels.length; i++) {
  695. let level = levels[i];
  696. let levelNodes = hier.distributionOrdering[level];
  697. if (levelNodes.length > 1) {
  698. for (let j = 0; j < levelNodes.length - 1; j++) {
  699. let node1 = levelNodes[j];
  700. let node2 = levelNodes[j+1];
  701. // NOTE: logic maintained as it was; if nodes have same ancestor,
  702. // then of course they are in the same sub-network.
  703. if (hier.hasSameParent(node1, node2) && hier.inSameSubNetwork(node1, node2) ) {
  704. callback(node1, node2, centerParents);
  705. }
  706. }
  707. }
  708. }
  709. };
  710. // callback for shifting branches
  711. let branchShiftCallback = (node1, node2, centerParent = false) => {
  712. //window.CALLBACKS.push(() => {
  713. let pos1 = this._getPositionForHierarchy(node1);
  714. let pos2 = this._getPositionForHierarchy(node2);
  715. let diffAbs = Math.abs(pos2 - pos1);
  716. let nodeSpacing = this.options.hierarchical.nodeSpacing;
  717. //console.log("NOW CHECKING:", node1.id, node2.id, diffAbs);
  718. if (diffAbs > nodeSpacing) {
  719. let branchNodes1 = {};
  720. let branchNodes2 = {};
  721. getBranchNodes(node1, branchNodes1);
  722. getBranchNodes(node2, branchNodes2);
  723. // check the largest distance between the branches
  724. let maxLevel = getCollisionLevel(node1, node2);
  725. let branchNodeBoundary1 = getBranchBoundary(branchNodes1, maxLevel);
  726. let branchNodeBoundary2 = getBranchBoundary(branchNodes2, maxLevel);
  727. let max1 = branchNodeBoundary1[1];
  728. let min2 = branchNodeBoundary2[0];
  729. let minSpace2 = branchNodeBoundary2[2];
  730. //console.log(node1.id, getBranchBoundary(branchNodes1, maxLevel), node2.id,
  731. // getBranchBoundary(branchNodes2, maxLevel), maxLevel);
  732. let diffBranch = Math.abs(max1 - min2);
  733. if (diffBranch > nodeSpacing) {
  734. let offset = max1 - min2 + nodeSpacing;
  735. if (offset < -minSpace2 + nodeSpacing) {
  736. offset = -minSpace2 + nodeSpacing;
  737. //console.log("RESETTING OFFSET", max1 - min2 + this.options.hierarchical.nodeSpacing, -minSpace2, offset);
  738. }
  739. if (offset < 0) {
  740. //console.log("SHIFTING", node2.id, offset);
  741. this._shiftBlock(node2.id, offset);
  742. stillShifting = true;
  743. if (centerParent === true)
  744. this._centerParent(node2);
  745. }
  746. }
  747. }
  748. //this.body.emitter.emit("_redraw");})
  749. };
  750. let minimizeEdgeLength = (iterations, node) => {
  751. //window.CALLBACKS.push(() => {
  752. // console.log("ts",node.id);
  753. let nodeId = node.id;
  754. let allEdges = node.edges;
  755. let nodeLevel = this.hierarchical.levels[node.id];
  756. // gather constants
  757. let C2 = this.options.hierarchical.levelSeparation * this.options.hierarchical.levelSeparation;
  758. let referenceNodes = {};
  759. let aboveEdges = [];
  760. for (let i = 0; i < allEdges.length; i++) {
  761. let edge = allEdges[i];
  762. if (edge.toId != edge.fromId) {
  763. let otherNode = edge.toId == nodeId ? edge.from : edge.to;
  764. referenceNodes[allEdges[i].id] = otherNode;
  765. if (this.hierarchical.levels[otherNode.id] < nodeLevel) {
  766. aboveEdges.push(edge);
  767. }
  768. }
  769. }
  770. // differentiated sum of lengths based on only moving one node over one axis
  771. let getFx = (point, edges) => {
  772. let sum = 0;
  773. for (let i = 0; i < edges.length; i++) {
  774. if (referenceNodes[edges[i].id] !== undefined) {
  775. let a = this._getPositionForHierarchy(referenceNodes[edges[i].id]) - point;
  776. sum += a / Math.sqrt(a * a + C2);
  777. }
  778. }
  779. return sum;
  780. };
  781. // doubly differentiated sum of lengths based on only moving one node over one axis
  782. let getDFx = (point, edges) => {
  783. let sum = 0;
  784. for (let i = 0; i < edges.length; i++) {
  785. if (referenceNodes[edges[i].id] !== undefined) {
  786. let a = this._getPositionForHierarchy(referenceNodes[edges[i].id]) - point;
  787. sum -= (C2 * Math.pow(a * a + C2, -1.5));
  788. }
  789. }
  790. return sum;
  791. };
  792. let getGuess = (iterations, edges) => {
  793. let guess = this._getPositionForHierarchy(node);
  794. // Newton's method for optimization
  795. let guessMap = {};
  796. for (let i = 0; i < iterations; i++) {
  797. let fx = getFx(guess, edges);
  798. let dfx = getDFx(guess, edges);
  799. // we limit the movement to avoid instability.
  800. let limit = 40;
  801. let ratio = Math.max(-limit, Math.min(limit, Math.round(fx/dfx)));
  802. guess = guess - ratio;
  803. // reduce duplicates
  804. if (guessMap[guess] !== undefined) {
  805. break;
  806. }
  807. guessMap[guess] = i;
  808. }
  809. return guess;
  810. };
  811. let moveBranch = (guess) => {
  812. // position node if there is space
  813. let nodePosition = this._getPositionForHierarchy(node);
  814. // check movable area of the branch
  815. if (branches[node.id] === undefined) {
  816. let branchNodes = {};
  817. getBranchNodes(node, branchNodes);
  818. branches[node.id] = branchNodes;
  819. }
  820. let branchBoundary = getBranchBoundary(branches[node.id]);
  821. let minSpaceBranch = branchBoundary[2];
  822. let maxSpaceBranch = branchBoundary[3];
  823. let diff = guess - nodePosition;
  824. // check if we are allowed to move the node:
  825. let branchOffset = 0;
  826. if (diff > 0) {
  827. branchOffset = Math.min(diff, maxSpaceBranch - this.options.hierarchical.nodeSpacing);
  828. }
  829. else if (diff < 0) {
  830. branchOffset = -Math.min(-diff, minSpaceBranch - this.options.hierarchical.nodeSpacing);
  831. }
  832. if (branchOffset != 0) {
  833. //console.log("moving branch:",branchOffset, maxSpaceBranch, minSpaceBranch)
  834. this._shiftBlock(node.id, branchOffset);
  835. //this.body.emitter.emit("_redraw");
  836. stillShifting = true;
  837. }
  838. };
  839. let moveNode = (guess) => {
  840. let nodePosition = this._getPositionForHierarchy(node);
  841. // position node if there is space
  842. let [minSpace, maxSpace] = this._getSpaceAroundNode(node);
  843. let diff = guess - nodePosition;
  844. // check if we are allowed to move the node:
  845. let newPosition = nodePosition;
  846. if (diff > 0) {
  847. newPosition = Math.min(nodePosition + (maxSpace - this.options.hierarchical.nodeSpacing), guess);
  848. }
  849. else if (diff < 0) {
  850. newPosition = Math.max(nodePosition - (minSpace - this.options.hierarchical.nodeSpacing), guess);
  851. }
  852. if (newPosition !== nodePosition) {
  853. //console.log("moving Node:",diff, minSpace, maxSpace);
  854. this._setPositionForHierarchy(node, newPosition, undefined, true);
  855. //this.body.emitter.emit("_redraw");
  856. stillShifting = true;
  857. }
  858. };
  859. let guess = getGuess(iterations, aboveEdges);
  860. moveBranch(guess);
  861. guess = getGuess(iterations, allEdges);
  862. moveNode(guess);
  863. //})
  864. };
  865. // method to remove whitespace between branches. Because we do bottom up, we can center the parents.
  866. let minimizeEdgeLengthBottomUp = (iterations) => {
  867. let levels = this.hierarchical.getLevels();
  868. levels = levels.reverse();
  869. for (let i = 0; i < iterations; i++) {
  870. stillShifting = false;
  871. for (let j = 0; j < levels.length; j++) {
  872. let level = levels[j];
  873. let levelNodes = this.hierarchical.distributionOrdering[level];
  874. for (let k = 0; k < levelNodes.length; k++) {
  875. minimizeEdgeLength(1000, levelNodes[k]);
  876. }
  877. }
  878. if (stillShifting !== true) {
  879. //console.log("FINISHED minimizeEdgeLengthBottomUp IN " + i);
  880. break;
  881. }
  882. }
  883. };
  884. // method to remove whitespace between branches. Because we do bottom up, we can center the parents.
  885. let shiftBranchesCloserBottomUp = (iterations) => {
  886. let levels = this.hierarchical.getLevels();
  887. levels = levels.reverse();
  888. for (let i = 0; i < iterations; i++) {
  889. stillShifting = false;
  890. shiftElementsCloser(branchShiftCallback, levels, true);
  891. if (stillShifting !== true) {
  892. //console.log("FINISHED shiftBranchesCloserBottomUp IN " + (i+1));
  893. break;
  894. }
  895. }
  896. };
  897. // center all parents
  898. let centerAllParents = () => {
  899. for (let nodeId in this.body.nodes) {
  900. if (this.body.nodes.hasOwnProperty(nodeId))
  901. this._centerParent(this.body.nodes[nodeId]);
  902. }
  903. };
  904. // center all parents
  905. let centerAllParentsBottomUp = () => {
  906. let levels = this.hierarchical.getLevels();
  907. levels = levels.reverse();
  908. for (let i = 0; i < levels.length; i++) {
  909. let level = levels[i];
  910. let levelNodes = this.hierarchical.distributionOrdering[level];
  911. for (let j = 0; j < levelNodes.length; j++) {
  912. this._centerParent(levelNodes[j]);
  913. }
  914. }
  915. };
  916. // the actual work is done here.
  917. if (this.options.hierarchical.blockShifting === true) {
  918. shiftBranchesCloserBottomUp(5);
  919. centerAllParents();
  920. }
  921. // minimize edge length
  922. if (this.options.hierarchical.edgeMinimization === true) {
  923. minimizeEdgeLengthBottomUp(20);
  924. }
  925. if (this.options.hierarchical.parentCentralization === true) {
  926. centerAllParentsBottomUp()
  927. }
  928. shiftTrees();
  929. }
  930. /**
  931. * This gives the space around the node. IF a map is supplied, it will only check against nodes NOT in the map.
  932. * This is used to only get the distances to nodes outside of a branch.
  933. * @param node
  934. * @param map
  935. * @returns {*[]}
  936. * @private
  937. */
  938. _getSpaceAroundNode(node, map) {
  939. let useMap = true;
  940. if (map === undefined) {
  941. useMap = false;
  942. }
  943. let level = this.hierarchical.levels[node.id];
  944. if (level !== undefined) {
  945. let index = this.hierarchical.distributionIndex[node.id];
  946. let position = this._getPositionForHierarchy(node);
  947. let ordering = this.hierarchical.distributionOrdering[level];
  948. let minSpace = 1e9;
  949. let maxSpace = 1e9;
  950. if (index !== 0) {
  951. let prevNode = ordering[index - 1];
  952. if ((useMap === true && map[prevNode.id] === undefined) || useMap === false) {
  953. let prevPos = this._getPositionForHierarchy(prevNode);
  954. minSpace = position - prevPos;
  955. }
  956. }
  957. if (index != ordering.length - 1) {
  958. let nextNode = ordering[index + 1];
  959. if ((useMap === true && map[nextNode.id] === undefined) || useMap === false) {
  960. let nextPos = this._getPositionForHierarchy(nextNode);
  961. maxSpace = Math.min(maxSpace, nextPos - position);
  962. }
  963. }
  964. return [minSpace, maxSpace];
  965. }
  966. else {
  967. return [0, 0];
  968. }
  969. }
  970. /**
  971. * We use this method to center a parent node and check if it does not cross other nodes when it does.
  972. * @param node
  973. * @private
  974. */
  975. _centerParent(node) {
  976. if (this.hierarchical.parentReference[node.id]) {
  977. let parents = this.hierarchical.parentReference[node.id];
  978. for (var i = 0; i < parents.length; i++) {
  979. let parentId = parents[i];
  980. let parentNode = this.body.nodes[parentId];
  981. let children = this.hierarchical.childrenReference[parentId];
  982. if (children !== undefined) {
  983. // get the range of the children
  984. let newPosition = this._getCenterPosition(children);
  985. let position = this._getPositionForHierarchy(parentNode);
  986. let [minSpace, maxSpace] = this._getSpaceAroundNode(parentNode);
  987. let diff = position - newPosition;
  988. if ((diff < 0 && Math.abs(diff) < maxSpace - this.options.hierarchical.nodeSpacing) ||
  989. (diff > 0 && Math.abs(diff) < minSpace - this.options.hierarchical.nodeSpacing)) {
  990. this._setPositionForHierarchy(parentNode, newPosition, undefined, true);
  991. }
  992. }
  993. }
  994. }
  995. }
  996. /**
  997. * This function places the nodes on the canvas based on the hierarchial distribution.
  998. *
  999. * @param {Object} distribution | obtained by the function this._getDistribution()
  1000. * @private
  1001. */
  1002. _placeNodesByHierarchy(distribution) {
  1003. this.positionedNodes = {};
  1004. // start placing all the level 0 nodes first. Then recursively position their branches.
  1005. for (let level in distribution) {
  1006. if (distribution.hasOwnProperty(level)) {
  1007. // sort nodes in level by position:
  1008. let nodeArray = Object.keys(distribution[level]);
  1009. nodeArray = this._indexArrayToNodes(nodeArray);
  1010. this._sortNodeArray(nodeArray);
  1011. let handledNodeCount = 0;
  1012. for (let i = 0; i < nodeArray.length; i++) {
  1013. let node = nodeArray[i];
  1014. if (this.positionedNodes[node.id] === undefined) {
  1015. let spacing = this.options.hierarchical.nodeSpacing;
  1016. let pos = spacing * handledNodeCount;
  1017. // We get the X or Y values we need and store them in pos and previousPos.
  1018. // The get and set make sure we get X or Y
  1019. if (handledNodeCount > 0) {
  1020. pos = this._getPositionForHierarchy(nodeArray[i-1]) + spacing;
  1021. }
  1022. this._setPositionForHierarchy(node, pos, level);
  1023. this._validatePositionAndContinue(node, level, pos);
  1024. handledNodeCount++;
  1025. }
  1026. }
  1027. }
  1028. }
  1029. }
  1030. /**
  1031. * This is a recursively called function to enumerate the branches from the largest hubs and place the nodes
  1032. * on a X position that ensures there will be no overlap.
  1033. *
  1034. * @param parentId
  1035. * @param parentLevel
  1036. * @private
  1037. */
  1038. _placeBranchNodes(parentId, parentLevel) {
  1039. let childRef = this.hierarchical.childrenReference[parentId];
  1040. // if this is not a parent, cancel the placing. This can happen with multiple parents to one child.
  1041. if (childRef === undefined) {
  1042. return;
  1043. }
  1044. // get a list of childNodes
  1045. let childNodes = [];
  1046. for (let i = 0; i < childRef.length; i++) {
  1047. childNodes.push(this.body.nodes[childRef[i]]);
  1048. }
  1049. // use the positions to order the nodes.
  1050. this._sortNodeArray(childNodes);
  1051. // position the childNodes
  1052. for (let i = 0; i < childNodes.length; i++) {
  1053. let childNode = childNodes[i];
  1054. let childNodeLevel = this.hierarchical.levels[childNode.id];
  1055. // check if the child node is below the parent node and if it has already been positioned.
  1056. if (childNodeLevel > parentLevel && this.positionedNodes[childNode.id] === undefined) {
  1057. // get the amount of space required for this node. If parent the width is based on the amount of children.
  1058. let spacing = this.options.hierarchical.nodeSpacing;
  1059. let pos;
  1060. // we get the X or Y values we need and store them in pos and previousPos.
  1061. // The get and set make sure we get X or Y
  1062. if (i === 0) {pos = this._getPositionForHierarchy(this.body.nodes[parentId]);}
  1063. else {pos = this._getPositionForHierarchy(childNodes[i-1]) + spacing;}
  1064. this._setPositionForHierarchy(childNode, pos, childNodeLevel);
  1065. this._validatePositionAndContinue(childNode, childNodeLevel, pos);
  1066. }
  1067. else {
  1068. return;
  1069. }
  1070. }
  1071. // center the parent nodes.
  1072. let center = this._getCenterPosition(childNodes);
  1073. this._setPositionForHierarchy(this.body.nodes[parentId], center, parentLevel);
  1074. }
  1075. /**
  1076. * This method checks for overlap and if required shifts the branch. It also keeps records of positioned nodes.
  1077. * Finally it will call _placeBranchNodes to place the branch nodes.
  1078. * @param node
  1079. * @param level
  1080. * @param pos
  1081. * @private
  1082. */
  1083. _validatePositionAndContinue(node, level, pos) {
  1084. // This method only works for formal trees and formal forests
  1085. // Early exit if this is not the case
  1086. if (!this.hierarchical.isTree) return;
  1087. // if overlap has been detected, we shift the branch
  1088. if (this.lastNodeOnLevel[level] !== undefined) {
  1089. let previousPos = this._getPositionForHierarchy(this.body.nodes[this.lastNodeOnLevel[level]]);
  1090. if (pos - previousPos < this.options.hierarchical.nodeSpacing) {
  1091. let diff = (previousPos + this.options.hierarchical.nodeSpacing) - pos;
  1092. let sharedParent = this._findCommonParent(this.lastNodeOnLevel[level], node.id);
  1093. this._shiftBlock(sharedParent.withChild, diff);
  1094. }
  1095. }
  1096. this.lastNodeOnLevel[level] = node.id; // store change in position.
  1097. this.positionedNodes[node.id] = true;
  1098. this._placeBranchNodes(node.id, level);
  1099. }
  1100. /**
  1101. * Receives an array with node indices and returns an array with the actual node references.
  1102. * Used for sorting based on node properties.
  1103. * @param idArray
  1104. */
  1105. _indexArrayToNodes(idArray) {
  1106. let array = [];
  1107. for (let i = 0; i < idArray.length; i++) {
  1108. array.push(this.body.nodes[idArray[i]])
  1109. }
  1110. return array;
  1111. }
  1112. /**
  1113. * This function get the distribution of levels based on hubsize
  1114. *
  1115. * @returns {Object}
  1116. * @private
  1117. */
  1118. _getDistribution() {
  1119. let distribution = {};
  1120. let nodeId, node;
  1121. // we fix Y because the hierarchy is vertical,
  1122. // we fix X so we do not give a node an x position for a second time.
  1123. // the fix of X is removed after the x value has been set.
  1124. for (nodeId in this.body.nodes) {
  1125. if (this.body.nodes.hasOwnProperty(nodeId)) {
  1126. node = this.body.nodes[nodeId];
  1127. let level = this.hierarchical.levels[nodeId] === undefined ? 0 : this.hierarchical.levels[nodeId];
  1128. if(this._isVertical()) {
  1129. node.y = this.options.hierarchical.levelSeparation * level;
  1130. node.options.fixed.y = true;
  1131. }
  1132. else {
  1133. node.x = this.options.hierarchical.levelSeparation * level;
  1134. node.options.fixed.x = true;
  1135. }
  1136. if (distribution[level] === undefined) {
  1137. distribution[level] = {};
  1138. }
  1139. distribution[level][nodeId] = node;
  1140. }
  1141. }
  1142. return distribution;
  1143. }
  1144. /**
  1145. * Return the active (i.e. visible) edges for this node
  1146. *
  1147. * @returns {array} Array of edge instances
  1148. * @private
  1149. */
  1150. _getActiveEdges(node) {
  1151. let result = [];
  1152. for (let j in node.edges) {
  1153. let edge = node.edges[j];
  1154. if (this.body.edgeIndices.indexOf(edge.id) !== -1) {
  1155. result.push(edge);
  1156. }
  1157. }
  1158. return result;
  1159. }
  1160. /**
  1161. * Get the hubsizes for all active nodes.
  1162. *
  1163. * @returns {number}
  1164. * @private
  1165. */
  1166. _getHubSizes() {
  1167. let hubSizes = {};
  1168. let nodeIds = this.body.nodeIndices;
  1169. for (let i in nodeIds) {
  1170. let nodeId = nodeIds[i];
  1171. let node = this.body.nodes[nodeId];
  1172. let hubSize = this._getActiveEdges(node).length;
  1173. hubSizes[hubSize] = true;
  1174. }
  1175. // Make an array of the size sorted descending
  1176. let result = [];
  1177. for (let size in hubSizes) {
  1178. result.push(Number(size));
  1179. }
  1180. result.sort(function(a, b) {
  1181. return b - a;
  1182. });
  1183. return result;
  1184. }
  1185. /**
  1186. * this function allocates nodes in levels based on the recursive branching from the largest hubs.
  1187. *
  1188. * @private
  1189. */
  1190. _determineLevelsByHubsize() {
  1191. let levelDownstream = (nodeA, nodeB) => {
  1192. this.hierarchical.levelDownstream(nodeA, nodeB);
  1193. }
  1194. let hubSizes = this._getHubSizes();
  1195. for (let i = 0; i < hubSizes.length; ++i ) {
  1196. let hubSize = hubSizes[i];
  1197. if (hubSize === 0) break;
  1198. let nodeIds = this.body.nodeIndices;
  1199. for (let j in nodeIds) {
  1200. let nodeId = nodeIds[j];
  1201. let node = this.body.nodes[nodeId];
  1202. if (hubSize === this._getActiveEdges(node).length) {
  1203. this._crawlNetwork(levelDownstream, nodeId);
  1204. }
  1205. }
  1206. }
  1207. }
  1208. /**
  1209. * TODO: release feature
  1210. * TODO: Determine if this feature is needed at all
  1211. *
  1212. * @private
  1213. */
  1214. _determineLevelsCustomCallback() {
  1215. let minLevel = 100000;
  1216. // TODO: this should come from options.
  1217. let customCallback = function(nodeA, nodeB, edge) { // eslint-disable-line no-unused-vars
  1218. };
  1219. // TODO: perhaps move to HierarchicalStatus.
  1220. // But I currently don't see the point, this method is not used.
  1221. let levelByDirection = (nodeA, nodeB, edge) => {
  1222. let levelA = this.hierarchical.levels[nodeA.id];
  1223. // set initial level
  1224. if (levelA === undefined) { levelA = this.hierarchical.levels[nodeA.id] = minLevel;}
  1225. let diff = customCallback(
  1226. NetworkUtil.cloneOptions(nodeA,'node'),
  1227. NetworkUtil.cloneOptions(nodeB,'node'),
  1228. NetworkUtil.cloneOptions(edge,'edge')
  1229. );
  1230. this.hierarchical.levels[nodeB.id] = levelA + diff;
  1231. };
  1232. this._crawlNetwork(levelByDirection);
  1233. this.hierarchical.setMinLevelToZero(this.body.nodes);
  1234. }
  1235. /**
  1236. * Allocate nodes in levels based on the direction of the edges.
  1237. *
  1238. * @param hubsize
  1239. * @private
  1240. */
  1241. _determineLevelsDirected() {
  1242. let minLevel = 10000;
  1243. /**
  1244. * Check if there is an edge going the opposite direction for given edge
  1245. */
  1246. let self = this;
  1247. let isBidirectional = (edge) => {
  1248. for (let key in self.body.edges) {
  1249. let otherEdge = self.body.edges[key];
  1250. if (otherEdge.toId === edge.fromId && otherEdge.fromId === edge.toId) {
  1251. return true;
  1252. }
  1253. }
  1254. return false;
  1255. };
  1256. let levelByDirection = (nodeA, nodeB, edge) => {
  1257. let levelA = this.hierarchical.levels[nodeA.id];
  1258. let levelB = this.hierarchical.levels[nodeB.id];
  1259. if (isBidirectional(edge) && levelA !== undefined && levelB !== undefined) {
  1260. // Don't redo the level determination if already done in this case.
  1261. return;
  1262. }
  1263. // set initial level
  1264. if (levelA === undefined) { levelA = this.hierarchical.levels[nodeA.id] = minLevel;}
  1265. if (edge.toId == nodeB.id) {
  1266. this.hierarchical.levels[nodeB.id] = levelA + 1;
  1267. }
  1268. else {
  1269. this.hierarchical.levels[nodeB.id] = levelA - 1;
  1270. }
  1271. };
  1272. this._crawlNetwork(levelByDirection);
  1273. this.hierarchical.setMinLevelToZero(this.body.nodes);
  1274. }
  1275. /**
  1276. * Update the bookkeeping of parent and child.
  1277. * @private
  1278. */
  1279. _generateMap() {
  1280. let fillInRelations = (parentNode, childNode) => {
  1281. if (this.hierarchical.levels[childNode.id] > this.hierarchical.levels[parentNode.id]) {
  1282. this.hierarchical.addRelation(parentNode.id, childNode.id);
  1283. }
  1284. };
  1285. this._crawlNetwork(fillInRelations);
  1286. this.hierarchical.checkIfTree();
  1287. }
  1288. /**
  1289. * Crawl over the entire network and use a callback on each node couple that is connected to each other.
  1290. * @param callback | will receive nodeA, nodeB and the connecting edge. A and B are distinct.
  1291. * @param startingNodeId
  1292. * @private
  1293. */
  1294. _crawlNetwork(callback = function() {}, startingNodeId) {
  1295. let progress = {};
  1296. let crawler = (node, tree) => {
  1297. if (progress[node.id] === undefined) {
  1298. this.hierarchical.setTreeIndex(node, tree);
  1299. progress[node.id] = true;
  1300. let childNode;
  1301. let edges = this._getActiveEdges(node);
  1302. for (let i = 0; i < edges.length; i++) {
  1303. let edge = edges[i];
  1304. if (edge.connected === true) {
  1305. if (edge.toId == node.id) { // Not '===' because id's can be string and numeric
  1306. childNode = edge.from;
  1307. }
  1308. else {
  1309. childNode = edge.to;
  1310. }
  1311. if (node.id != childNode.id) { // Not '!==' because id's can be string and numeric
  1312. callback(node, childNode, edge);
  1313. crawler(childNode, tree);
  1314. }
  1315. }
  1316. }
  1317. }
  1318. };
  1319. if (startingNodeId === undefined) {
  1320. // Crawl over all nodes
  1321. let treeIndex = 0; // Serves to pass a unique id for the current distinct tree
  1322. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  1323. let nodeId = this.body.nodeIndices[i];
  1324. if (progress[nodeId] === undefined) {
  1325. let node = this.body.nodes[nodeId];
  1326. crawler(node, treeIndex);
  1327. treeIndex += 1;
  1328. }
  1329. }
  1330. }
  1331. else {
  1332. // Crawl from the given starting node
  1333. let node = this.body.nodes[startingNodeId];
  1334. if (node === undefined) {
  1335. console.error("Node not found:", startingNodeId);
  1336. return;
  1337. }
  1338. crawler(node);
  1339. }
  1340. }
  1341. /**
  1342. * Shift a branch a certain distance
  1343. * @param parentId
  1344. * @param diff
  1345. * @private
  1346. */
  1347. _shiftBlock(parentId, diff) {
  1348. let progress = {};
  1349. let shifter = (parentId) => {
  1350. if (progress[parentId]) {
  1351. return;
  1352. }
  1353. progress[parentId] = true;
  1354. if(this._isVertical()) {
  1355. this.body.nodes[parentId].x += diff;
  1356. }
  1357. else {
  1358. this.body.nodes[parentId].y += diff;
  1359. }
  1360. let childRef = this.hierarchical.childrenReference[parentId];
  1361. if (childRef !== undefined) {
  1362. for (let i = 0; i < childRef.length; i++) {
  1363. shifter(childRef[i]);
  1364. }
  1365. }
  1366. };
  1367. shifter(parentId);
  1368. }
  1369. /**
  1370. * Find a common parent between branches.
  1371. * @param childA
  1372. * @param childB
  1373. * @returns {{foundParent, withChild}}
  1374. * @private
  1375. */
  1376. _findCommonParent(childA,childB) {
  1377. let parents = {};
  1378. let iterateParents = (parents,child) => {
  1379. let parentRef = this.hierarchical.parentReference[child];
  1380. if (parentRef !== undefined) {
  1381. for (let i = 0; i < parentRef.length; i++) {
  1382. let parent = parentRef[i];
  1383. parents[parent] = true;
  1384. iterateParents(parents, parent)
  1385. }
  1386. }
  1387. };
  1388. let findParent = (parents, child) => {
  1389. let parentRef = this.hierarchical.parentReference[child];
  1390. if (parentRef !== undefined) {
  1391. for (let i = 0; i < parentRef.length; i++) {
  1392. let parent = parentRef[i];
  1393. if (parents[parent] !== undefined) {
  1394. return {foundParent:parent, withChild:child};
  1395. }
  1396. let branch = findParent(parents, parent);
  1397. if (branch.foundParent !== null) {
  1398. return branch;
  1399. }
  1400. }
  1401. }
  1402. return {foundParent:null, withChild:child};
  1403. };
  1404. iterateParents(parents, childA);
  1405. return findParent(parents, childB);
  1406. }
  1407. /**
  1408. * Abstract the getting of the position so we won't have to repeat the check for direction all the time
  1409. * @param node
  1410. * @param position
  1411. * @param level
  1412. * @private
  1413. */
  1414. _setPositionForHierarchy(node, position, level, doNotUpdate = false) {
  1415. //console.log('_setPositionForHierarchy',node.id, position)
  1416. if (doNotUpdate !== true) {
  1417. this.hierarchical.addToOrdering(node, level);
  1418. }
  1419. if(this._isVertical()) {
  1420. node.x = position;
  1421. }
  1422. else {
  1423. node.y = position;
  1424. }
  1425. }
  1426. /**
  1427. * Utility function to cut down on typing this all the time.
  1428. *
  1429. * TODO: use this in all applicable situations in this class.
  1430. *
  1431. * @private
  1432. */
  1433. _isVertical() {
  1434. return (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU');
  1435. }
  1436. /**
  1437. * Abstract the getting of the position of a node so we do not have to repeat the direction check all the time.
  1438. * @param node
  1439. * @returns {number|*}
  1440. * @private
  1441. */
  1442. _getPositionForHierarchy(node) {
  1443. if(this._isVertical()) {
  1444. return node.x;
  1445. }
  1446. else {
  1447. return node.y;
  1448. }
  1449. }
  1450. /**
  1451. * Use the x or y value to sort the array, allowing users to specify order.
  1452. * @param nodeArray
  1453. * @private
  1454. */
  1455. _sortNodeArray(nodeArray) {
  1456. if (nodeArray.length > 1) {
  1457. if(this._isVertical()) {
  1458. nodeArray.sort(function (a, b) {
  1459. return a.x - b.x;
  1460. })
  1461. }
  1462. else {
  1463. nodeArray.sort(function (a, b) {
  1464. return a.y - b.y;
  1465. })
  1466. }
  1467. }
  1468. }
  1469. /**
  1470. * Get the type of static smooth curve in case it is required.
  1471. *
  1472. * The return value is the type to use to translate dynamic curves to
  1473. * another type, in the case of hierarchical layout. Dynamic curves do
  1474. * not work for that layout type.
  1475. */
  1476. getStaticType() {
  1477. // Node that 'type' is the edge type, and therefore 'orthogonal' to the layout type.
  1478. let type = 'horizontal';
  1479. if (!this._isVertical()) {
  1480. type = 'vertical';
  1481. }
  1482. return type;
  1483. }
  1484. /**
  1485. * Determine the center position of a branch from the passed list of child nodes
  1486. *
  1487. * This takes into account the positions of all the child nodes.
  1488. * @param childNodes {array} Array of either child nodes or node id's
  1489. * @return {number}
  1490. * @private
  1491. */
  1492. _getCenterPosition(childNodes) {
  1493. let minPos = 1e9;
  1494. let maxPos = -1e9;
  1495. for (let i = 0; i < childNodes.length; i++) {
  1496. let childNode;
  1497. if (childNodes[i].id !== undefined) {
  1498. childNode = childNodes[i];
  1499. } else {
  1500. let childNodeId = childNodes[i];
  1501. childNode = this.body.nodes[childNodeId];
  1502. }
  1503. let position = this._getPositionForHierarchy(childNode);
  1504. minPos = Math.min(minPos, position);
  1505. maxPos = Math.max(maxPos, position);
  1506. }
  1507. return 0.5 * (minPos + maxPos);
  1508. }
  1509. }
  1510. export default LayoutEngine;