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.

1723 lines
54 KiB

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