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.

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