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.

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