From 2bff294789bf693b755e8472c8a7580b55b5a6b1 Mon Sep 17 00:00:00 2001 From: Alex de Mulder Date: Sat, 13 Feb 2016 14:37:12 +0100 Subject: [PATCH] - Fixed #1644, #1631: overlapping nodes in hierarchical layout should no longer occur. - Added parentCentralization option for hierarchical layout. --- HISTORY.md | 2 + dist/vis.js | 158 +- docs/network/layout.html | 2 + .../hierarchicalLayoutWithoutPhysics.html | 4 + lib/network/modules/LayoutEngine.js | 145 +- lib/network/options.js | 2 + test/networkTest.html | 13208 +--------------- 7 files changed, 263 insertions(+), 13258 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index d37c49b7..506f4636 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -20,6 +20,8 @@ http://visjs.org ### Network - Fixed #1635: edges are now referring to the correct points. +- Fixed #1644, #1631: overlapping nodes in hierarchical layout should no longer occur. +- Added parentCentralization option for hierarchical layout. ## 2016-02-04, version 4.14.0 diff --git a/dist/vis.js b/dist/vis.js index a9d8ac86..b1220eec 100644 --- a/dist/vis.js +++ b/dist/vis.js @@ -5,7 +5,7 @@ * A dynamic, browser-based visualization library. * * @version 4.14.0 - * @date 2016-02-12 + * @date 2016-02-13 * * @license * Copyright (C) 2011-2016 Almende B.V, http://almende.com @@ -1582,7 +1582,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(module) {//! moment.js - //! version : 2.11.1 + //! version : 2.11.2 //! authors : Tim Wood, Iskren Chernev, Moment.js contributors //! license : MIT //! momentjs.com @@ -3399,7 +3399,7 @@ return /******/ (function(modules) { // webpackBootstrap } // ASP.NET json date format regex - var aspNetRegex = /(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/; + var aspNetRegex = /^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?\d*)?$/; // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere @@ -5154,7 +5154,7 @@ return /******/ (function(modules) { // webpackBootstrap // Side effect imports - utils_hooks__hooks.version = '2.11.1'; + utils_hooks__hooks.version = '2.11.2'; setHookCallback(local__createLocal); @@ -40083,6 +40083,7 @@ return /******/ (function(modules) { // webpackBootstrap treeSpacing: 200, blockShifting: true, edgeMinimization: true, + parentCentralization: true, direction: 'UD', // UD, DU, LR, RL sortMethod: 'hubsize' // hubsize, directed } @@ -40364,8 +40365,8 @@ return /******/ (function(modules) { // webpackBootstrap var undefinedLevel = false; this.hierarchicalLevels = {}; this.lastNodeOnLevel = {}; - this.hierarchicalParents = {}; - this.hierarchicalChildren = {}; + this.hierarchicalChildrenReference = {}; + this.hierarchicalParentReference = {}; this.hierarchicalTrees = {}; this.treeIndex = -1; @@ -40447,9 +40448,7 @@ return /******/ (function(modules) { // webpackBootstrap var treeSizes = getTreeSizes(); for (var i = 0; i < treeSizes.length - 1; i++) { var diff = treeSizes[i].max - treeSizes[i + 1].min; - if (diff !== _this2.options.hierarchical.treeSpacing) { - shiftTree(i + 1, diff - _this2.options.hierarchical.treeSpacing); - } + shiftTree(i + 1, diff + _this2.options.hierarchical.treeSpacing); } }; @@ -40458,7 +40457,9 @@ return /******/ (function(modules) { // webpackBootstrap for (var nodeId in _this2.hierarchicalTrees) { if (_this2.hierarchicalTrees.hasOwnProperty(nodeId)) { if (_this2.hierarchicalTrees[nodeId] === index) { - _this2._setPositionForHierarchy(_this2.body.nodes[nodeId], offset, undefined, true); + var node = _this2.body.nodes[nodeId]; + var pos = _this2._getPositionForHierarchy(node); + _this2._setPositionForHierarchy(node, pos + offset, undefined, true); } } } @@ -40483,7 +40484,7 @@ return /******/ (function(modules) { // webpackBootstrap // get the width of all trees var getTreeSizes = function getTreeSizes() { var treeWidths = []; - for (var i = 0; i < _this2.treeIndex; i++) { + for (var i = 0; i <= _this2.treeIndex; i++) { treeWidths.push(getTreeSize(i)); } return treeWidths; @@ -40492,8 +40493,8 @@ return /******/ (function(modules) { // webpackBootstrap // get a map of all nodes in this branch var getBranchNodes = function getBranchNodes(source, map) { map[source.id] = true; - if (_this2.hierarchicalParents[source.id]) { - var children = _this2.hierarchicalParents[source.id].children; + if (_this2.hierarchicalChildrenReference[source.id]) { + var children = _this2.hierarchicalChildrenReference[source.id]; if (children.length > 0) { for (var i = 0; i < children.length; i++) { getBranchNodes(_this2.body.nodes[children[i]], map); @@ -40543,8 +40544,8 @@ return /******/ (function(modules) { // webpackBootstrap // get the maximum level of a branch. var getMaxLevel = function getMaxLevel(nodeId) { var level = _this2.hierarchicalLevels[nodeId]; - if (_this2.hierarchicalParents[nodeId]) { - var children = _this2.hierarchicalParents[nodeId].children; + if (_this2.hierarchicalChildrenReference[nodeId]) { + var children = _this2.hierarchicalChildrenReference[nodeId]; if (children.length > 0) { for (var i = 0; i < children.length; i++) { level = Math.max(level, getMaxLevel(children[i])); @@ -40563,13 +40564,12 @@ return /******/ (function(modules) { // webpackBootstrap // check if two nodes have the same parent(s) var hasSameParent = function hasSameParent(node1, node2) { - var parents1 = _this2.hierarchicalChildren[node1.id]; - var parents2 = _this2.hierarchicalChildren[node2.id]; + var parents1 = _this2.hierarchicalParentReference[node1.id]; + var parents2 = _this2.hierarchicalParentReference[node2.id]; if (parents1 === undefined || parents2 === undefined) { return false; } - parents1 = parents1.parents; - parents2 = parents2.parents; + for (var i = 0; i < parents1.length; i++) { for (var j = 0; j < parents2.length; j++) { if (parents1[i] == parents2[j]) { @@ -40782,7 +40782,7 @@ return /******/ (function(modules) { // webpackBootstrap } if (newPosition !== nodePosition) { - //console.log("moving Node:",diff, minSpace, maxSpace) + //console.log("moving Node:",diff, minSpace, maxSpace); _this2._setPositionForHierarchy(node, newPosition, undefined, true); //this.body.emitter.emit("_redraw"); stillShifting = true; @@ -40816,7 +40816,7 @@ return /******/ (function(modules) { // webpackBootstrap } }; - //// method to remove whitespace between branches. Because we do bottom up, we can center the parents. + // method to remove whitespace between branches. Because we do bottom up, we can center the parents. var shiftBranchesCloserBottomUp = function shiftBranchesCloserBottomUp(iterations) { var levels = Object.keys(_this2.distributionOrdering); levels = levels.reverse(); @@ -40837,6 +40837,19 @@ return /******/ (function(modules) { // webpackBootstrap } }; + // center all parents + var centerAllParentsBottomUp = function centerAllParentsBottomUp() { + var levels = Object.keys(_this2.distributionOrdering); + levels = levels.reverse(); + for (var i = 0; i < levels.length; i++) { + var level = levels[i]; + var levelNodes = _this2.distributionOrdering[level]; + for (var j = 0; j < levelNodes.length; j++) { + _this2._centerParent(levelNodes[j]); + } + } + }; + // the actual work is done here. if (this.options.hierarchical.blockShifting === true) { shiftBranchesCloserBottomUp(5); @@ -40848,6 +40861,10 @@ return /******/ (function(modules) { // webpackBootstrap minimizeEdgeLengthBottomUp(20); } + if (this.options.hierarchical.parentCentralization === true) { + centerAllParentsBottomUp(); + } + shiftTrees(); } @@ -40902,16 +40919,16 @@ return /******/ (function(modules) { // webpackBootstrap }, { key: '_centerParent', value: function _centerParent(node) { - if (this.hierarchicalChildren[node.id]) { - var parents = this.hierarchicalChildren[node.id].parents; + if (this.hierarchicalParentReference[node.id]) { + var parents = this.hierarchicalParentReference[node.id]; for (var i = 0; i < parents.length; i++) { var parentId = parents[i]; var parentNode = this.body.nodes[parentId]; - if (this.hierarchicalParents[parentId]) { + if (this.hierarchicalChildrenReference[parentId]) { // get the range of the children var minPos = 1e9; var maxPos = -1e9; - var children = this.hierarchicalParents[parentId].children; + var children = this.hierarchicalChildrenReference[parentId]; if (children.length > 0) { for (var _i = 0; _i < children.length; _i++) { var childNode = this.body.nodes[children[_i]]; @@ -40956,13 +40973,22 @@ return /******/ (function(modules) { // webpackBootstrap var nodeArray = Object.keys(distribution[level]); nodeArray = this._indexArrayToNodes(nodeArray); this._sortNodeArray(nodeArray); + var handledNodeCount = 0; for (var i = 0; i < nodeArray.length; i++) { var node = nodeArray[i]; if (this.positionedNodes[node.id] === undefined) { - this._setPositionForHierarchy(node, this.options.hierarchical.nodeSpacing * i, level); + var pos = this.options.hierarchical.nodeSpacing * handledNodeCount; + // we get the X or Y values we need and store them in pos and previousPos. The get and set make sure we get X or Y + if (handledNodeCount > 0) { + pos = this._getPositionForHierarchy(nodeArray[i - 1]) + this.options.hierarchical.nodeSpacing; + } + this._setPositionForHierarchy(node, pos, level); + this.positionedNodes[node.id] = true; this._placeBranchNodes(node.id, level); + + handledNodeCount++; } } } @@ -41178,14 +41204,14 @@ return /******/ (function(modules) { // webpackBootstrap if (_this6.hierarchicalLevels[childNode.id] > _this6.hierarchicalLevels[parentNode.id]) { var parentNodeId = parentNode.id; var childNodeId = childNode.id; - if (_this6.hierarchicalParents[parentNodeId] === undefined) { - _this6.hierarchicalParents[parentNodeId] = { children: [], amount: 0 }; + if (_this6.hierarchicalChildrenReference[parentNodeId] === undefined) { + _this6.hierarchicalChildrenReference[parentNodeId] = []; } - _this6.hierarchicalParents[parentNodeId].children.push(childNodeId); - if (_this6.hierarchicalChildren[childNodeId] === undefined) { - _this6.hierarchicalChildren[childNodeId] = { parents: [], amount: 0 }; + _this6.hierarchicalChildrenReference[parentNodeId].push(childNodeId); + if (_this6.hierarchicalParentReference[childNodeId] === undefined) { + _this6.hierarchicalParentReference[childNodeId] = []; } - _this6.hierarchicalChildren[childNodeId].parents.push(parentNodeId); + _this6.hierarchicalParentReference[childNodeId].push(parentNodeId); } }; @@ -41201,11 +41227,21 @@ return /******/ (function(modules) { // webpackBootstrap }, { key: '_crawlNetwork', value: function _crawlNetwork(callback, startingNodeId) { + var _this7 = this; + if (callback === undefined) callback = function () {}; var progress = {}; - var crawler = function crawler(node) { + var treeIndex = 0; + + var crawler = function crawler(node, tree) { if (progress[node.id] === undefined) { + + if (_this7.hierarchicalTrees[node.id] === undefined) { + _this7.hierarchicalTrees[node.id] = tree; + _this7.treeIndex = Math.max(tree, _this7.treeIndex); + } + progress[node.id] = true; var childNode = undefined; for (var i = 0; i < node.edges.length; i++) { @@ -41218,7 +41254,7 @@ return /******/ (function(modules) { // webpackBootstrap if (node.id !== childNode.id) { callback(node, childNode, node.edges[i]); - crawler(childNode); + crawler(childNode, tree); } } } @@ -41229,7 +41265,10 @@ return /******/ (function(modules) { // webpackBootstrap if (startingNodeId === undefined) { for (var i = 0; i < this.body.nodeIndices.length; i++) { var node = this.body.nodes[this.body.nodeIndices[i]]; - crawler(node); + if (progress[node.id] === undefined) { + crawler(node, treeIndex); + treeIndex += 1; + } } } else { var node = this.body.nodes[startingNodeId]; @@ -41253,14 +41292,14 @@ return /******/ (function(modules) { // webpackBootstrap key: '_placeBranchNodes', value: function _placeBranchNodes(parentId, parentLevel) { // if this is not a parent, cancel the placing. This can happen with multiple parents to one child. - if (this.hierarchicalParents[parentId] === undefined) { + if (this.hierarchicalChildrenReference[parentId] === undefined) { return; } // get a list of childNodes var childNodes = []; - for (var i = 0; i < this.hierarchicalParents[parentId].children.length; i++) { - childNodes.push(this.body.nodes[this.hierarchicalParents[parentId].children[i]]); + for (var i = 0; i < this.hierarchicalChildrenReference[parentId].length; i++) { + childNodes.push(this.body.nodes[this.hierarchicalChildrenReference[parentId][i]]); } // use the positions to order the nodes. @@ -41329,9 +41368,9 @@ return /******/ (function(modules) { // webpackBootstrap } else { this.body.nodes[parentId].y += diff; } - if (this.hierarchicalParents[parentId] !== undefined) { - for (var i = 0; i < this.hierarchicalParents[parentId].children.length; i++) { - this._shiftBlock(this.hierarchicalParents[parentId].children[i], diff); + if (this.hierarchicalChildrenReference[parentId] !== undefined) { + for (var i = 0; i < this.hierarchicalChildrenReference[parentId].length; i++) { + this._shiftBlock(this.hierarchicalChildrenReference[parentId][i], diff); } } } @@ -41346,22 +41385,22 @@ return /******/ (function(modules) { // webpackBootstrap }, { key: '_findCommonParent', value: function _findCommonParent(childA, childB) { - var _this7 = this; + var _this8 = this; var parents = {}; var iterateParents = function iterateParents(parents, child) { - if (_this7.hierarchicalChildren[child] !== undefined) { - for (var i = 0; i < _this7.hierarchicalChildren[child].parents.length; i++) { - var _parent = _this7.hierarchicalChildren[child].parents[i]; + if (_this8.hierarchicalParentReference[child] !== undefined) { + for (var i = 0; i < _this8.hierarchicalParentReference[child].length; i++) { + var _parent = _this8.hierarchicalParentReference[child][i]; parents[_parent] = true; iterateParents(parents, _parent); } } }; var findParent = function findParent(parents, child) { - if (_this7.hierarchicalChildren[child] !== undefined) { - for (var i = 0; i < _this7.hierarchicalChildren[child].parents.length; i++) { - var _parent2 = _this7.hierarchicalChildren[child].parents[i]; + if (_this8.hierarchicalParentReference[child] !== undefined) { + for (var i = 0; i < _this8.hierarchicalParentReference[child].length; i++) { + var _parent2 = _this8.hierarchicalParentReference[child][i]; if (parents[_parent2] !== undefined) { return { foundParent: _parent2, withChild: child }; } @@ -41401,27 +41440,6 @@ return /******/ (function(modules) { // webpackBootstrap this.distributionIndex[node.id] = this.distributionOrdering[level].length - 1; } this.distributionOrderingPresence[level][node.id] = true; - - if (this.hierarchicalTrees[node.id] === undefined) { - if (this.hierarchicalChildren[node.id] !== undefined) { - var tree = 1; - // get the lowest tree denominator. - for (var i = 0; i < this.hierarchicalChildren[node.id].parents.length; i++) { - var parentId = this.hierarchicalChildren[node.id].parents[i]; - if (this.hierarchicalTrees[parentId] !== undefined) { - //tree = Math.min(tree,this.hierarchicalTrees[parentId]); - tree = this.hierarchicalTrees[parentId]; - } - } - //for (let i = 0; i < this.hierarchicalChildren.parents.length; i++) { - // let parentId = this.hierarchicalChildren.parents[i]; - // this.hierarchicalTrees[parentId] = tree; - //} - this.hierarchicalTrees[node.id] = tree; - } else { - this.hierarchicalTrees[node.id] = ++this.treeIndex; - } - } } if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') { @@ -42829,6 +42847,7 @@ return /******/ (function(modules) { // webpackBootstrap treeSpacing: { number: number }, blockShifting: { boolean: boolean }, edgeMinimization: { boolean: boolean }, + parentCentralization: { boolean: boolean }, direction: { string: ['UD', 'DU', 'LR', 'RL'] }, // UD, DU, LR, RL sortMethod: { string: ['hubsize', 'directed'] }, // hubsize, directed __type__: { object: object, boolean: boolean } @@ -43132,6 +43151,7 @@ return /******/ (function(modules) { // webpackBootstrap treeSpacing: [200, 20, 500, 5], blockShifting: true, edgeMinimization: true, + parentCentralization: true, direction: ['UD', 'DU', 'LR', 'RL'], // UD, DU, LR, RL sortMethod: ['hubsize', 'directed'] // hubsize, directed } diff --git a/docs/network/layout.html b/docs/network/layout.html index 08f1eca9..3b38048b 100644 --- a/docs/network/layout.html +++ b/docs/network/layout.html @@ -109,6 +109,7 @@ var options = { treeSpacing: 200, blockShifting: true, edgeMinimization: true, + parentCentralization: true, direction: 'UD', // UD, DU, LR, RL sortMethod: 'hubsize' // hubsize, directed } @@ -142,6 +143,7 @@ network.setOptions(options); it's branch along with it for as far as it can, respecting the nodeSpacing on any level. This is mainly for the initial layout. If you enable physics, they layout will be determined by the physics. This will greatly speed up the stabilization time though! hierarchical.edgeMinimizationBooleantrue Method for reducing whitespace. Can be used alone or together with block shifting. Enabling block shifting will usually speed up the layout process. Each node will try to move along its free axis to reduce the total length of it's edges. This is mainly for the initial layout. If you enable physics, they layout will be determined by the physics. This will greatly speed up the stabilization time though! + hierarchical.parentCentralizationBooleantrue When true, the parents nodes will be centered again after the the layout algorithm has been finished. hierarchical.directionString'UD' The direction of the hierarchical layout. The available options are: UD, DU, LR, RL. To simplify: up-down, down-up, left-right, right-left. hierarchical.sortMethodString'hubsize' The algorithm used to ascertain the levels of the nodes based on the data. The possible options are: hubsize, directed.

