|
|
@ -3,6 +3,171 @@ |
|
|
|
let util = require('../../util'); |
|
|
|
import NetworkUtil from '../NetworkUtil'; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Container for derived data on current network, relating to hierarchy. |
|
|
|
* |
|
|
|
* Local, private class. |
|
|
|
* |
|
|
|
* TODO: Perhaps move more code for hierarchy state handling to this class. |
|
|
|
* Till now, only the required and most obvious has been done. |
|
|
|
*/ |
|
|
|
class HierarchicalStatus { |
|
|
|
|
|
|
|
constructor() { |
|
|
|
this.childrenReference = {}; |
|
|
|
this.parentReference = {}; |
|
|
|
this.levels = {}; |
|
|
|
this.trees = {}; |
|
|
|
|
|
|
|
this.isTree = false; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Add the relation between given nodes to the current state. |
|
|
|
*/ |
|
|
|
addRelation(parentNodeId, childNodeId) { |
|
|
|
if (this.childrenReference[parentNodeId] === undefined) { |
|
|
|
this.childrenReference[parentNodeId] = []; |
|
|
|
} |
|
|
|
this.childrenReference[parentNodeId].push(childNodeId); |
|
|
|
|
|
|
|
if (this.parentReference[childNodeId] === undefined) { |
|
|
|
this.parentReference[childNodeId] = []; |
|
|
|
} |
|
|
|
this.parentReference[childNodeId].push(parentNodeId); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Check if the current state is for a tree or forest network. |
|
|
|
* |
|
|
|
* This is the case if every node has at most one parent. |
|
|
|
* |
|
|
|
* Pre: parentReference init'ed properly for current network |
|
|
|
*/ |
|
|
|
checkIfTree() { |
|
|
|
for (let i in this.parentReference) { |
|
|
|
if (this.parentReference[i].length > 1) { |
|
|
|
this.isTree = false; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
this.isTree = true; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Ensure level for given id is defined. |
|
|
|
* |
|
|
|
* Sets level to zero for given node id if not already present |
|
|
|
*/ |
|
|
|
ensureLevel(nodeId) { |
|
|
|
if (this.levels[nodeId] === undefined) { |
|
|
|
this.levels[nodeId] = 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* get the maximum level of a branch. |
|
|
|
* |
|
|
|
* TODO: Never entered; find a test case to test this! |
|
|
|
*/ |
|
|
|
getMaxLevel(nodeId) { |
|
|
|
let accumulator = {}; |
|
|
|
|
|
|
|
let _getMaxLevel = (nodeId) => { |
|
|
|
if (accumulator[nodeId] !== undefined) { |
|
|
|
return accumulator[nodeId]; |
|
|
|
} |
|
|
|
let level = this.levels[nodeId]; |
|
|
|
if (this.childrenReference[nodeId]) { |
|
|
|
let children = this.childrenReference[nodeId]; |
|
|
|
if (children.length > 0) { |
|
|
|
for (let i = 0; i < children.length; i++) { |
|
|
|
level = Math.max(level,_getMaxLevel(children[i])); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
accumulator[nodeId] = level; |
|
|
|
return level; |
|
|
|
}; |
|
|
|
|
|
|
|
return _getMaxLevel(nodeId); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
levelDownstream(nodeA, nodeB) { |
|
|
|
if (this.levels[nodeB.id] === undefined) { |
|
|
|
// set initial level
|
|
|
|
if (this.levels[nodeA.id] === undefined) { |
|
|
|
this.levels[nodeA.id] = 0; |
|
|
|
} |
|
|
|
// set level
|
|
|
|
this.levels[nodeB.id] = this.levels[nodeA.id] + 1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Small util method to set the minimum levels of the nodes to zero. |
|
|
|
*/ |
|
|
|
setMinLevelToZero(nodes) { |
|
|
|
let minLevel = 1e9; |
|
|
|
// get the minimum level
|
|
|
|
for (let nodeId in nodes) { |
|
|
|
if (nodes.hasOwnProperty(nodeId)) { |
|
|
|
if (this.levels[nodeId] !== undefined) { |
|
|
|
minLevel = Math.min(this.levels[nodeId], minLevel); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// subtract the minimum from the set so we have a range starting from 0
|
|
|
|
for (let nodeId in nodes) { |
|
|
|
if (nodes.hasOwnProperty(nodeId)) { |
|
|
|
if (this.levels[nodeId] !== undefined) { |
|
|
|
this.levels[nodeId] -= minLevel; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Get the min and max xy-coordinates of a given tree |
|
|
|
*/ |
|
|
|
getTreeSize(nodes, index) { |
|
|
|
let min_x = 1e9; |
|
|
|
let max_x = -1e9; |
|
|
|
let min_y = 1e9; |
|
|
|
let max_y = -1e9; |
|
|
|
|
|
|
|
for (let nodeId in this.trees) { |
|
|
|
if (this.trees.hasOwnProperty(nodeId)) { |
|
|
|
if (this.trees[nodeId] === index) { |
|
|
|
let node = nodes[nodeId]; |
|
|
|
min_x = Math.min(node.x, min_x); |
|
|
|
max_x = Math.max(node.x, max_x); |
|
|
|
min_y = Math.min(node.y, min_y); |
|
|
|
max_y = Math.max(node.y, max_y); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return { |
|
|
|
min_x: min_x, |
|
|
|
max_x: max_x, |
|
|
|
min_y: min_y, |
|
|
|
max_y: max_y |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
class LayoutEngine { |
|
|
|
constructor(body) { |
|
|
|
this.body = body; |
|
|
@ -308,11 +473,8 @@ class LayoutEngine { |
|
|
|
let definedLevel = false; |
|
|
|
let definedPositions = true; |
|
|
|
let undefinedLevel = false; |
|
|
|
this.hierarchicalLevels = {}; |
|
|
|
this.lastNodeOnLevel = {}; |
|
|
|
this.hierarchicalChildrenReference = {}; |
|
|
|
this.hierarchicalParentReference = {}; |
|
|
|
this.hierarchicalTrees = {}; |
|
|
|
this.hierarchical = new HierarchicalStatus(); |
|
|
|
this.treeIndex = -1; |
|
|
|
|
|
|
|
this.distributionOrdering = {}; |
|
|
@ -328,7 +490,7 @@ class LayoutEngine { |
|
|
|
} |
|
|
|
if (node.options.level !== undefined) { |
|
|
|
definedLevel = true; |
|
|
|
this.hierarchicalLevels[nodeId] = node.options.level; |
|
|
|
this.hierarchical.levels[nodeId] = node.options.level; |
|
|
|
} |
|
|
|
else { |
|
|
|
undefinedLevel = true; |
|
|
@ -358,9 +520,7 @@ class LayoutEngine { |
|
|
|
// fallback for cases where there are nodes but no edges
|
|
|
|
for (let nodeId in this.body.nodes) { |
|
|
|
if (this.body.nodes.hasOwnProperty(nodeId)) { |
|
|
|
if (this.hierarchicalLevels[nodeId] === undefined) { |
|
|
|
this.hierarchicalLevels[nodeId] = 0; |
|
|
|
} |
|
|
|
this.hierarchical.ensureLevel(nodeId); |
|
|
|
} |
|
|
|
} |
|
|
|
// check the distribution of the nodes per level.
|
|
|
@ -402,9 +562,9 @@ class LayoutEngine { |
|
|
|
|
|
|
|
// shift a single tree by an offset
|
|
|
|
let shiftTree = (index, offset) => { |
|
|
|
for (let nodeId in this.hierarchicalTrees) { |
|
|
|
if (this.hierarchicalTrees.hasOwnProperty(nodeId)) { |
|
|
|
if (this.hierarchicalTrees[nodeId] === index) { |
|
|
|
for (let nodeId in this.hierarchical.trees) { |
|
|
|
if (this.hierarchical.trees.hasOwnProperty(nodeId)) { |
|
|
|
if (this.hierarchical.trees[nodeId] === index) { |
|
|
|
let node = this.body.nodes[nodeId]; |
|
|
|
let pos = this._getPositionForHierarchy(node); |
|
|
|
this._setPositionForHierarchy(node, pos + offset, undefined, true); |
|
|
@ -415,18 +575,12 @@ class LayoutEngine { |
|
|
|
|
|
|
|
// get the width of a tree
|
|
|
|
let getTreeSize = (index) => { |
|
|
|
let min = 1e9; |
|
|
|
let max = -1e9; |
|
|
|
for (let nodeId in this.hierarchicalTrees) { |
|
|
|
if (this.hierarchicalTrees.hasOwnProperty(nodeId)) { |
|
|
|
if (this.hierarchicalTrees[nodeId] === index) { |
|
|
|
let pos = this._getPositionForHierarchy(this.body.nodes[nodeId]); |
|
|
|
min = Math.min(pos, min); |
|
|
|
max = Math.max(pos, max); |
|
|
|
} |
|
|
|
} |
|
|
|
let res = this.hierarchical.getTreeSize(this.body.nodes, index); |
|
|
|
if (this._isVertical()) { |
|
|
|
return {min: res.min_x, max: res.max_x}; |
|
|
|
} else { |
|
|
|
return {min: res.min_y, max: res.max_y}; |
|
|
|
} |
|
|
|
return {min:min, max:max}; |
|
|
|
}; |
|
|
|
|
|
|
|
// get the width of all trees
|
|
|
@ -445,8 +599,8 @@ class LayoutEngine { |
|
|
|
return; |
|
|
|
} |
|
|
|
map[source.id] = true; |
|
|
|
if (this.hierarchicalChildrenReference[source.id]) { |
|
|
|
let children = this.hierarchicalChildrenReference[source.id]; |
|
|
|
if (this.hierarchical.childrenReference[source.id]) { |
|
|
|
let children = this.hierarchical.childrenReference[source.id]; |
|
|
|
if (children.length > 0) { |
|
|
|
for (let i = 0; i < children.length; i++) { |
|
|
|
getBranchNodes(this.body.nodes[children[i]], map); |
|
|
@ -465,7 +619,7 @@ class LayoutEngine { |
|
|
|
for (let branchNode in branchMap) { |
|
|
|
if (branchMap.hasOwnProperty(branchNode)) { |
|
|
|
let node = this.body.nodes[branchNode]; |
|
|
|
let level = this.hierarchicalLevels[node.id]; |
|
|
|
let level = this.hierarchical.levels[node.id]; |
|
|
|
let position = this._getPositionForHierarchy(node); |
|
|
|
|
|
|
|
// get the space around the node.
|
|
|
@ -484,39 +638,18 @@ class LayoutEngine { |
|
|
|
return [min, max, minSpace, maxSpace]; |
|
|
|
}; |
|
|
|
|
|
|
|
// get the maximum level of a branch.
|
|
|
|
let getMaxLevel = (nodeId) => { |
|
|
|
let accumulator = {}; |
|
|
|
let _getMaxLevel = (nodeId) => { |
|
|
|
if (accumulator[nodeId] !== undefined) { |
|
|
|
return accumulator[nodeId]; |
|
|
|
} |
|
|
|
let level = this.hierarchicalLevels[nodeId]; |
|
|
|
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])); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
accumulator[nodeId] = level; |
|
|
|
return level; |
|
|
|
}; |
|
|
|
return _getMaxLevel(nodeId); |
|
|
|
}; |
|
|
|
|
|
|
|
// check what the maximum level is these nodes have in common.
|
|
|
|
let getCollisionLevel = (node1, node2) => { |
|
|
|
let maxLevel1 = getMaxLevel(node1.id); |
|
|
|
let maxLevel2 = getMaxLevel(node2.id); |
|
|
|
let maxLevel1 = this.hierarchical.getMaxLevel(node1.id); |
|
|
|
let maxLevel2 = this.hierarchical.getMaxLevel(node2.id); |
|
|
|
return Math.min(maxLevel1, maxLevel2); |
|
|
|
}; |
|
|
|
|
|
|
|
// check if two nodes have the same parent(s)
|
|
|
|
let hasSameParent = (node1, node2) => { |
|
|
|
let parents1 = this.hierarchicalParentReference[node1.id]; |
|
|
|
let parents2 = this.hierarchicalParentReference[node2.id]; |
|
|
|
let parents1 = this.hierarchical.parentReference[node1.id]; |
|
|
|
let parents2 = this.hierarchical.parentReference[node2.id]; |
|
|
|
if (parents1 === undefined || parents2 === undefined) { |
|
|
|
return false; |
|
|
|
} |
|
|
@ -539,7 +672,7 @@ class LayoutEngine { |
|
|
|
if (levelNodes.length > 1) { |
|
|
|
for (let j = 0; j < levelNodes.length - 1; j++) { |
|
|
|
if (hasSameParent(levelNodes[j],levelNodes[j+1]) === true) { |
|
|
|
if (this.hierarchicalTrees[levelNodes[j].id] === this.hierarchicalTrees[levelNodes[j+1].id]) { |
|
|
|
if (this.hierarchical.trees[levelNodes[j].id] === this.hierarchical.trees[levelNodes[j+1].id]) { |
|
|
|
callback(levelNodes[j],levelNodes[j+1], centerParents); |
|
|
|
} |
|
|
|
}} |
|
|
@ -593,7 +726,7 @@ class LayoutEngine { |
|
|
|
// console.log("ts",node.id);
|
|
|
|
let nodeId = node.id; |
|
|
|
let allEdges = node.edges; |
|
|
|
let nodeLevel = this.hierarchicalLevels[node.id]; |
|
|
|
let nodeLevel = this.hierarchical.levels[node.id]; |
|
|
|
|
|
|
|
// gather constants
|
|
|
|
let C2 = this.options.hierarchical.levelSeparation * this.options.hierarchical.levelSeparation; |
|
|
@ -604,7 +737,7 @@ class LayoutEngine { |
|
|
|
if (edge.toId != edge.fromId) { |
|
|
|
let otherNode = edge.toId == nodeId ? edge.from : edge.to; |
|
|
|
referenceNodes[allEdges[i].id] = otherNode; |
|
|
|
if (this.hierarchicalLevels[otherNode.id] < nodeLevel) { |
|
|
|
if (this.hierarchical.levels[otherNode.id] < nodeLevel) { |
|
|
|
aboveEdges.push(edge); |
|
|
|
} |
|
|
|
} |
|
|
@ -802,7 +935,7 @@ class LayoutEngine { |
|
|
|
if (map === undefined) { |
|
|
|
useMap = false; |
|
|
|
} |
|
|
|
let level = this.hierarchicalLevels[node.id]; |
|
|
|
let level = this.hierarchical.levels[node.id]; |
|
|
|
if (level !== undefined) { |
|
|
|
let index = this.distributionIndex[node.id]; |
|
|
|
let position = this._getPositionForHierarchy(node); |
|
|
@ -837,16 +970,16 @@ class LayoutEngine { |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
_centerParent(node) { |
|
|
|
if (this.hierarchicalParentReference[node.id]) { |
|
|
|
let parents = this.hierarchicalParentReference[node.id]; |
|
|
|
if (this.hierarchical.parentReference[node.id]) { |
|
|
|
let parents = this.hierarchical.parentReference[node.id]; |
|
|
|
for (var i = 0; i < parents.length; i++) { |
|
|
|
let parentId = parents[i]; |
|
|
|
let parentNode = this.body.nodes[parentId]; |
|
|
|
if (this.hierarchicalChildrenReference[parentId]) { |
|
|
|
if (this.hierarchical.childrenReference[parentId]) { |
|
|
|
// get the range of the children
|
|
|
|
let minPos = 1e9; |
|
|
|
let maxPos = -1e9; |
|
|
|
let children = this.hierarchicalChildrenReference[parentId]; |
|
|
|
let children = this.hierarchical.childrenReference[parentId]; |
|
|
|
if (children.length > 0) { |
|
|
|
for (let i = 0; i < children.length; i++) { |
|
|
|
let childNode = this.body.nodes[children[i]]; |
|
|
@ -893,7 +1026,7 @@ class LayoutEngine { |
|
|
|
// 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._validataPositionAndContinue(node, level, pos); |
|
|
|
this._validatePositionAndContinue(node, level, pos); |
|
|
|
|
|
|
|
handledNodeCount++; |
|
|
|
} |
|
|
@ -913,14 +1046,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.hierarchicalChildrenReference[parentId] === undefined) { |
|
|
|
if (this.hierarchical.childrenReference[parentId] === undefined) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// get a list of childNodes
|
|
|
|
let childNodes = []; |
|
|
|
for (let i = 0; i < this.hierarchicalChildrenReference[parentId].length; i++) { |
|
|
|
childNodes.push(this.body.nodes[this.hierarchicalChildrenReference[parentId][i]]); |
|
|
|
for (let i = 0; i < this.hierarchical.childrenReference[parentId].length; i++) { |
|
|
|
childNodes.push(this.body.nodes[this.hierarchical.childrenReference[parentId][i]]); |
|
|
|
} |
|
|
|
|
|
|
|
// use the positions to order the nodes.
|
|
|
@ -929,7 +1062,7 @@ class LayoutEngine { |
|
|
|
// position the childNodes
|
|
|
|
for (let i = 0; i < childNodes.length; i++) { |
|
|
|
let childNode = childNodes[i]; |
|
|
|
let childNodeLevel = this.hierarchicalLevels[childNode.id]; |
|
|
|
let childNodeLevel = this.hierarchical.levels[childNode.id]; |
|
|
|
// check if the child node is below the parent node and if it has already been positioned.
|
|
|
|
if (childNodeLevel > parentLevel && this.positionedNodes[childNode.id] === undefined) { |
|
|
|
// get the amount of space required for this node. If parent the width is based on the amount of children.
|
|
|
@ -939,7 +1072,7 @@ class LayoutEngine { |
|
|
|
if (i === 0) {pos = this._getPositionForHierarchy(this.body.nodes[parentId]);} |
|
|
|
else {pos = this._getPositionForHierarchy(childNodes[i-1]) + this.options.hierarchical.nodeSpacing;} |
|
|
|
this._setPositionForHierarchy(childNode, pos, childNodeLevel); |
|
|
|
this._validataPositionAndContinue(childNode, childNodeLevel, pos); |
|
|
|
this._validatePositionAndContinue(childNode, childNodeLevel, pos); |
|
|
|
} |
|
|
|
else { |
|
|
|
return; |
|
|
@ -966,7 +1099,11 @@ class LayoutEngine { |
|
|
|
* @param pos |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
_validataPositionAndContinue(node, level, pos) { |
|
|
|
_validatePositionAndContinue(node, level, pos) { |
|
|
|
// This only works for strict hierarchical networks, i.e. trees and forests
|
|
|
|
// Early exit if this is not the case
|
|
|
|
if (!this.hierarchical.isTree) return; |
|
|
|
|
|
|
|
// if overlap has been detected, we shift the branch
|
|
|
|
if (this.lastNodeOnLevel[level] !== undefined) { |
|
|
|
let previousPos = this._getPositionForHierarchy(this.body.nodes[this.lastNodeOnLevel[level]]); |
|
|
@ -1013,7 +1150,7 @@ class LayoutEngine { |
|
|
|
for (nodeId in this.body.nodes) { |
|
|
|
if (this.body.nodes.hasOwnProperty(nodeId)) { |
|
|
|
node = this.body.nodes[nodeId]; |
|
|
|
let level = this.hierarchicalLevels[nodeId] === undefined ? 0 : this.hierarchicalLevels[nodeId]; |
|
|
|
let level = this.hierarchical.levels[nodeId] === undefined ? 0 : this.hierarchical.levels[nodeId]; |
|
|
|
if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') { |
|
|
|
node.y = this.options.hierarchical.levelSeparation * level; |
|
|
|
node.options.fixed.y = true; |
|
|
@ -1043,7 +1180,7 @@ class LayoutEngine { |
|
|
|
for (let nodeId in this.body.nodes) { |
|
|
|
if (this.body.nodes.hasOwnProperty(nodeId)) { |
|
|
|
let node = this.body.nodes[nodeId]; |
|
|
|
if (this.hierarchicalLevels[nodeId] === undefined) { |
|
|
|
if (this.hierarchical.levels[nodeId] === undefined) { |
|
|
|
hubSize = node.edges.length < hubSize ? hubSize : node.edges.length; |
|
|
|
} |
|
|
|
} |
|
|
@ -1062,15 +1199,8 @@ class LayoutEngine { |
|
|
|
let hubSize = 1; |
|
|
|
|
|
|
|
let levelDownstream = (nodeA, nodeB) => { |
|
|
|
if (this.hierarchicalLevels[nodeB.id] === undefined) { |
|
|
|
// set initial level
|
|
|
|
if (this.hierarchicalLevels[nodeA.id] === undefined) { |
|
|
|
this.hierarchicalLevels[nodeA.id] = 0; |
|
|
|
} |
|
|
|
// set level
|
|
|
|
this.hierarchicalLevels[nodeB.id] = this.hierarchicalLevels[nodeA.id] + 1; |
|
|
|
} |
|
|
|
}; |
|
|
|
this.hierarchical.levelDownstream(nodeA, nodeB); |
|
|
|
} |
|
|
|
|
|
|
|
while (hubSize > 0) { |
|
|
|
// determine hubs
|
|
|
@ -1089,8 +1219,11 @@ class LayoutEngine { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* TODO: release feature |
|
|
|
* TODO: Determine if this feature is needed at all |
|
|
|
* |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
_determineLevelsCustomCallback() { |
|
|
@ -1101,10 +1234,12 @@ class LayoutEngine { |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
// TODO: perhaps move to HierarchicalStatus.
|
|
|
|
// But I currently don't see the point, this method is not used.
|
|
|
|
let levelByDirection = (nodeA, nodeB, edge) => { |
|
|
|
let levelA = this.hierarchicalLevels[nodeA.id]; |
|
|
|
let levelA = this.hierarchical.levels[nodeA.id]; |
|
|
|
// set initial level
|
|
|
|
if (levelA === undefined) {this.hierarchicalLevels[nodeA.id] = minLevel;} |
|
|
|
if (levelA === undefined) {this.hierarchical.levels[nodeA.id] = minLevel;} |
|
|
|
|
|
|
|
let diff = customCallback( |
|
|
|
NetworkUtil.cloneOptions(nodeA,'node'), |
|
|
@ -1112,11 +1247,11 @@ class LayoutEngine { |
|
|
|
NetworkUtil.cloneOptions(edge,'edge') |
|
|
|
); |
|
|
|
|
|
|
|
this.hierarchicalLevels[nodeB.id] = this.hierarchicalLevels[nodeA.id] + diff; |
|
|
|
this.hierarchical.levels[nodeB.id] = this.hierarchical.levels[nodeA.id] + diff; |
|
|
|
}; |
|
|
|
|
|
|
|
this._crawlNetwork(levelByDirection); |
|
|
|
this._setMinLevelToZero(); |
|
|
|
this.hierarchical.setMinLevelToZero(this.body.nodes); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
@ -1127,45 +1262,21 @@ class LayoutEngine { |
|
|
|
*/ |
|
|
|
_determineLevelsDirected() { |
|
|
|
let minLevel = 10000; |
|
|
|
|
|
|
|
let levelByDirection = (nodeA, nodeB, edge) => { |
|
|
|
let levelA = this.hierarchicalLevels[nodeA.id]; |
|
|
|
let levelA = this.hierarchical.levels[nodeA.id]; |
|
|
|
// set initial level
|
|
|
|
if (levelA === undefined) {this.hierarchicalLevels[nodeA.id] = minLevel;} |
|
|
|
if (levelA === undefined) {this.hierarchical.levels[nodeA.id] = minLevel;} |
|
|
|
if (edge.toId == nodeB.id) { |
|
|
|
this.hierarchicalLevels[nodeB.id] = this.hierarchicalLevels[nodeA.id] + 1; |
|
|
|
this.hierarchical.levels[nodeB.id] = this.hierarchical.levels[nodeA.id] + 1; |
|
|
|
} |
|
|
|
else { |
|
|
|
this.hierarchicalLevels[nodeB.id] = this.hierarchicalLevels[nodeA.id] - 1; |
|
|
|
this.hierarchical.levels[nodeB.id] = this.hierarchical.levels[nodeA.id] - 1; |
|
|
|
} |
|
|
|
}; |
|
|
|
this._crawlNetwork(levelByDirection); |
|
|
|
this._setMinLevelToZero(); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Small util method to set the minimum levels of the nodes to zero. |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
_setMinLevelToZero() { |
|
|
|
let minLevel = 1e9; |
|
|
|
// get the minimum level
|
|
|
|
for (let nodeId in this.body.nodes) { |
|
|
|
if (this.body.nodes.hasOwnProperty(nodeId)) { |
|
|
|
if (this.hierarchicalLevels[nodeId] !== undefined) { |
|
|
|
minLevel = Math.min(this.hierarchicalLevels[nodeId], minLevel); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// subtract the minimum from the set so we have a range starting from 0
|
|
|
|
for (let nodeId in this.body.nodes) { |
|
|
|
if (this.body.nodes.hasOwnProperty(nodeId)) { |
|
|
|
if (this.hierarchicalLevels[nodeId] !== undefined) { |
|
|
|
this.hierarchicalLevels[nodeId] -= minLevel; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
this._crawlNetwork(levelByDirection); |
|
|
|
this.hierarchical.setMinLevelToZero(this.body.nodes); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -1175,21 +1286,13 @@ class LayoutEngine { |
|
|
|
*/ |
|
|
|
_generateMap() { |
|
|
|
let fillInRelations = (parentNode, childNode) => { |
|
|
|
if (this.hierarchicalLevels[childNode.id] > this.hierarchicalLevels[parentNode.id]) { |
|
|
|
let parentNodeId = parentNode.id; |
|
|
|
let childNodeId = childNode.id; |
|
|
|
if (this.hierarchicalChildrenReference[parentNodeId] === undefined) { |
|
|
|
this.hierarchicalChildrenReference[parentNodeId] = []; |
|
|
|
} |
|
|
|
this.hierarchicalChildrenReference[parentNodeId].push(childNodeId); |
|
|
|
if (this.hierarchicalParentReference[childNodeId] === undefined) { |
|
|
|
this.hierarchicalParentReference[childNodeId] = []; |
|
|
|
} |
|
|
|
this.hierarchicalParentReference[childNodeId].push(parentNodeId); |
|
|
|
if (this.hierarchical.levels[childNode.id] > this.hierarchical.levels[parentNode.id]) { |
|
|
|
this.hierarchical.addRelation(parentNode.id, childNode.id); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
this._crawlNetwork(fillInRelations); |
|
|
|
this.hierarchical.checkIfTree(); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -1206,8 +1309,8 @@ class LayoutEngine { |
|
|
|
let crawler = (node, tree) => { |
|
|
|
if (progress[node.id] === undefined) { |
|
|
|
|
|
|
|
if (this.hierarchicalTrees[node.id] === undefined) { |
|
|
|
this.hierarchicalTrees[node.id] = tree; |
|
|
|
if (this.hierarchical.trees[node.id] === undefined) { |
|
|
|
this.hierarchical.trees[node.id] = tree; |
|
|
|
this.treeIndex = Math.max(tree, this.treeIndex); |
|
|
|
} |
|
|
|
|
|
|
@ -1272,9 +1375,9 @@ class LayoutEngine { |
|
|
|
else { |
|
|
|
this.body.nodes[parentId].y += diff; |
|
|
|
} |
|
|
|
if (this.hierarchicalChildrenReference[parentId] !== undefined) { |
|
|
|
for (let i = 0; i < this.hierarchicalChildrenReference[parentId].length; i++) { |
|
|
|
shifter(this.hierarchicalChildrenReference[parentId][i]); |
|
|
|
if (this.hierarchical.childrenReference[parentId] !== undefined) { |
|
|
|
for (let i = 0; i < this.hierarchical.childrenReference[parentId].length; i++) { |
|
|
|
shifter(this.hierarchical.childrenReference[parentId][i]); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
@ -1292,18 +1395,18 @@ class LayoutEngine { |
|
|
|
_findCommonParent(childA,childB) { |
|
|
|
let parents = {}; |
|
|
|
let iterateParents = (parents,child) => { |
|
|
|
if (this.hierarchicalParentReference[child] !== undefined) { |
|
|
|
for (let i = 0; i < this.hierarchicalParentReference[child].length; i++) { |
|
|
|
let parent = this.hierarchicalParentReference[child][i]; |
|
|
|
if (this.hierarchical.parentReference[child] !== undefined) { |
|
|
|
for (let i = 0; i < this.hierarchical.parentReference[child].length; i++) { |
|
|
|
let parent = this.hierarchical.parentReference[child][i]; |
|
|
|
parents[parent] = true; |
|
|
|
iterateParents(parents, parent) |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
let findParent = (parents, child) => { |
|
|
|
if (this.hierarchicalParentReference[child] !== undefined) { |
|
|
|
for (let i = 0; i < this.hierarchicalParentReference[child].length; i++) { |
|
|
|
let parent = this.hierarchicalParentReference[child][i]; |
|
|
|
if (this.hierarchical.parentReference[child] !== undefined) { |
|
|
|
for (let i = 0; i < this.hierarchical.parentReference[child].length; i++) { |
|
|
|
let parent = this.hierarchical.parentReference[child][i]; |
|
|
|
if (parents[parent] !== undefined) { |
|
|
|
return {foundParent:parent, withChild:child}; |
|
|
|
} |
|
|
@ -1350,6 +1453,18 @@ class LayoutEngine { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Utility function to cut down on typing this all the time. |
|
|
|
* |
|
|
|
* TODO: use this in all applicable situations in this class. |
|
|
|
* |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
_isVertical() { |
|
|
|
return (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU'); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Abstract the getting of the position of a node so we do not have to repeat the direction check all the time. |
|
|
|
* @param node |
|
|
@ -1384,9 +1499,6 @@ class LayoutEngine { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
export default LayoutEngine; |