Browse Source

- Fixed #1644, #1631: overlapping nodes in hierarchical layout should no longer occur.

- Added parentCentralization option for hierarchical layout.
codeClimate
Alex de Mulder 8 years ago
parent
commit
2bff294789
7 changed files with 263 additions and 13258 deletions
  1. +2
    -0
      HISTORY.md
  2. +89
    -69
      dist/vis.js
  3. +2
    -0
      docs/network/layout.html
  4. +4
    -0
      examples/network/layout/hierarchicalLayoutWithoutPhysics.html
  5. +80
    -65
      lib/network/modules/LayoutEngine.js
  6. +2
    -0
      lib/network/options.js
  7. +84
    -13124
      test/networkTest.html

+ 2
- 0
HISTORY.md View File

@ -20,6 +20,8 @@ http://visjs.org
### Network ### Network
- Fixed #1635: edges are now referring to the correct points. - 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 ## 2016-02-04, version 4.14.0

+ 89
- 69
dist/vis.js View File

@ -5,7 +5,7 @@
* A dynamic, browser-based visualization library. * A dynamic, browser-based visualization library.
* *
* @version 4.14.0 * @version 4.14.0
* @date 2016-02-12
* @date 2016-02-13
* *
* @license * @license
* Copyright (C) 2011-2016 Almende B.V, http://almende.com * Copyright (C) 2011-2016 Almende B.V, http://almende.com
@ -1582,7 +1582,7 @@ return /******/ (function(modules) { // webpackBootstrap
/***/ function(module, exports, __webpack_require__) { /***/ function(module, exports, __webpack_require__) {
/* WEBPACK VAR INJECTION */(function(module) {//! moment.js /* WEBPACK VAR INJECTION */(function(module) {//! moment.js
//! version : 2.11.1
//! version : 2.11.2
//! authors : Tim Wood, Iskren Chernev, Moment.js contributors //! authors : Tim Wood, Iskren Chernev, Moment.js contributors
//! license : MIT //! license : MIT
//! momentjs.com //! momentjs.com
@ -3399,7 +3399,7 @@ return /******/ (function(modules) { // webpackBootstrap
} }
// ASP.NET json date format regex // 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 // 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 // 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 // Side effect imports
utils_hooks__hooks.version = '2.11.1';
utils_hooks__hooks.version = '2.11.2';
setHookCallback(local__createLocal); setHookCallback(local__createLocal);
@ -40083,6 +40083,7 @@ return /******/ (function(modules) { // webpackBootstrap
treeSpacing: 200, treeSpacing: 200,
blockShifting: true, blockShifting: true,
edgeMinimization: true, edgeMinimization: true,
parentCentralization: true,
direction: 'UD', // UD, DU, LR, RL direction: 'UD', // UD, DU, LR, RL
sortMethod: 'hubsize' // hubsize, directed sortMethod: 'hubsize' // hubsize, directed
} }
@ -40364,8 +40365,8 @@ return /******/ (function(modules) { // webpackBootstrap
var undefinedLevel = false; var undefinedLevel = false;
this.hierarchicalLevels = {}; this.hierarchicalLevels = {};
this.lastNodeOnLevel = {}; this.lastNodeOnLevel = {};
this.hierarchicalParents = {};
this.hierarchicalChildren = {};
this.hierarchicalChildrenReference = {};
this.hierarchicalParentReference = {};
this.hierarchicalTrees = {}; this.hierarchicalTrees = {};
this.treeIndex = -1; this.treeIndex = -1;
@ -40447,9 +40448,7 @@ return /******/ (function(modules) { // webpackBootstrap
var treeSizes = getTreeSizes(); var treeSizes = getTreeSizes();
for (var i = 0; i < treeSizes.length - 1; i++) { for (var i = 0; i < treeSizes.length - 1; i++) {
var diff = treeSizes[i].max - treeSizes[i + 1].min; 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) { for (var nodeId in _this2.hierarchicalTrees) {
if (_this2.hierarchicalTrees.hasOwnProperty(nodeId)) { if (_this2.hierarchicalTrees.hasOwnProperty(nodeId)) {
if (_this2.hierarchicalTrees[nodeId] === index) { 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 // get the width of all trees
var getTreeSizes = function getTreeSizes() { var getTreeSizes = function getTreeSizes() {
var treeWidths = []; var treeWidths = [];
for (var i = 0; i < _this2.treeIndex; i++) {
for (var i = 0; i <= _this2.treeIndex; i++) {
treeWidths.push(getTreeSize(i)); treeWidths.push(getTreeSize(i));
} }
return treeWidths; return treeWidths;
@ -40492,8 +40493,8 @@ return /******/ (function(modules) { // webpackBootstrap
// get a map of all nodes in this branch // get a map of all nodes in this branch
var getBranchNodes = function getBranchNodes(source, map) { var getBranchNodes = function getBranchNodes(source, map) {
map[source.id] = true; 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) { if (children.length > 0) {
for (var i = 0; i < children.length; i++) { for (var i = 0; i < children.length; i++) {
getBranchNodes(_this2.body.nodes[children[i]], map); getBranchNodes(_this2.body.nodes[children[i]], map);
@ -40543,8 +40544,8 @@ return /******/ (function(modules) { // webpackBootstrap
// get the maximum level of a branch. // get the maximum level of a branch.
var getMaxLevel = function getMaxLevel(nodeId) { var getMaxLevel = function getMaxLevel(nodeId) {
var level = _this2.hierarchicalLevels[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) { if (children.length > 0) {
for (var i = 0; i < children.length; i++) { for (var i = 0; i < children.length; i++) {
level = Math.max(level, getMaxLevel(children[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) // check if two nodes have the same parent(s)
var hasSameParent = function hasSameParent(node1, node2) { 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) { if (parents1 === undefined || parents2 === undefined) {
return false; return false;
} }
parents1 = parents1.parents;
parents2 = parents2.parents;
for (var i = 0; i < parents1.length; i++) { for (var i = 0; i < parents1.length; i++) {
for (var j = 0; j < parents2.length; j++) { for (var j = 0; j < parents2.length; j++) {
if (parents1[i] == parents2[j]) { if (parents1[i] == parents2[j]) {
@ -40782,7 +40782,7 @@ return /******/ (function(modules) { // webpackBootstrap
} }
if (newPosition !== nodePosition) { if (newPosition !== nodePosition) {
//console.log("moving Node:",diff, minSpace, maxSpace)
//console.log("moving Node:",diff, minSpace, maxSpace);
_this2._setPositionForHierarchy(node, newPosition, undefined, true); _this2._setPositionForHierarchy(node, newPosition, undefined, true);
//this.body.emitter.emit("_redraw"); //this.body.emitter.emit("_redraw");
stillShifting = true; 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 shiftBranchesCloserBottomUp = function shiftBranchesCloserBottomUp(iterations) {
var levels = Object.keys(_this2.distributionOrdering); var levels = Object.keys(_this2.distributionOrdering);
levels = levels.reverse(); 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. // the actual work is done here.
if (this.options.hierarchical.blockShifting === true) { if (this.options.hierarchical.blockShifting === true) {
shiftBranchesCloserBottomUp(5); shiftBranchesCloserBottomUp(5);
@ -40848,6 +40861,10 @@ return /******/ (function(modules) { // webpackBootstrap
minimizeEdgeLengthBottomUp(20); minimizeEdgeLengthBottomUp(20);
} }
if (this.options.hierarchical.parentCentralization === true) {
centerAllParentsBottomUp();
}
shiftTrees(); shiftTrees();
} }
@ -40902,16 +40919,16 @@ return /******/ (function(modules) { // webpackBootstrap
}, { }, {
key: '_centerParent', key: '_centerParent',
value: function _centerParent(node) { 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++) { for (var i = 0; i < parents.length; i++) {
var parentId = parents[i]; var parentId = parents[i];
var parentNode = this.body.nodes[parentId]; var parentNode = this.body.nodes[parentId];
if (this.hierarchicalParents[parentId]) {
if (this.hierarchicalChildrenReference[parentId]) {
// get the range of the children // get the range of the children
var minPos = 1e9; var minPos = 1e9;
var maxPos = -1e9; var maxPos = -1e9;
var children = this.hierarchicalParents[parentId].children;
var children = this.hierarchicalChildrenReference[parentId];
if (children.length > 0) { if (children.length > 0) {
for (var _i = 0; _i < children.length; _i++) { for (var _i = 0; _i < children.length; _i++) {
var childNode = this.body.nodes[children[_i]]; var childNode = this.body.nodes[children[_i]];
@ -40956,13 +40973,22 @@ return /******/ (function(modules) { // webpackBootstrap
var nodeArray = Object.keys(distribution[level]); var nodeArray = Object.keys(distribution[level]);
nodeArray = this._indexArrayToNodes(nodeArray); nodeArray = this._indexArrayToNodes(nodeArray);
this._sortNodeArray(nodeArray); this._sortNodeArray(nodeArray);
var handledNodeCount = 0;
for (var i = 0; i < nodeArray.length; i++) { for (var i = 0; i < nodeArray.length; i++) {
var node = nodeArray[i]; var node = nodeArray[i];
if (this.positionedNodes[node.id] === undefined) { 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.positionedNodes[node.id] = true;
this._placeBranchNodes(node.id, level); this._placeBranchNodes(node.id, level);
handledNodeCount++;
} }
} }
} }
@ -41178,14 +41204,14 @@ return /******/ (function(modules) { // webpackBootstrap
if (_this6.hierarchicalLevels[childNode.id] > _this6.hierarchicalLevels[parentNode.id]) { if (_this6.hierarchicalLevels[childNode.id] > _this6.hierarchicalLevels[parentNode.id]) {
var parentNodeId = parentNode.id; var parentNodeId = parentNode.id;
var childNodeId = childNode.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', key: '_crawlNetwork',
value: function _crawlNetwork(callback, startingNodeId) { value: function _crawlNetwork(callback, startingNodeId) {
var _this7 = this;
if (callback === undefined) callback = function () {}; if (callback === undefined) callback = function () {};
var progress = {}; var progress = {};
var crawler = function crawler(node) {
var treeIndex = 0;
var crawler = function crawler(node, tree) {
if (progress[node.id] === undefined) { 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; progress[node.id] = true;
var childNode = undefined; var childNode = undefined;
for (var i = 0; i < node.edges.length; i++) { for (var i = 0; i < node.edges.length; i++) {
@ -41218,7 +41254,7 @@ return /******/ (function(modules) { // webpackBootstrap
if (node.id !== childNode.id) { if (node.id !== childNode.id) {
callback(node, childNode, node.edges[i]); callback(node, childNode, node.edges[i]);
crawler(childNode);
crawler(childNode, tree);
} }
} }
} }
@ -41229,7 +41265,10 @@ return /******/ (function(modules) { // webpackBootstrap
if (startingNodeId === undefined) { if (startingNodeId === undefined) {
for (var i = 0; i < this.body.nodeIndices.length; i++) { for (var i = 0; i < this.body.nodeIndices.length; i++) {
var node = this.body.nodes[this.body.nodeIndices[i]]; var node = this.body.nodes[this.body.nodeIndices[i]];
crawler(node);
if (progress[node.id] === undefined) {
crawler(node, treeIndex);
treeIndex += 1;
}
} }
} else { } else {
var node = this.body.nodes[startingNodeId]; var node = this.body.nodes[startingNodeId];
@ -41253,14 +41292,14 @@ return /******/ (function(modules) { // webpackBootstrap
key: '_placeBranchNodes', key: '_placeBranchNodes',
value: function _placeBranchNodes(parentId, parentLevel) { 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 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; return;
} }
// get a list of childNodes // get a list of childNodes
var 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. // use the positions to order the nodes.
@ -41329,9 +41368,9 @@ return /******/ (function(modules) { // webpackBootstrap
} else { } else {
this.body.nodes[parentId].y += diff; 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', key: '_findCommonParent',
value: function _findCommonParent(childA, childB) { value: function _findCommonParent(childA, childB) {
var _this7 = this;
var _this8 = this;
var parents = {}; var parents = {};
var iterateParents = function iterateParents(parents, child) { 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; parents[_parent] = true;
iterateParents(parents, _parent); iterateParents(parents, _parent);
} }
} }
}; };
var findParent = function findParent(parents, child) { 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) { if (parents[_parent2] !== undefined) {
return { foundParent: _parent2, withChild: child }; return { foundParent: _parent2, withChild: child };
} }
@ -41401,27 +41440,6 @@ return /******/ (function(modules) { // webpackBootstrap
this.distributionIndex[node.id] = this.distributionOrdering[level].length - 1; this.distributionIndex[node.id] = this.distributionOrdering[level].length - 1;
} }
this.distributionOrderingPresence[level][node.id] = true; 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') { if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') {
@ -42829,6 +42847,7 @@ return /******/ (function(modules) { // webpackBootstrap
treeSpacing: { number: number }, treeSpacing: { number: number },
blockShifting: { boolean: boolean }, blockShifting: { boolean: boolean },
edgeMinimization: { boolean: boolean }, edgeMinimization: { boolean: boolean },
parentCentralization: { boolean: boolean },
direction: { string: ['UD', 'DU', 'LR', 'RL'] }, // UD, DU, LR, RL direction: { string: ['UD', 'DU', 'LR', 'RL'] }, // UD, DU, LR, RL
sortMethod: { string: ['hubsize', 'directed'] }, // hubsize, directed sortMethod: { string: ['hubsize', 'directed'] }, // hubsize, directed
__type__: { object: object, boolean: boolean } __type__: { object: object, boolean: boolean }
@ -43132,6 +43151,7 @@ return /******/ (function(modules) { // webpackBootstrap
treeSpacing: [200, 20, 500, 5], treeSpacing: [200, 20, 500, 5],
blockShifting: true, blockShifting: true,
edgeMinimization: true, edgeMinimization: true,
parentCentralization: true,
direction: ['UD', 'DU', 'LR', 'RL'], // UD, DU, LR, RL direction: ['UD', 'DU', 'LR', 'RL'], // UD, DU, LR, RL
sortMethod: ['hubsize', 'directed'] // hubsize, directed sortMethod: ['hubsize', 'directed'] // hubsize, directed
} }

+ 2
- 0
docs/network/layout.html View File

@ -109,6 +109,7 @@ var options = {
treeSpacing: 200, treeSpacing: 200,
blockShifting: true, blockShifting: true,
edgeMinimization: true, edgeMinimization: true,
parentCentralization: true,
direction: 'UD', // UD, DU, LR, RL direction: 'UD', // UD, DU, LR, RL
sortMethod: 'hubsize' // hubsize, directed 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!</td></tr> 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!</td></tr>
<tr parent="hierarchical" class="hidden"><td class="indent">hierarchical.edgeMinimization</td><td>Boolean</td><td><code>true</code></td> <td>Method for reducing whitespace. Can be used alone or together with block shifting. Enabling block shifting will usually speed up the layout process. <tr parent="hierarchical" class="hidden"><td class="indent">hierarchical.edgeMinimization</td><td>Boolean</td><td><code>true</code></td> <td>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!</td></tr> 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!</td></tr>
<tr parent="hierarchical" class="hidden"><td class="indent">hierarchical.parentCentralization</td><td>Boolean</td><td><code>true</code></td> <td>When true, the parents nodes will be centered again after the the layout algorithm has been finished.</td></tr>
<tr parent="hierarchical" class="hidden"><td class="indent">hierarchical.direction</td><td>String</td><td><code>'UD'</code></td> <td>The direction of the hierarchical layout. The available options are: <code>UD, DU, LR, RL</code>. To simplify: up-down, down-up, left-right, right-left.</td></tr> <tr parent="hierarchical" class="hidden"><td class="indent">hierarchical.direction</td><td>String</td><td><code>'UD'</code></td> <td>The direction of the hierarchical layout. The available options are: <code>UD, DU, LR, RL</code>. To simplify: up-down, down-up, left-right, right-left.</td></tr>
<tr parent="hierarchical" class="hidden"><td class="indent">hierarchical.sortMethod</td><td>String</td><td><code>'hubsize'</code></td> <td>The algorithm used to ascertain the levels of the nodes based on the data. The possible options are: <code>hubsize, directed</code>. <br><br> <tr parent="hierarchical" class="hidden"><td class="indent">hierarchical.sortMethod</td><td>String</td><td><code>'hubsize'</code></td> <td>The algorithm used to ascertain the levels of the nodes based on the data. The possible options are: <code>hubsize, directed</code>. <br><br>
Hubsize takes the nodes with the most edges and puts them at the top. From that the rest of the hierarchy is evaluated. <br><br> Hubsize takes the nodes with the most edges and puts them at the top. From that the rest of the hierarchy is evaluated. <br><br>

+ 4
- 0
examples/network/layout/hierarchicalLayoutWithoutPhysics.html View File

@ -47,6 +47,10 @@ The hierarchical layout can now be controlled without the use of physics. This i
<td>Method for reducing whitespace. Can be used alone or together with block shifting. Enabling block shifting will usually speed up the layout process. <td>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.</td> Each node will try to move along its free axis to reduce the total length of it's edges.</td>
</tr> </tr>
<tr>
<td><code>parentCentralization</code></td>
<td>When true, the parents nodes will be centered again after the the layout algorithm has been finished.</td>
</tr>
</table> </table>
<br /><br /> <br /><br />
Play with the settings below the network and see how the layout changes! Play with the settings below the network and see how the layout changes!

+ 80
- 65
lib/network/modules/LayoutEngine.js View File

@ -23,6 +23,7 @@ class LayoutEngine {
treeSpacing: 200, treeSpacing: 200,
blockShifting: true, blockShifting: true,
edgeMinimization: true, edgeMinimization: true,
parentCentralization: true,
direction: 'UD', // UD, DU, LR, RL direction: 'UD', // UD, DU, LR, RL
sortMethod: 'hubsize' // hubsize, directed sortMethod: 'hubsize' // hubsize, directed
} }
@ -294,8 +295,8 @@ class LayoutEngine {
let undefinedLevel = false; let undefinedLevel = false;
this.hierarchicalLevels = {}; this.hierarchicalLevels = {};
this.lastNodeOnLevel = {}; this.lastNodeOnLevel = {};
this.hierarchicalParents = {};
this.hierarchicalChildren = {};
this.hierarchicalChildrenReference = {};
this.hierarchicalParentReference = {};
this.hierarchicalTrees = {}; this.hierarchicalTrees = {};
this.treeIndex = -1; this.treeIndex = -1;
@ -379,9 +380,7 @@ class LayoutEngine {
let treeSizes = getTreeSizes(); let treeSizes = getTreeSizes();
for (let i = 0; i < treeSizes.length - 1; i++) { for (let i = 0; i < treeSizes.length - 1; i++) {
let diff = treeSizes[i].max - treeSizes[i+1].min; 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) { for (let nodeId in this.hierarchicalTrees) {
if (this.hierarchicalTrees.hasOwnProperty(nodeId)) { if (this.hierarchicalTrees.hasOwnProperty(nodeId)) {
if (this.hierarchicalTrees[nodeId] === index) { 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 // get the width of all trees
let getTreeSizes = () => { let getTreeSizes = () => {
let treeWidths = []; let treeWidths = [];
for (let i = 0; i < this.treeIndex; i++) {
for (let i = 0; i <= this.treeIndex; i++) {
treeWidths.push(getTreeSize(i)); treeWidths.push(getTreeSize(i));
} }
return treeWidths; return treeWidths;
@ -425,8 +426,8 @@ class LayoutEngine {
// get a map of all nodes in this branch // get a map of all nodes in this branch
let getBranchNodes = (source, map) => { let getBranchNodes = (source, map) => {
map[source.id] = true; 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) { if (children.length > 0) {
for (let i = 0; i < children.length; i++) { for (let i = 0; i < children.length; i++) {
getBranchNodes(this.body.nodes[children[i]], map); getBranchNodes(this.body.nodes[children[i]], map);
@ -467,8 +468,8 @@ class LayoutEngine {
// get the maximum level of a branch. // get the maximum level of a branch.
let getMaxLevel = (nodeId) => { let getMaxLevel = (nodeId) => {
let level = this.hierarchicalLevels[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) { if (children.length > 0) {
for (let i = 0; i < children.length; i++) { for (let i = 0; i < children.length; i++) {
level = Math.max(level,getMaxLevel(children[i])); level = Math.max(level,getMaxLevel(children[i]));
@ -487,13 +488,12 @@ class LayoutEngine {
// check if two nodes have the same parent(s) // check if two nodes have the same parent(s)
let hasSameParent = (node1, node2) => { 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) { if (parents1 === undefined || parents2 === undefined) {
return false; return false;
} }
parents1 = parents1.parents;
parents2 = parents2.parents;
for (let i = 0; i < parents1.length; i++) { for (let i = 0; i < parents1.length; i++) {
for (let j = 0; j < parents2.length; j++) { for (let j = 0; j < parents2.length; j++) {
if (parents1[i] == parents2[j]) { if (parents1[i] == parents2[j]) {
@ -676,7 +676,7 @@ class LayoutEngine {
} }
if (newPosition !== nodePosition) { if (newPosition !== nodePosition) {
//console.log("moving Node:",diff, minSpace, maxSpace)
//console.log("moving Node:",diff, minSpace, maxSpace);
this._setPositionForHierarchy(node, newPosition, undefined, true); this._setPositionForHierarchy(node, newPosition, undefined, true);
//this.body.emitter.emit("_redraw"); //this.body.emitter.emit("_redraw");
stillShifting = true; 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 shiftBranchesCloserBottomUp = (iterations) => {
let levels = Object.keys(this.distributionOrdering); let levels = Object.keys(this.distributionOrdering);
levels = levels.reverse(); 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. // the actual work is done here.
if (this.options.hierarchical.blockShifting === true) { if (this.options.hierarchical.blockShifting === true) {
shiftBranchesCloserBottomUp(5); shiftBranchesCloserBottomUp(5);
@ -743,6 +756,10 @@ class LayoutEngine {
minimizeEdgeLengthBottomUp(20); minimizeEdgeLengthBottomUp(20);
} }
if (this.options.hierarchical.parentCentralization === true) {
centerAllParentsBottomUp()
}
shiftTrees(); shiftTrees();
} }
@ -794,16 +811,16 @@ class LayoutEngine {
* @private * @private
*/ */
_centerParent(node) { _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++) { for (var i = 0; i < parents.length; i++) {
let parentId = parents[i]; let parentId = parents[i];
let parentNode = this.body.nodes[parentId]; let parentNode = this.body.nodes[parentId];
if (this.hierarchicalParents[parentId]) {
if (this.hierarchicalChildrenReference[parentId]) {
// get the range of the children // get the range of the children
let minPos = 1e9; let minPos = 1e9;
let maxPos = -1e9; let maxPos = -1e9;
let children = this.hierarchicalParents[parentId].children;
let children = this.hierarchicalChildrenReference[parentId];
if (children.length > 0) { if (children.length > 0) {
for (let i = 0; i < children.length; i++) { for (let i = 0; i < children.length; i++) {
let childNode = this.body.nodes[children[i]]; let childNode = this.body.nodes[children[i]];
@ -841,13 +858,20 @@ class LayoutEngine {
let nodeArray = Object.keys(distribution[level]); let nodeArray = Object.keys(distribution[level]);
nodeArray = this._indexArrayToNodes(nodeArray); nodeArray = this._indexArrayToNodes(nodeArray);
this._sortNodeArray(nodeArray); this._sortNodeArray(nodeArray);
let handledNodeCount = 0;
for (let i = 0; i < nodeArray.length; i++) { for (let i = 0; i < nodeArray.length; i++) {
let node = nodeArray[i]; let node = nodeArray[i];
if (this.positionedNodes[node.id] === undefined) { 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.positionedNodes[node.id] = true;
this._placeBranchNodes(node.id, level); this._placeBranchNodes(node.id, level);
handledNodeCount++;
} }
} }
} }
@ -1047,14 +1071,14 @@ class LayoutEngine {
if (this.hierarchicalLevels[childNode.id] > this.hierarchicalLevels[parentNode.id]) { if (this.hierarchicalLevels[childNode.id] > this.hierarchicalLevels[parentNode.id]) {
let parentNodeId = parentNode.id; let parentNodeId = parentNode.id;
let childNodeId = childNode.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) { _crawlNetwork(callback = function() {}, startingNodeId) {
let progress = {}; let progress = {};
let crawler = (node) => {
let treeIndex = 0;
let crawler = (node, tree) => {
if (progress[node.id] === undefined) { 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; progress[node.id] = true;
let childNode; let childNode;
for (let i = 0; i < node.edges.length; i++) { for (let i = 0; i < node.edges.length; i++) {
@ -1085,7 +1117,7 @@ class LayoutEngine {
if (node.id !== childNode.id) { if (node.id !== childNode.id) {
callback(node, childNode, node.edges[i]); callback(node, childNode, node.edges[i]);
crawler(childNode);
crawler(childNode, tree);
} }
} }
} }
@ -1097,7 +1129,10 @@ class LayoutEngine {
if (startingNodeId === undefined) { if (startingNodeId === undefined) {
for (let i = 0; i < this.body.nodeIndices.length; i++) { for (let i = 0; i < this.body.nodeIndices.length; i++) {
let node = this.body.nodes[this.body.nodeIndices[i]]; let node = this.body.nodes[this.body.nodeIndices[i]];
crawler(node);
if (progress[node.id] === undefined) {
crawler(node, treeIndex);
treeIndex += 1;
}
} }
} }
else { else {
@ -1121,14 +1156,14 @@ class LayoutEngine {
*/ */
_placeBranchNodes(parentId, parentLevel) { _placeBranchNodes(parentId, parentLevel) {
// if this is not a parent, cancel the placing. This can happen with multiple parents to one child. // 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; return;
} }
// get a list of childNodes // get a list of childNodes
let 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. // use the positions to order the nodes.
@ -1182,6 +1217,8 @@ class LayoutEngine {
} }
/** /**
* Shift a branch a certain distance * Shift a branch a certain distance
* @param parentId * @param parentId
@ -1195,9 +1232,9 @@ class LayoutEngine {
else { else {
this.body.nodes[parentId].y += diff; 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) { _findCommonParent(childA,childB) {
let parents = {}; let parents = {};
let iterateParents = (parents,child) => { 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; parents[parent] = true;
iterateParents(parents, parent) iterateParents(parents, parent)
} }
} }
}; };
let findParent = (parents, child) => { 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) { if (parents[parent] !== undefined) {
return {foundParent:parent, withChild:child}; return {foundParent:parent, withChild:child};
} }
@ -1260,28 +1297,6 @@ class LayoutEngine {
this.distributionIndex[node.id] = this.distributionOrdering[level].length - 1; this.distributionIndex[node.id] = this.distributionOrdering[level].length - 1;
} }
this.distributionOrderingPresence[level][node.id] = true; 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') { if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') {

+ 2
- 0
lib/network/options.js View File

@ -127,6 +127,7 @@ let allOptions = {
treeSpacing: { number }, treeSpacing: { number },
blockShifting: { boolean }, blockShifting: { boolean },
edgeMinimization: { boolean }, edgeMinimization: { boolean },
parentCentralization: { boolean },
direction: { string: ['UD', 'DU', 'LR', 'RL'] }, // UD, DU, LR, RL direction: { string: ['UD', 'DU', 'LR', 'RL'] }, // UD, DU, LR, RL
sortMethod: { string: ['hubsize', 'directed'] }, // hubsize, directed sortMethod: { string: ['hubsize', 'directed'] }, // hubsize, directed
__type__: { object, boolean } __type__: { object, boolean }
@ -431,6 +432,7 @@ let configureOptions = {
treeSpacing: [200, 20, 500, 5], treeSpacing: [200, 20, 500, 5],
blockShifting: true, blockShifting: true,
edgeMinimization: true, edgeMinimization: true,
parentCentralization: true,
direction: ['UD', 'DU', 'LR', 'RL'], // UD, DU, LR, RL direction: ['UD', 'DU', 'LR', 'RL'], // UD, DU, LR, RL
sortMethod: ['hubsize', 'directed'] // hubsize, directed sortMethod: ['hubsize', 'directed'] // hubsize, directed
} }

+ 84
- 13124
test/networkTest.html
File diff suppressed because it is too large
View File


Loading…
Cancel
Save