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.

1742 lines
55 KiB

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