Hubsize takes the nodes with the most edges and puts them at the top. From that the rest of the hierarchy is evaluated.

diff --git a/examples/network/layout/hierarchicalLayoutWithoutPhysics.html b/examples/network/layout/hierarchicalLayoutWithoutPhysics.html index dfa32f3b..27854810 100644 --- a/examples/network/layout/hierarchicalLayoutWithoutPhysics.html +++ b/examples/network/layout/hierarchicalLayoutWithoutPhysics.html @@ -47,6 +47,10 @@ The hierarchical layout can now be controlled without the use of physics. This i Method for reducing whitespace. Can be used alone or together with block shifting. Enabling block shifting will usually speed up the layout process. Each node will try to move along its free axis to reduce the total length of it's edges. + + parentCentralization + When true, the parents nodes will be centered again after the the layout algorithm has been finished. +

Play with the settings below the network and see how the layout changes! diff --git a/lib/network/modules/LayoutEngine.js b/lib/network/modules/LayoutEngine.js index 8e4fc6b3..48a6aa98 100644 --- a/lib/network/modules/LayoutEngine.js +++ b/lib/network/modules/LayoutEngine.js @@ -23,6 +23,7 @@ class LayoutEngine { treeSpacing: 200, blockShifting: true, edgeMinimization: true, + parentCentralization: true, direction: 'UD', // UD, DU, LR, RL sortMethod: 'hubsize' // hubsize, directed } @@ -294,8 +295,8 @@ class LayoutEngine { let undefinedLevel = false; this.hierarchicalLevels = {}; this.lastNodeOnLevel = {}; - this.hierarchicalParents = {}; - this.hierarchicalChildren = {}; + this.hierarchicalChildrenReference = {}; + this.hierarchicalParentReference = {}; this.hierarchicalTrees = {}; this.treeIndex = -1; @@ -379,9 +380,7 @@ class LayoutEngine { let treeSizes = getTreeSizes(); for (let i = 0; i < treeSizes.length - 1; i++) { let diff = treeSizes[i].max - treeSizes[i+1].min; - if (diff !== this.options.hierarchical.treeSpacing) { - shiftTree(i + 1, diff - this.options.hierarchical.treeSpacing); - } + shiftTree(i + 1, diff + this.options.hierarchical.treeSpacing); } }; @@ -390,7 +389,9 @@ class LayoutEngine { for (let nodeId in this.hierarchicalTrees) { if (this.hierarchicalTrees.hasOwnProperty(nodeId)) { if (this.hierarchicalTrees[nodeId] === index) { - this._setPositionForHierarchy(this.body.nodes[nodeId], offset, undefined, true); + let node = this.body.nodes[nodeId]; + let pos = this._getPositionForHierarchy(node); + this._setPositionForHierarchy(node, pos + offset, undefined, true); } } } @@ -415,7 +416,7 @@ class LayoutEngine { // get the width of all trees let getTreeSizes = () => { let treeWidths = []; - for (let i = 0; i < this.treeIndex; i++) { + for (let i = 0; i <= this.treeIndex; i++) { treeWidths.push(getTreeSize(i)); } return treeWidths; @@ -425,8 +426,8 @@ class LayoutEngine { // get a map of all nodes in this branch let getBranchNodes = (source, map) => { map[source.id] = true; - if (this.hierarchicalParents[source.id]) { - let children = this.hierarchicalParents[source.id].children; + if (this.hierarchicalChildrenReference[source.id]) { + let children = this.hierarchicalChildrenReference[source.id]; if (children.length > 0) { for (let i = 0; i < children.length; i++) { getBranchNodes(this.body.nodes[children[i]], map); @@ -467,8 +468,8 @@ class LayoutEngine { // get the maximum level of a branch. let getMaxLevel = (nodeId) => { let level = this.hierarchicalLevels[nodeId]; - if (this.hierarchicalParents[nodeId]) { - let children = this.hierarchicalParents[nodeId].children; + if (this.hierarchicalChildrenReference[nodeId]) { + let children = this.hierarchicalChildrenReference[nodeId]; if (children.length > 0) { for (let i = 0; i < children.length; i++) { level = Math.max(level,getMaxLevel(children[i])); @@ -487,13 +488,12 @@ class LayoutEngine { // check if two nodes have the same parent(s) let hasSameParent = (node1, node2) => { - let parents1 = this.hierarchicalChildren[node1.id]; - let parents2 = this.hierarchicalChildren[node2.id]; + let parents1 = this.hierarchicalParentReference[node1.id]; + let parents2 = this.hierarchicalParentReference[node2.id]; if (parents1 === undefined || parents2 === undefined) { return false; } - parents1 = parents1.parents; - parents2 = parents2.parents; + for (let i = 0; i < parents1.length; i++) { for (let j = 0; j < parents2.length; j++) { if (parents1[i] == parents2[j]) { @@ -676,7 +676,7 @@ class LayoutEngine { } if (newPosition !== nodePosition) { - //console.log("moving Node:",diff, minSpace, maxSpace) + //console.log("moving Node:",diff, minSpace, maxSpace); this._setPositionForHierarchy(node, newPosition, undefined, true); //this.body.emitter.emit("_redraw"); stillShifting = true; @@ -710,7 +710,7 @@ class LayoutEngine { } }; - //// method to remove whitespace between branches. Because we do bottom up, we can center the parents. + // method to remove whitespace between branches. Because we do bottom up, we can center the parents. let shiftBranchesCloserBottomUp = (iterations) => { let levels = Object.keys(this.distributionOrdering); levels = levels.reverse(); @@ -732,6 +732,19 @@ class LayoutEngine { } }; + // center all parents + let centerAllParentsBottomUp = () => { + let levels = Object.keys(this.distributionOrdering); + levels = levels.reverse(); + for (let i = 0; i < levels.length; i++) { + let level = levels[i]; + let levelNodes = this.distributionOrdering[level]; + for (let j = 0; j < levelNodes.length; j++) { + this._centerParent(levelNodes[j]); + } + } + }; + // the actual work is done here. if (this.options.hierarchical.blockShifting === true) { shiftBranchesCloserBottomUp(5); @@ -743,6 +756,10 @@ class LayoutEngine { minimizeEdgeLengthBottomUp(20); } + if (this.options.hierarchical.parentCentralization === true) { + centerAllParentsBottomUp() + } + shiftTrees(); } @@ -794,16 +811,16 @@ class LayoutEngine { * @private */ _centerParent(node) { - if (this.hierarchicalChildren[node.id]) { - let parents = this.hierarchicalChildren[node.id].parents; + if (this.hierarchicalParentReference[node.id]) { + let parents = this.hierarchicalParentReference[node.id]; for (var i = 0; i < parents.length; i++) { let parentId = parents[i]; let parentNode = this.body.nodes[parentId]; - if (this.hierarchicalParents[parentId]) { + if (this.hierarchicalChildrenReference[parentId]) { // get the range of the children let minPos = 1e9; let maxPos = -1e9; - let children = this.hierarchicalParents[parentId].children; + let children = this.hierarchicalChildrenReference[parentId]; if (children.length > 0) { for (let i = 0; i < children.length; i++) { let childNode = this.body.nodes[children[i]]; @@ -841,13 +858,20 @@ class LayoutEngine { let nodeArray = Object.keys(distribution[level]); nodeArray = this._indexArrayToNodes(nodeArray); this._sortNodeArray(nodeArray); + let handledNodeCount = 0; for (let i = 0; i < nodeArray.length; i++) { let node = nodeArray[i]; if (this.positionedNodes[node.id] === undefined) { - this._setPositionForHierarchy(node, this.options.hierarchical.nodeSpacing * i, level); + let pos = this.options.hierarchical.nodeSpacing * handledNodeCount; + // we get the X or Y values we need and store them in pos and previousPos. The get and set make sure we get X or Y + if (handledNodeCount > 0) {pos = this._getPositionForHierarchy(nodeArray[i-1]) + this.options.hierarchical.nodeSpacing;} + this._setPositionForHierarchy(node, pos, level); + this.positionedNodes[node.id] = true; this._placeBranchNodes(node.id, level); + + handledNodeCount++; } } } @@ -1047,14 +1071,14 @@ class LayoutEngine { if (this.hierarchicalLevels[childNode.id] > this.hierarchicalLevels[parentNode.id]) { let parentNodeId = parentNode.id; let childNodeId = childNode.id; - if (this.hierarchicalParents[parentNodeId] === undefined) { - this.hierarchicalParents[parentNodeId] = {children: [], amount: 0}; + if (this.hierarchicalChildrenReference[parentNodeId] === undefined) { + this.hierarchicalChildrenReference[parentNodeId] = []; } - this.hierarchicalParents[parentNodeId].children.push(childNodeId); - if (this.hierarchicalChildren[childNodeId] === undefined) { - this.hierarchicalChildren[childNodeId] = {parents: [], amount: 0}; + this.hierarchicalChildrenReference[parentNodeId].push(childNodeId); + if (this.hierarchicalParentReference[childNodeId] === undefined) { + this.hierarchicalParentReference[childNodeId] = []; } - this.hierarchicalChildren[childNodeId].parents.push(parentNodeId); + this.hierarchicalParentReference[childNodeId].push(parentNodeId); } }; @@ -1070,8 +1094,16 @@ class LayoutEngine { */ _crawlNetwork(callback = function() {}, startingNodeId) { let progress = {}; - let crawler = (node) => { + let treeIndex = 0; + + let crawler = (node, tree) => { if (progress[node.id] === undefined) { + + if (this.hierarchicalTrees[node.id] === undefined) { + this.hierarchicalTrees[node.id] = tree; + this.treeIndex = Math.max(tree, this.treeIndex); + } + progress[node.id] = true; let childNode; for (let i = 0; i < node.edges.length; i++) { @@ -1085,7 +1117,7 @@ class LayoutEngine { if (node.id !== childNode.id) { callback(node, childNode, node.edges[i]); - crawler(childNode); + crawler(childNode, tree); } } } @@ -1097,7 +1129,10 @@ class LayoutEngine { if (startingNodeId === undefined) { for (let i = 0; i < this.body.nodeIndices.length; i++) { let node = this.body.nodes[this.body.nodeIndices[i]]; - crawler(node); + if (progress[node.id] === undefined) { + crawler(node, treeIndex); + treeIndex += 1; + } } } else { @@ -1121,14 +1156,14 @@ class LayoutEngine { */ _placeBranchNodes(parentId, parentLevel) { // if this is not a parent, cancel the placing. This can happen with multiple parents to one child. - if (this.hierarchicalParents[parentId] === undefined) { + if (this.hierarchicalChildrenReference[parentId] === undefined) { return; } // get a list of childNodes let childNodes = []; - for (let i = 0; i < this.hierarchicalParents[parentId].children.length; i++) { - childNodes.push(this.body.nodes[this.hierarchicalParents[parentId].children[i]]); + for (let i = 0; i < this.hierarchicalChildrenReference[parentId].length; i++) { + childNodes.push(this.body.nodes[this.hierarchicalChildrenReference[parentId][i]]); } // use the positions to order the nodes. @@ -1182,6 +1217,8 @@ class LayoutEngine { } + + /** * Shift a branch a certain distance * @param parentId @@ -1195,9 +1232,9 @@ class LayoutEngine { else { this.body.nodes[parentId].y += diff; } - if (this.hierarchicalParents[parentId] !== undefined) { - for (let i = 0; i < this.hierarchicalParents[parentId].children.length; i++) { - this._shiftBlock(this.hierarchicalParents[parentId].children[i], diff); + if (this.hierarchicalChildrenReference[parentId] !== undefined) { + for (let i = 0; i < this.hierarchicalChildrenReference[parentId].length; i++) { + this._shiftBlock(this.hierarchicalChildrenReference[parentId][i], diff); } } } @@ -1213,18 +1250,18 @@ class LayoutEngine { _findCommonParent(childA,childB) { let parents = {}; let iterateParents = (parents,child) => { - if (this.hierarchicalChildren[child] !== undefined) { - for (let i = 0; i < this.hierarchicalChildren[child].parents.length; i++) { - let parent = this.hierarchicalChildren[child].parents[i]; + if (this.hierarchicalParentReference[child] !== undefined) { + for (let i = 0; i < this.hierarchicalParentReference[child].length; i++) { + let parent = this.hierarchicalParentReference[child][i]; parents[parent] = true; iterateParents(parents, parent) } } }; let findParent = (parents, child) => { - if (this.hierarchicalChildren[child] !== undefined) { - for (let i = 0; i < this.hierarchicalChildren[child].parents.length; i++) { - let parent = this.hierarchicalChildren[child].parents[i]; + if (this.hierarchicalParentReference[child] !== undefined) { + for (let i = 0; i < this.hierarchicalParentReference[child].length; i++) { + let parent = this.hierarchicalParentReference[child][i]; if (parents[parent] !== undefined) { return {foundParent:parent, withChild:child}; } @@ -1260,28 +1297,6 @@ class LayoutEngine { this.distributionIndex[node.id] = this.distributionOrdering[level].length - 1; } this.distributionOrderingPresence[level][node.id] = true; - - if (this.hierarchicalTrees[node.id] === undefined) { - if (this.hierarchicalChildren[node.id] !== undefined) { - let tree = 1; - // get the lowest tree denominator. - for (let i = 0; i < this.hierarchicalChildren[node.id].parents.length; i++) { - let parentId = this.hierarchicalChildren[node.id].parents[i]; - if (this.hierarchicalTrees[parentId] !== undefined) { - //tree = Math.min(tree,this.hierarchicalTrees[parentId]); - tree = this.hierarchicalTrees[parentId]; - } - } - //for (let i = 0; i < this.hierarchicalChildren.parents.length; i++) { - // let parentId = this.hierarchicalChildren.parents[i]; - // this.hierarchicalTrees[parentId] = tree; - //} - this.hierarchicalTrees[node.id] = tree; - } - else { - this.hierarchicalTrees[node.id] = ++this.treeIndex; - } - } } if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') { diff --git a/lib/network/options.js b/lib/network/options.js index 59904163..91bb6b16 100644 --- a/lib/network/options.js +++ b/lib/network/options.js @@ -127,6 +127,7 @@ let allOptions = { treeSpacing: { number }, blockShifting: { boolean }, edgeMinimization: { boolean }, + parentCentralization: { boolean }, direction: { string: ['UD', 'DU', 'LR', 'RL'] }, // UD, DU, LR, RL sortMethod: { string: ['hubsize', 'directed'] }, // hubsize, directed __type__: { object, boolean } @@ -431,6 +432,7 @@ let configureOptions = { treeSpacing: [200, 20, 500, 5], blockShifting: true, edgeMinimization: true, + parentCentralization: true, direction: ['UD', 'DU', 'LR', 'RL'], // UD, DU, LR, RL sortMethod: ['hubsize', 'directed'] // hubsize, directed } diff --git a/test/networkTest.html b/test/networkTest.html index d1422420..60fcb6e7 100644 --- a/test/networkTest.html +++ b/test/networkTest.html @@ -18,13140 +18,100 @@