@ -207,35 +207,47 @@ class LayoutEngine {
this . body . emitter . on ( '_resetHierarchicalLayout' , ( ) => {
this . setupHierarchicalLayout ( ) ;
} ) ;
this . body . emitter . on ( '_adjustEdgesForHierarchicalLayout' , ( ) => {
if ( this . options . hierarchical . enabled !== true ) {
return ;
}
// get the type of static smooth curve in case it is required
let type = this . getStaticType ( ) ;
// force all edges into static smooth curves.
this . body . emitter . emit ( '_forceDisableDynamicCurves' , type , false ) ;
} ) ;
}
setOptions ( options , allOptions ) {
if ( options !== undefined ) {
let prevHierarchicalState = this . options . hierarchical . enabled ;
let hierarchical = this . options . hierarchical ;
let prevHierarchicalState = hierarchical . enabled ;
util . selectiveDeepExtend ( [ "randomSeed" , "improvedLayout" ] , this . options , options ) ;
util . mergeOptions ( this . options , options , 'hierarchical' ) ;
if ( options . randomSeed !== undefined ) { this . initialRandomSeed = options . randomSeed ; }
if ( this . options . hierarchical . enabled === true ) {
if ( hierarchical . enabled === true ) {
if ( prevHierarchicalState === true ) {
// refresh the overridden options for nodes and edges.
this . body . emitter . emit ( 'refresh' , true ) ;
}
// make sure the level separation is the right way up
if ( this . options . hierarchical . direction === 'RL' || this . options . hierarchical . direction === 'DU' ) {
if ( this . options . hierarchical . levelSeparation > 0 ) {
this . options . hierarchical . levelSeparation *= - 1 ;
if ( hierarchical . direction === 'RL' || hierarchical . direction === 'DU' ) {
if ( hierarchical . levelSeparation > 0 ) {
hierarchical . levelSeparation *= - 1 ;
}
}
else {
if ( this . options . hierarchical . levelSeparation < 0 ) {
this . options . hierarchical . levelSeparation *= - 1 ;
if ( hierarchical . levelSeparation < 0 ) {
hierarchical . levelSeparation *= - 1 ;
}
}
this . body . emitter . emit ( '_resetHierarchicalLayout' ) ;
// because the hierarchical system needs it's own physics and smooth curve settings, we adapt the other options if needed.
// because the hierarchical system needs it's own physics and smooth curve settings,
// we adapt the other options if needed.
return this . adaptAllOptionsForHierarchicalLayout ( allOptions ) ;
}
else {
@ -251,32 +263,32 @@ class LayoutEngine {
adaptAllOptionsForHierarchicalLayout ( allOptions ) {
if ( this . options . hierarchical . enabled === true ) {
let backupPhysics = this . optionsBackup . physics ;
// set the physics
if ( allOptions . physics === undefined || allOptions . physics === true ) {
allOptions . physics = {
enabled : this . optionsBackup . p hysics. enabled === undefined ? true : this . optionsBackup . p hysics . enabled ,
solver : 'hierarchicalRepulsion'
enabled : backupP hysics . enabled === undefined ? true : backupP hysics. enabled ,
solver : 'hierarchicalRepulsion'
} ;
this . optionsBackup . p hysics. enabled = this . optionsBackup . p hysics. enabled === undefined ? true : this . optionsBackup . p hysics. enabled ;
this . optionsBackup . p hysics. solver = this . optionsBackup . p hysics. solver || 'barnesHut' ;
backupP hysics. enabled = backupP hysics. enabled === undefined ? true : backupP hysics. enabled ;
backupP hysics. solver = backupP hysics. solver || 'barnesHut' ;
}
else if ( typeof allOptions . physics === 'object' ) {
this . optionsBackup . p hysics. enabled = allOptions . physics . enabled === undefined ? true : allOptions . physics . enabled ;
this . optionsBackup . p hysics. solver = allOptions . physics . solver || 'barnesHut' ;
backupP hysics. enabled = allOptions . physics . enabled === undefined ? true : allOptions . physics . enabled ;
backupP hysics. solver = allOptions . physics . solver || 'barnesHut' ;
allOptions . physics . solver = 'hierarchicalRepulsion' ;
}
else if ( allOptions . physics !== false ) {
this . optionsBackup . p hysics. solver = 'barnesHut' ;
backupP hysics. solver = 'barnesHut' ;
allOptions . physics = { solver : 'hierarchicalRepulsion' } ;
}
// get the type of static smooth curve in case it is required
let type = 'horizontal' ;
if ( this . options . hierarchical . direction === 'RL' || this . options . hierarchical . direction === 'LR' ) {
type = 'vertical' ;
}
let type = this . getStaticType ( ) ;
// disable smooth curves if nothing is defined. If smooth curves have been turned on, turn them into static smooth curves.
// disable smooth curves if nothing is defined. If smooth curves have been turned on,
// turn them into static smooth curves.
if ( allOptions . edges === undefined ) {
this . optionsBackup . edges = { smooth : { enabled : true , type : 'dynamic' } } ;
allOptions . edges = { smooth : false } ;
@ -291,27 +303,34 @@ class LayoutEngine {
allOptions . edges . smooth = { enabled : allOptions . edges . smooth , type : type }
}
else {
let smooth = allOptions . edges . smooth ;
// allow custom types except for dynamic
if ( allOptions . edges . smooth . type !== undefined && allOptions . edges . smooth . type !== 'dynamic' ) {
type = allOptions . edges . smooth . type ;
if ( smooth . type !== undefined && smooth . type !== 'dynamic' ) {
type = smooth . type ;
}
// TODO: this is options merging; see if the standard routines can be used here.
this . optionsBackup . edges = {
smooth : allOptions . edges . smooth . enabled === undefined ? true : allOptions . edges . smooth . enabled ,
type : allOptions . edges . smooth . type === undefined ? 'dynamic' : allOptions . edges . smooth . type ,
roundness : allOptions . edges . smooth . roundness === undefined ? 0.5 : allOptions . edges . smooth . roundness ,
forceDirection : allOptions . edges . smooth . forceDirection === undefined ? false : allOptions . edges . smooth . forceDirection
smooth : smooth . enabled === undefined ? true : smooth . enabled ,
type : smooth . type === undefined ? 'dynamic' : smooth . type ,
roundness : smooth . roundness === undefined ? 0.5 : smooth . roundness ,
forceDirection : smooth . forceDirection === undefined ? false : smooth . forceDirection
} ;
// NOTE: Copying an object to self; this is basically setting defaults for undefined variables
allOptions . edges . smooth = {
enabled : allOptions . edges . smooth . enabled === undefined ? true : allOptions . edges . smooth . enabled ,
type : type ,
roundness : allOptions . edges . smooth . roundness === undefined ? 0.5 : allOptions . edges . smooth . roundness ,
forceDirection : allOptions . edges . smooth . forceDirection === undefined ? false : allOptions . edges . smooth . forceDirection
enabled : smooth . enabled === undefined ? true : smooth . enabled ,
type : type ,
roundness : smooth . roundness === undefined ? 0.5 : smooth . roundness ,
forceDirection : smooth . forceDirection === undefined ? false : smooth . forceDirection
}
}
}
// force all edges into static smooth curves. Only applies to edges that do not use the global options for smooth.
// Force all edges into static smooth curves.
// Only applies to edges that do not use the global options for smooth.
this . body . emitter . emit ( '_forceDisableDynamicCurves' , type ) ;
}
@ -347,22 +366,25 @@ class LayoutEngine {
* /
layoutNetwork ( ) {
if ( this . options . hierarchical . enabled !== true && this . options . improvedLayout === true ) {
let indices = this . body . nodeIndices ;
// first check if we should Kamada Kawai to layout. The threshold is if less than half of the visible
// nodes have predefined positions we use this.
let positionDefined = 0 ;
for ( let i = 0 ; i < this . body . nodeI ndices. length ; i ++ ) {
let node = this . body . nodes [ this . body . nodeI ndices[ i ] ] ;
for ( let i = 0 ; i < i ndices. length ; i ++ ) {
let node = this . body . nodes [ i ndices[ i ] ] ;
if ( node . predefinedPosition === true ) {
positionDefined += 1 ;
}
}
// if less than half of the nodes have a predefined position we continue
if ( positionDefined < 0.5 * this . body . nodeI ndices. length ) {
if ( positionDefined < 0.5 * i ndices. length ) {
let MAX_LEVELS = 10 ;
let level = 0 ;
let clusterThreshold = 150 ;
//Performance enhancement, during clustering edges need only be simple straight lines. These options don't propagate outside the clustering phase.
// Performance enhancement, during clustering edges need only be simple straight lines.
// These options don't propagate outside the clustering phase.
let clusterOptions = {
clusterEdgeProperties : {
smooth : {
@ -372,12 +394,15 @@ class LayoutEngine {
} ;
// if there are a lot of nodes, we cluster before we run the algorithm.
if ( this . body . nodeIndices . length > clusterThreshold ) {
let startLength = this . body . nodeIndices . length ;
while ( this . body . nodeIndices . length > clusterThreshold && level <= MAX_LEVELS ) {
// NOTE: this part fails to find clusters for large scale-free networks, which should
// be easily clusterable.
// TODO: examine why this is so
if ( indices . length > clusterThreshold ) {
let startLength = indices . length ;
while ( indices . length > clusterThreshold && level <= MAX_LEVELS ) {
//console.time("clustering")
level += 1 ;
let before = this . body . nodeIndices . length ;
let before = i ndices. length ;
// if there are many nodes we do a hubsize cluster
if ( level % 3 === 0 ) {
this . body . modules . clustering . clusterBridges ( clusterOptions ) ;
@ -385,11 +410,12 @@ class LayoutEngine {
else {
this . body . modules . clustering . clusterOutliers ( clusterOptions ) ;
}
let after = this . body . nodeI ndices. length ;
let after = i ndices. length ;
if ( before == after && level % 3 !== 0 ) {
this . _declusterAll ( ) ;
this . body . emitter . emit ( "_layoutFailed" ) ;
console . info ( "This network could not be positioned by this version of the improved layout algorithm. Please disable improvedLayout for better performance." ) ;
console . info ( "This network could not be positioned by this version of the improved layout algorithm."
+ " Please disable improvedLayout for better performance." ) ;
return ;
}
//console.timeEnd("clustering")
@ -399,22 +425,24 @@ class LayoutEngine {
this . body . modules . kamadaKawai . setOptions ( { springLength : Math . max ( 150 , 2 * startLength ) } )
}
if ( level > MAX_LEVELS ) {
console . info ( "The clustering didn't succeed within the amount of interations allowed, progressing with partial result." ) ;
console . info ( "The clustering didn't succeed within the amount of interations allowed,"
+ " progressing with partial result." ) ;
}
// position the system for these nodes and edges
this . body . modules . kamadaKawai . solve ( this . body . nodeI ndices, this . body . edgeIndices , true ) ;
this . body . modules . kamadaKawai . solve ( i ndices, this . body . edgeIndices , true ) ;
// shift to center point
this . _shiftToCenter ( ) ;
// perturb the nodes a little bit to force the physics to kick in
let offset = 70 ;
for ( let i = 0 ; i < this . body . nodeI ndices. length ; i ++ ) {
for ( let i = 0 ; i < i ndices. length ; i ++ ) {
// Only perturb the nodes that aren't fixed
if ( this . body . nodes [ this . body . nodeIndices [ i ] ] . predefinedPosition === false ) {
this . body . nodes [ this . body . nodeIndices [ i ] ] . x += ( 0.5 - this . seededRandom ( ) ) * offset ;
this . body . nodes [ this . body . nodeIndices [ i ] ] . y += ( 0.5 - this . seededRandom ( ) ) * offset ;
let node = this . body . nodes [ indices [ i ] ] ;
if ( node . predefinedPosition === false ) {
node . x += ( 0.5 - this . seededRandom ( ) ) * offset ;
node . y += ( 0.5 - this . seededRandom ( ) ) * offset ;
}
}
@ -435,8 +463,9 @@ class LayoutEngine {
let range = NetworkUtil . getRangeCore ( this . body . nodes , this . body . nodeIndices ) ;
let center = NetworkUtil . findCenter ( range ) ;
for ( let i = 0 ; i < this . body . nodeIndices . length ; i ++ ) {
this . body . nodes [ this . body . nodeIndices [ i ] ] . x -= center . x ;
this . body . nodes [ this . body . nodeIndices [ i ] ] . y -= center . y ;
let node = this . body . nodes [ this . body . nodeIndices [ i ] ] ;
node . x -= center . x ;
node . y -= center . y ;
}
}
@ -500,18 +529,20 @@ class LayoutEngine {
// if the user defined some levels but not all, alert and run without hierarchical layout
if ( undefinedLevel === true && definedLevel === true ) {
throw new Error ( 'To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.' ) ;
throw new Error ( 'To use the hierarchical layout, nodes require either no predefined levels'
+ ' or levels have to be defined for all nodes.' ) ;
}
else {
// define levels if undefined by the users. Based on hubsize.
if ( undefinedLevel === true ) {
if ( this . options . hierarchical . sortMethod === 'hubsize' ) {
let sortMethod = this . options . hierarchical . sortMethod ;
if ( sortMethod === 'hubsize' ) {
this . _determineLevelsByHubsize ( ) ;
}
else if ( this . options . hierarchical . sortMethod === 'directed' ) {
else if ( sortMethod === 'directed' ) {
this . _determineLevelsDirected ( ) ;
}
else if ( this . options . hierarchical . sortMethod === 'custom' ) {
else if ( sortMethod === 'custom' ) {
this . _determineLevelsCustomCallback ( ) ;
}
}
@ -686,8 +717,9 @@ class LayoutEngine {
let pos1 = this . _getPositionForHierarchy ( node1 ) ;
let pos2 = this . _getPositionForHierarchy ( node2 ) ;
let diffAbs = Math . abs ( pos2 - pos1 ) ;
//console.log("NOW CHEcKING:", node1.id, node2.id, diffAbs);
if ( diffAbs > this . options . hierarchical . nodeSpacing ) {
let nodeSpacing = this . options . hierarchical . nodeSpacing ;
//console.log("NOW CHECKING:", node1.id, node2.id, diffAbs);
if ( diffAbs > nodeSpacing ) {
let branchNodes1 = { } ;
let branchNodes2 = { } ;
@ -699,12 +731,13 @@ class LayoutEngine {
let [ min1 , max1 , minSpace1 , maxSpace1 ] = getBranchBoundary ( branchNodes1 , maxLevel ) ;
let [ min2 , max2 , minSpace2 , maxSpace2 ] = getBranchBoundary ( branchNodes2 , maxLevel ) ;
//console.log(node1.id, getBranchBoundary(branchNodes1, maxLevel), node2.id, getBranchBoundary(branchNodes2, maxLevel), maxLevel);
//console.log(node1.id, getBranchBoundary(branchNodes1, maxLevel), node2.id,
// getBranchBoundary(branchNodes2, maxLevel), maxLevel);
let diffBranch = Math . abs ( max1 - min2 ) ;
if ( diffBranch > this . options . hierarchical . nodeSpacing ) {
let offset = max1 - min2 + this . options . hierarchical . nodeSpacing ;
if ( offset < - minSpace2 + this . options . hierarchical . nodeSpacing ) {
offset = - minSpace2 + this . options . hierarchical . nodeSpacing ;
if ( diffBranch > nodeSpacing ) {
let offset = max1 - min2 + nodeSpacing ;
if ( offset < - minSpace2 + nodeSpacing ) {
offset = - minSpace2 + nodeSpacing ;
//console.log("RESETTING OFFSET", max1 - min2 + this.options.hierarchical.nodeSpacing, -minSpace2, offset);
}
if ( offset < 0 ) {
@ -939,18 +972,19 @@ class LayoutEngine {
if ( level !== undefined ) {
let index = this . distributionIndex [ node . id ] ;
let position = this . _getPositionForHierarchy ( node ) ;
let ordering = this . distributionOrdering [ level ] ;
let minSpace = 1 e9 ;
let maxSpace = 1 e9 ;
if ( index !== 0 ) {
let prevNode = this . distributi onO rdering[ level ] [ index - 1 ] ;
let prevNode = ordering [ index - 1 ] ;
if ( ( useMap === true && map [ prevNode . id ] === undefined ) || useMap === false ) {
let prevPos = this . _getPositionForHierarchy ( prevNode ) ;
minSpace = position - prevPos ;
}
}
if ( index != this . distributi onO rdering[ level ] . length - 1 ) {
let nextNode = this . distributi onO rdering[ level ] [ index + 1 ] ;
if ( index != ordering . length - 1 ) {
let nextNode = ordering [ index + 1 ] ;
if ( ( useMap === true && map [ nextNode . id ] === undefined ) || useMap === false ) {
let nextPos = this . _getPositionForHierarchy ( nextNode ) ;
maxSpace = Math . min ( maxSpace , nextPos - position ) ;
@ -975,24 +1009,17 @@ class LayoutEngine {
for ( var i = 0 ; i < parents . length ; i ++ ) {
let parentId = parents [ i ] ;
let parentNode = this . body . nodes [ parentId ] ;
if ( this . hierarchical . childrenReference [ parentId ] ) {
let children = this . hierarchical . childrenReference [ parentId ] ;
if ( children !== undefined ) {
// get the range of the children
let minPos = 1 e9 ;
let maxPos = - 1 e9 ;
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 ] ] ;
minPos = Math . min ( minPos , this . _getPositionForHierarchy ( childNode ) ) ;
maxPos = Math . max ( maxPos , this . _getPositionForHierarchy ( childNode ) ) ;
}
}
let newPosition = this . _getCenterPosition ( children ) ;
let position = this . _getPositionForHierarchy ( parentNode ) ;
let [ minSpace , maxSpace ] = this . _getSpaceAroundNode ( parentNode ) ;
let newPosition = 0.5 * ( minPos + maxPos ) ;
let diff = position - newPosition ;
if ( ( diff < 0 && Math . abs ( diff ) < maxSpace - this . options . hierarchical . nodeSpacing ) || ( diff > 0 && Math . abs ( diff ) < minSpace - this . options . hierarchical . nodeSpacing ) ) {
if ( ( diff < 0 && Math . abs ( diff ) < maxSpace - this . options . hierarchical . nodeSpacing ) ||
( diff > 0 && Math . abs ( diff ) < minSpace - this . options . hierarchical . nodeSpacing ) ) {
this . _setPositionForHierarchy ( parentNode , newPosition , undefined , true ) ;
}
}
@ -1022,9 +1049,13 @@ class LayoutEngine {
for ( let i = 0 ; i < nodeArray . length ; i ++ ) {
let node = nodeArray [ i ] ;
if ( this . positionedNodes [ node . id ] === undefined ) {
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 ; }
let spacing = this . options . hierarchical . nodeSpacing ;
let pos = spacing * 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 ] ) + spacing ;
}
this . _setPositionForHierarchy ( node , pos , level ) ;
this . _validatePositionAndContinue ( node , level , pos ) ;
@ -1045,15 +1076,17 @@ class LayoutEngine {
* @ private
* /
_placeBranchNodes ( parentId , parentLevel ) {
let childRef = this . hierarchical . childrenReference [ parentId ] ;
// if this is not a parent, cancel the placing. This can happen with multiple parents to one child.
if ( this . hierarchical . childrenReference [ parentId ] === undefined ) {
if ( childRef === undefined ) {
return ;
}
// get a list of childNodes
let childNodes = [ ] ;
for ( let i = 0 ; i < this . hierarchical . children Reference [ parentId ] . length ; i ++ ) {
childNodes . push ( this . body . nodes [ this . hierarchical . children Reference [ parentId ] [ i ] ] ) ;
for ( let i = 0 ; i < childRef . length ; i ++ ) {
childNodes . push ( this . body . nodes [ childRef [ i ] ] ) ;
}
// use the positions to order the nodes.
@ -1066,11 +1099,13 @@ class LayoutEngine {
// 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.
let spacing = this . options . hierarchical . nodeSpacing ;
let pos ;
// 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
// 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 ( i === 0 ) { pos = this . _getPositionForHierarchy ( this . body . nodes [ parentId ] ) ; }
else { pos = this . _getPositionForHierarchy ( childNodes [ i - 1 ] ) + this . option s. hierarchical . nodeS pacing; }
else { pos = this . _getPositionForHierarchy ( childNodes [ i - 1 ] ) + spacing ; }
this . _setPositionForHierarchy ( childNode , pos , childNodeLevel ) ;
this . _validatePositionAndContinue ( childNode , childNodeLevel , pos ) ;
}
@ -1080,14 +1115,8 @@ class LayoutEngine {
}
// center the parent nodes.
let minPos = 1 e9 ;
let maxPos = - 1 e9 ;
for ( let i = 0 ; i < childNodes . length ; i ++ ) {
let childNodeId = childNodes [ i ] . id ;
minPos = Math . min ( minPos , this . _getPositionForHierarchy ( this . body . nodes [ childNodeId ] ) ) ;
maxPos = Math . max ( maxPos , this . _getPositionForHierarchy ( this . body . nodes [ childNodeId ] ) ) ;
}
this . _setPositionForHierarchy ( this . body . nodes [ parentId ] , 0.5 * ( minPos + maxPos ) , parentLevel ) ;
let center = this . _getCenterPosition ( childNodes ) ;
this . _setPositionForHierarchy ( this . body . nodes [ parentId ] , center , parentLevel ) ;
}
@ -1123,8 +1152,8 @@ class LayoutEngine {
}
/ * *
* Receives an array with node indices and returns an array with the actual node references . Used for sorting based on
* node properties .
* Receives an array with node indices and returns an array with the actual node references .
* Used for sorting based on node properties .
* @ param idArray
* /
_indexArrayToNodes ( idArray ) {
@ -1145,13 +1174,14 @@ class LayoutEngine {
let distribution = { } ;
let nodeId , node ;
// we fix Y because the hierarchy is vertical, we fix X so we do not give a node an x position for a second time.
// we fix Y because the hierarchy is vertical,
// we fix X so we do not give a node an x position for a second time.
// the fix of X is removed after the x value has been set.
for ( nodeId in this . body . nodes ) {
if ( this . body . nodes . hasOwnProperty ( nodeId ) ) {
node = this . body . nodes [ nodeId ] ;
let level = this . hierarchical . levels [ nodeId ] === undefined ? 0 : this . hierarchical . levels [ nodeId ] ;
if ( this . options . hierarchical . direction === 'UD' || this . options . hierarchical . direction === 'DU' ) {
if ( this . _isVertical ( ) ) {
node . y = this . options . hierarchical . levelSeparation * level ;
node . options . fixed . y = true ;
}
@ -1170,50 +1200,78 @@ class LayoutEngine {
/ * *
* Get the hubsize from all remaining unlevelled nodes .
* Return the active ( i . e . visible ) edges for this node
*
* @ returns { number }
* @ returns { array } Array of edge instances
* @ private
* /
_getHubSize ( ) {
let hubSize = 0 ;
for ( let nodeId in this . body . nodes ) {
if ( this . body . nodes . hasOwnProperty ( nodeId ) ) {
let node = this . body . nodes [ nodeId ] ;
if ( this . hierarchical . levels [ nodeId ] === undefined ) {
hubSize = node . edges . length < hubSize ? hubSize : node . edges . length ;
}
_getActiveEdges ( node ) {
let result = [ ] ;
for ( let j in node . edges ) {
let edge = node . edges [ j ] ;
if ( this . body . edgeIndices . indexOf ( edge . id ) !== - 1 ) {
result . push ( edge ) ;
}
}
return hubSize ;
return result ;
}
/ * *
* Get the hubsizes for all active nodes .
*
* @ returns { number }
* @ private
* /
_getHubSizes ( ) {
let hubSizes = { } ;
let nodeIds = this . body . nodeIndices ;
for ( let i in nodeIds ) {
let nodeId = nodeIds [ i ] ;
let node = this . body . nodes [ nodeId ] ;
let hubSize = this . _getActiveEdges ( node ) . length ;
hubSizes [ hubSize ] = true ;
}
// Make an array of the size sorted descending
let result = [ ] ;
for ( let size in hubSizes ) {
result . push ( Number ( size ) ) ;
}
result . sort ( function ( a , b ) {
return b - a ;
} ) ;
return result ;
}
/ * *
* this function allocates nodes in levels based on the recursive branching from the largest hubs .
*
* @ param hubsize
* @ private
* /
_determineLevelsByHubsize ( ) {
let hubSize = 1 ;
let levelDownstream = ( nodeA , nodeB ) => {
this . hierarchical . levelDownstream ( nodeA , nodeB ) ;
}
while ( hubSize > 0 ) {
// determine hubs
hubSize = this . _getHubSize ( ) ;
if ( hubSize === 0 )
break ;
let hubSizes = this . _getHubSizes ( ) ;
for ( let nodeId in this . body . nodes ) {
if ( this . body . nodes . hasOwnProperty ( nodeId ) ) {
let node = this . body . nodes [ nodeId ] ;
if ( node . edges . length === hubSize ) {
this . _crawlNetwork ( levelDownstream , nodeId ) ;
}
for ( let i = 0 ; i < hubSizes . length ; ++ i ) {
let hubSize = hubSizes [ i ] ;
if ( hubSize === 0 ) break ;
let nodeIds = this . body . nodeIndices ;
for ( let j in nodeIds ) {
let nodeId = nodeIds [ j ] ;
let node = this . body . nodes [ nodeId ] ;
if ( hubSize === this . _getActiveEdges ( node ) . length ) {
this . _crawlNetwork ( levelDownstream , nodeId ) ;
}
}
}
@ -1239,7 +1297,7 @@ class LayoutEngine {
let levelByDirection = ( nodeA , nodeB , edge ) => {
let levelA = this . hierarchical . levels [ nodeA . id ] ;
// set initial level
if ( levelA === undefined ) { this . hierarchical . levels [ nodeA . id ] = minLevel ; }
if ( levelA === undefined ) { levelA = this . hierarchical . levels [ nodeA . id ] = minLevel ; }
let diff = customCallback (
NetworkUtil . cloneOptions ( nodeA , 'node' ) ,
@ -1247,7 +1305,7 @@ class LayoutEngine {
NetworkUtil . cloneOptions ( edge , 'edge' )
) ;
this . hierarchical . levels [ nodeB . id ] = this . hierarchical . levels [ node A . id ] + diff ;
this . hierarchical . levels [ nodeB . id ] = levelA + diff ;
} ;
this . _crawlNetwork ( levelByDirection ) ;
@ -1266,12 +1324,12 @@ class LayoutEngine {
let levelByDirection = ( nodeA , nodeB , edge ) => {
let levelA = this . hierarchical . levels [ nodeA . id ] ;
// set initial level
if ( levelA === undefined ) { this . hierarchical . levels [ nodeA . id ] = minLevel ; }
if ( levelA === undefined ) { levelA = this . hierarchical . levels [ nodeA . id ] = minLevel ; }
if ( edge . toId == nodeB . id ) {
this . hierarchical . levels [ nodeB . id ] = this . hierarchical . levels [ node A . id ] + 1 ;
this . hierarchical . levels [ nodeB . id ] = levelA + 1 ;
}
else {
this . hierarchical . levels [ nodeB . id ] = this . hierarchical . levels [ node A . id ] - 1 ;
this . hierarchical . levels [ nodeB . id ] = levelA - 1 ;
}
} ;
@ -1298,7 +1356,7 @@ class LayoutEngine {
/ * *
* Crawl over the entire network and use a callback on each node couple that is connected to each other .
* @ param callback | will receive nodeA nodeB and the connecting edge . A and B are unique .
* @ param callback | will receive nodeA , nodeB and the connecting edge . A and B are distinct .
* @ param startingNodeId
* @ private
* /
@ -1316,17 +1374,19 @@ class LayoutEngine {
progress [ node . id ] = true ;
let childNode ;
for ( let i = 0 ; i < node . edges . length ; i ++ ) {
if ( node . edges [ i ] . connected === true ) {
if ( node . edges [ i ] . toId === node . id ) {
childNode = node . edges [ i ] . from ;
let edges = this . _getActiveEdges ( node ) ;
for ( let i = 0 ; i < edges . length ; i ++ ) {
let edge = edges [ i ] ;
if ( edge . connected === true ) {
if ( edge . toId == node . id ) { // '==' because id's can be string and numeric
childNode = edge . from ;
}
else {
childNode = node . edges [ i ] . to ;
childNode = edge . to ;
}
if ( node . id !== childNode . id ) {
callback ( node , childNode , node . edges [ i ] ) ;
if ( node . id != childNode . id ) { // '!=' because id's can be string and numeric
callback ( node , childNode , edge ) ;
crawler ( childNode , tree ) ;
}
}
@ -1369,15 +1429,17 @@ class LayoutEngine {
return ;
}
progress [ parentId ] = true ;
if ( this . options . hierarchical . direction === 'UD' || this . options . hierarchical . direction === 'DU' ) {
if ( this . _isVertical ( ) ) {
this . body . nodes [ parentId ] . x += diff ;
}
else {
this . body . nodes [ parentId ] . y += diff ;
}
if ( this . hierarchical . childrenReference [ parentId ] !== undefined ) {
for ( let i = 0 ; i < this . hierarchical . childrenReference [ parentId ] . length ; i ++ ) {
shifter ( this . hierarchical . childrenReference [ parentId ] [ i ] ) ;
let childRef = this . hierarchical . childrenReference [ parentId ] ;
if ( childRef !== undefined ) {
for ( let i = 0 ; i < childRef . length ; i ++ ) {
shifter ( childRef [ i ] ) ;
}
}
} ;
@ -1395,18 +1457,20 @@ class LayoutEngine {
_findCommonParent ( childA , childB ) {
let parents = { } ;
let iterateParents = ( parents , child ) => {
if ( this . hierarchical . parentReference [ child ] !== undefined ) {
for ( let i = 0 ; i < this . hierarchical . parentReference [ child ] . length ; i ++ ) {
let parent = this . hierarchical . parentReference [ child ] [ i ] ;
let parentRef = this . hierarchical . parentReference [ child ] ;
if ( parentRef !== undefined ) {
for ( let i = 0 ; i < parentRef . length ; i ++ ) {
let parent = parentRef [ i ] ;
parents [ parent ] = true ;
iterateParents ( parents , parent )
}
}
} ;
let findParent = ( parents , child ) => {
if ( this . hierarchical . parentReference [ child ] !== undefined ) {
for ( let i = 0 ; i < this . hierarchical . parentReference [ child ] . length ; i ++ ) {
let parent = this . hierarchical . parentReference [ child ] [ i ] ;
let parentRef = this . hierarchical . parentReference [ child ] ;
if ( parentRef !== undefined ) {
for ( let i = 0 ; i < parentRef . length ; i ++ ) {
let parent = parentRef [ i ] ;
if ( parents [ parent ] !== undefined ) {
return { foundParent : parent , withChild : child } ;
}
@ -1445,7 +1509,7 @@ class LayoutEngine {
this . distributionOrderingPresence [ level ] [ node . id ] = true ;
}
if ( this . options . hierarchical . direction === 'UD' || this . options . hierarchical . direction === 'DU' ) {
if ( this . _isVertical ( ) ) {
node . x = position ;
}
else {
@ -1472,7 +1536,7 @@ class LayoutEngine {
* @ private
* /
_getPositionForHierarchy ( node ) {
if ( this . options . hierarchical . direction === 'UD' || this . options . hierarchical . direction === 'DU' ) {
if ( this . _isVertical ( ) ) {
return node . x ;
}
else {
@ -1487,7 +1551,7 @@ class LayoutEngine {
* /
_sortNodeArray ( nodeArray ) {
if ( nodeArray . length > 1 ) {
if ( this . options . hierarchical . direction === 'UD' || this . options . hierarchical . direction === 'DU' ) {
if ( this . _isVertical ( ) ) {
nodeArray . sort ( function ( a , b ) {
return a . x - b . x ;
} )
@ -1499,6 +1563,54 @@ class LayoutEngine {
}
}
}
/ * *
* Get the type of static smooth curve in case it is required .
*
* The return value is the type to use to translate dynamic curves to
* another type , in the case of hierarchical layout . Dynamic curves do
* not work for that layout type .
* /
getStaticType ( ) {
// Node that 'type' is the edge type, and therefore 'orthogonal' to the layout type.
let type = 'horizontal' ;
if ( ! this . _isVertical ( ) ) {
type = 'vertical' ;
}
return type ;
}
/ * *
* Determine the center position of a branch from the passed list of child nodes
*
* This takes into account the positions of all the child nodes .
* @ param childNodes { array } Array of either child nodes or node id ' s
* @ return { number }
* @ private
* /
_getCenterPosition ( childNodes ) {
let minPos = 1 e9 ;
let maxPos = - 1 e9 ;
for ( let i = 0 ; i < childNodes . length ; i ++ ) {
let childNode ;
if ( childNodes [ i ] . id !== undefined ) {
childNode = childNodes [ i ] ;
} else {
let childNodeId = childNodes [ i ] ;
childNode = this . body . nodes [ childNodeId ] ;
}
let position = this . _getPositionForHierarchy ( childNode ) ;
minPos = Math . min ( minPos , position ) ;
maxPos = Math . max ( maxPos , position ) ;
}
return 0.5 * ( minPos + maxPos ) ;
}
}
export default LayoutEngine ;