@ -1,7 +1,172 @@
'use strict' ;
let util = require ( '../../util' ) ;
import NetworkUtil from '../NetworkUtil' ;
var NetworkUtil = require ( '../NetworkUtil' ) . default ;
/ * *
* 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 = 1 e9 ;
// 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 = 1 e9 ;
let max_x = - 1 e9 ;
let min_y = 1 e9 ;
let max_y = - 1 e9 ;
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 ) {
@ -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 . hierarchicalL evels [ nodeId ] = node . options . level ;
this . hierarchical . l evels[ 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 . hierarchicalT rees ) {
if ( this . hierarchicalT rees . hasOwnProperty ( nodeId ) ) {
if ( this . hierarchicalT rees [ nodeId ] === index ) {
for ( let nodeId in this . hierarchical . t rees) {
if ( this . hierarchical . t rees. hasOwnProperty ( nodeId ) ) {
if ( this . hierarchical . t rees[ 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 = 1 e9 ;
let max = - 1 e9 ;
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 . hierarchicalC hildrenReference [ source . id ] ) {
let children = this . hierarchicalC hildrenReference [ source . id ] ;
if ( this . hierarchical . c hildrenReference[ source . id ] ) {
let children = this . hierarchical . c hildrenReference[ 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 . hierarchicalL evels [ node . id ] ;
let level = this . hierarchical . l evels[ 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 . hierarchicalP arentReference [ node1 . id ] ;
let parents2 = this . hierarchicalP arentReference [ 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 . hierarchicalT rees [ levelNodes [ j ] . id ] === this . hierarchicalT rees [ levelNodes [ j + 1 ] . id ] ) {
if ( this . hierarchical . t rees[ levelNodes [ j ] . id ] === this . hierarchical . t rees[ 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 . hierarchicalL evels [ node . id ] ;
let nodeLevel = this . hierarchical . l evels[ 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 . hierarchicalL evels [ otherNode . id ] < nodeLevel ) {
if ( this . hierarchical . l evels[ otherNode . id ] < nodeLevel ) {
aboveEdges . push ( edge ) ;
}
}
@ -802,7 +935,7 @@ class LayoutEngine {
if ( map === undefined ) {
useMap = false ;
}
let level = this . hierarchicalL evels [ node . id ] ;
let level = this . hierarchical . l evels[ 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 . hierarchicalP arentReference [ node . id ] ) {
let parents = this . hierarchicalP arentReference [ node . id ] ;
if ( this . hierarchical . p arentReference[ node . id ] ) {
let parents = this . hierarchical . p arentReference[ node . id ] ;
for ( var i = 0 ; i < parents . length ; i ++ ) {
let parentId = parents [ i ] ;
let parentNode = this . body . nodes [ parentId ] ;
if ( this . hierarchicalC hildrenReference [ parentId ] ) {
if ( this . hierarchical . c hildrenReference[ parentId ] ) {
// get the range of the children
let minPos = 1 e9 ;
let maxPos = - 1 e9 ;
let children = this . hierarchicalC hildrenReference [ parentId ] ;
let children = this . hierarchical . c hildrenReference[ 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 . _validata PositionAndContinue ( node , level , pos ) ;
this . _validate PositionAndContinue ( 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 . hierarchicalC hildrenReference [ parentId ] === undefined ) {
if ( this . hierarchical . c hildrenReference[ parentId ] === undefined ) {
return ;
}
// get a list of childNodes
let childNodes = [ ] ;
for ( let i = 0 ; i < this . hierarchicalC hildrenReference [ parentId ] . length ; i ++ ) {
childNodes . push ( this . body . nodes [ this . hierarchicalC hildrenReference [ parentId ] [ i ] ] ) ;
for ( let i = 0 ; i < this . hierarchical . c hildrenReference[ parentId ] . length ; i ++ ) {
childNodes . push ( this . body . nodes [ this . hierarchical . c hildrenReference[ 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 . hierarchicalL evels [ childNode . id ] ;
let childNodeLevel = this . hierarchical . l evels[ 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 . _validata PositionAndContinue ( childNode , childNodeLevel , pos ) ;
this . _validate PositionAndContinue ( 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 . hierarchicalL evels [ nodeId ] === undefined ? 0 : this . hierarchicalL evels [ nodeId ] ;
let level = this . hierarchical . l evels[ nodeId ] === undefined ? 0 : this . hierarchical . l evels[ 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 . hierarchicalL evels [ nodeId ] === undefined ) {
if ( this . hierarchical . l evels[ 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 ( ) {