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.

1616 lines
51 KiB

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