@ -2,34 +2,99 @@
* Created by Alex on 3 / 3 / 2015.
* /
var util = require ( '../../util' ) ;
class LayoutEngine {
constructor ( body ) {
this . body = body ;
this . options = { } ;
this . defaultOptions = {
hierarchical : {
enabled : false ,
levelSeparation : 150 ,
direction : "UD" , // UD, DU, LR, RL
sortMethod : "hubsize" // hubsize, directed
}
}
util . extend ( this . options , this . defaultOptions ) ;
this . hierarchicalLevels = { } ;
this . body . emitter . on ( "_dataChanged" , ( ) => {
this . setupHierarchicalLayout ( ) ;
} )
}
setOptions ( options ) {
setOptions ( options , allOptions ) {
if ( options !== undefined ) {
util . mergeOptions ( this . options , options , 'hierarchical' ) ;
if ( this . options . hierarchical . enabled === true ) {
// make sure the level seperation 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 ;
}
}
else {
if ( this . options . hierarchical . levelSeparation < 0 ) {
this . options . hierarchical . levelSeparation *= - 1 ;
}
}
// because the hierarchical system needs it's own physics and smooth curve settings, we adapt the other options if needed.
return this . adaptAllOptions ( allOptions ) ;
}
}
return allOptions ;
}
positionInitially ( nodesArray ) {
for ( var i = 0 ; i < nodesArray . length ; i ++ ) {
let node = nodesArray [ i ] ;
if ( ( ! node . isFixed ( ) ) && ( node . x === null || node . y === null ) ) {
var radius = 10 * 0.1 * nodesArray . length + 10 ;
var angle = 2 * Math . PI * Math . random ( ) ;
if ( node . options . fixed . x == false ) { node . x = radius * Math . cos ( angle ) ; }
if ( node . options . fixed . x == false ) { node . y = radius * Math . sin ( angle ) ; }
adaptAllOptions ( allOptions ) {
if ( this . options . hierarchical . enabled === true ) {
// set the physics
if ( allOptions . physics === undefined || allOptions . physics === true ) {
allOptions . physics = { solver : 'hierarchicalRepulsion' } ;
}
else if ( options . physics !== false ) {
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' ;
}
// disable smooth curves if nothing is defined. If smooth curves have been turned on, turn them into static smooth curves.
if ( allOptions . edges === undefined ) {
allOptions . edges = { smooth : false } ;
}
else if ( allOptions . edges . smooth === undefined ) {
allOptions . edges . smooth = false ;
}
else {
allOptions . edges . smooth = { enabled : true , dynamic : false , type : type }
}
// force all edges into static smooth curves.
this . body . emitter . emit ( '_forceDisableDynamicCurves' , type ) ;
}
return allOptions ;
}
_resetLevels ( ) {
for ( var nodeId in this . body . nodes ) {
if ( this . body . nodes . hasOwnProperty ( nodeId ) ) {
var node = this . body . nodes [ nodeId ] ;
if ( node . preassignedLevel == false ) {
node . level = - 1 ;
node . hierarchyEnumerated = false ;
positionInitially ( nodesArray ) {
if ( this . options . hierarchical . enabled !== true ) {
for ( let i = 0 ; i < nodesArray . length ; i ++ ) {
let node = nodesArray [ i ] ;
if ( ( ! node . isFixed ( ) ) && ( node . x === undefined || node . y === undefined ) ) {
let radius = 10 * 0.1 * nodesArray . length + 10 ;
let angle = 2 * Math . PI * Math . random ( ) ;
if ( node . options . fixed . x == false ) {
node . x = radius * Math . cos ( angle ) ;
}
if ( node . options . fixed . x == false ) {
node . y = radius * Math . sin ( angle ) ;
}
}
}
}
@ -41,61 +106,58 @@ class LayoutEngine {
*
* @ private
* /
_ setupHierarchicalLayout( ) {
if ( this . constant s. hierarchicalLayout . enabled == true && this . nodeIndices . length > 0 ) {
setupHierarchicalLayout ( ) {
if ( this . option s. hierarchical . enabled == true && this . body . nodeIndices . length > 0 ) {
// get the size of the largest hubs and check if the user has defined a level for a node.
var hubsize = 0 ;
var node , nodeId ;
var definedLevel = false ;
var undefinedLevel = false ;
let hubsize = 0 ;
let node , nodeId ;
let definedLevel = false ;
let undefinedLevel = false ;
this . hierarchicalLevels = { } ;
this . nodeSpacing = 100 ;
for ( nodeId in this . body . nodes ) {
if ( this . body . nodes . hasOwnProperty ( nodeId ) ) {
node = this . body . nodes [ nodeId ] ;
if ( node . level != - 1 ) {
if ( node . options . level !== undefined ) {
definedLevel = true ;
this . hierarchicalLevels [ nodeId ] = node . options . level ;
}
else {
undefinedLevel = true ;
}
if ( hubsize < node . edges . length ) {
hubsize = node . edges . length ;
}
hubsize = hubsize < node . edges . length ? node . edges . length : hubsize ;
}
}
// 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." ) ;
this . zoomExtent ( { duration : 0 } , true , this . constants . clustering . enabled ) ;
if ( ! this . constants . clustering . enabled ) {
this . start ( ) ;
}
return ;
}
else {
// setup the system to use hierarchical method.
this . _changeConstants ( ) ;
//this._changeConstants();
// define levels if undefined by the users. Based on hubsize
if ( undefinedLevel == true ) {
if ( this . constants . hierarchicalLayout . layout == "hubsize" ) {
if ( this . options . hierarchical . sortMethod == "hubsize" ) {
this . _determineLevels ( hubsize ) ;
}
else {
this . _determineLevelsDirected ( false ) ;
else if ( this . options . hierarchical . sortMethod == "directed" || "direction" ) {
this . _determineLevelsDirected ( ) ;
}
}
// check the distribution of the nodes per level.
var distribution = this . _getDistribution ( ) ;
let distribution = this . _getDistribution ( ) ;
// place the nodes on the canvas. This also stablilizes the system. Redraw in started automatically after stabilize.
// place the nodes on the canvas.
this . _placeNodesByHierarchy ( distribution ) ;
}
}
}
/ * *
* This function places the nodes on the canvas based on the hierarchial distribution .
*
@ -103,39 +165,31 @@ class LayoutEngine {
* @ private
* /
_placeNodesByHierarchy ( distribution ) {
var nodeId , node ;
let nodeId , node ;
this . positionedNodes = { } ;
// start placing all the level 0 nodes first. Then recursively position their branches.
for ( var level in distribution ) {
for ( let level in distribution ) {
if ( distribution . hasOwnProperty ( level ) ) {
for ( nodeId in distribution [ level ] . nodes ) {
if ( distribution [ level ] . nodes . hasOwnProperty ( nodeId ) ) {
node = distribution [ level ] . nodes [ nodeId ] ;
if ( this . constants . hierarchicalLayout . direction == "UD" || this . constants . hierarchicalLayout . direction == "DU" ) {
if ( node . xFixed ) {
node . x = distribution [ level ] . minPos ;
node . xFixed = false ;
distribution [ level ] . minPos += distribution [ level ] . nodeSpacing ;
}
node = distribution [ level ] . nodes [ nodeId ] ;
if ( this . options . hierarchical . direction == "UD" || this . options . hierarchical . direction == "DU" ) {
if ( node . x === undefined ) { node . x = distribution [ level ] . distance ; }
distribution [ level ] . distance = node . x + this . nodeSpacing ;
}
else {
if ( node . yFixed ) {
node . y = distribution [ level ] . minPos ;
node . yFixed = false ;
distribution [ level ] . minPos += distribution [ level ] . nodeSpacing ;
}
if ( node . y === undefined ) { node . y = distribution [ level ] . distance ; }
distribution [ level ] . distance = node . y + this . nodeSpacing ;
}
this . _placeBranchNodes ( node . edges , node . id , distribution , node . level ) ;
this . positionedNodes [ nodeId ] = true ;
this . _placeBranchNodes ( node . edges , node . id , distribution , level ) ;
}
}
}
}
// stabilize the system after positioning. This function calls zoomExtent.
this . _stabilize ( ) ;
}
@ -146,49 +200,29 @@ class LayoutEngine {
* @ private
* /
_getDistribution ( ) {
var distribution = { } ;
var nodeId , node , level ;
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.
// 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 ] ;
node . xFixed = true ;
node . yFixed = true ;
if ( this . constants . hierarchicalLayout . direction == "UD" || this . constants . hierarchicalLayout . direction == "DU" ) {
node . y = this . constants . hierarchicalLayout . levelSeparation * node . level ;
if ( this . options . hierarchical . direction == "UD" || this . options . hierarchical . direction == "DU" ) {
node . y = this . options . hierarchical . levelSeparation * this . hierarchicalLevels [ nodeId ] ;
node . options . fixed . y = true ;
}
else {
node . x = this . constants . hierarchicalLayout . levelSeparation * node . level ;
node . x = this . options . hierarchical . levelSeparation * this . hierarchicalLevels [ nodeId ] ;
node . options . fixed . x = true ;
}
if ( distribution [ node . level ] === undefined ) {
distribution [ node . level ] = { amount : 0 , nodes : { } , minPos : 0 , nodeSpacing : 0 } ;
if ( distribution [ this . hierarchicalLevels [ nodeId ] ] === undefined ) {
distribution [ this . hierarchicalLevels [ nodeId ] ] = { amount : 0 , nodes : { } , distance : 0 } ;
}
distribution [ node . level ] . amount += 1 ;
distribution [ node . level ] . nodes [ nodeId ] = node ;
distribution [ this . hierarchicalLevels [ nodeId ] ] . amount += 1 ;
distribution [ this . hierarchicalLevels [ nodeId ] ] . nodes [ nodeId ] = node ;
}
}
// determine the largest amount of nodes of all levels
var maxCount = 0 ;
for ( level in distribution ) {
if ( distribution . hasOwnProperty ( level ) ) {
if ( maxCount < distribution [ level ] . amount ) {
maxCount = distribution [ level ] . amount ;
}
}
}
// set the initial position and spacing of each nodes accordingly
for ( level in distribution ) {
if ( distribution . hasOwnProperty ( level ) ) {
distribution [ level ] . nodeSpacing = ( maxCount + 1 ) * this . constants . hierarchicalLayout . nodeSpacing ;
distribution [ level ] . nodeSpacing /= ( distribution [ level ] . amount + 1 ) ;
distribution [ level ] . minPos = distribution [ level ] . nodeSpacing - ( 0.5 * ( distribution [ level ] . amount + 1 ) * distribution [ level ] . nodeSpacing ) ;
}
}
return distribution ;
}
@ -200,14 +234,14 @@ class LayoutEngine {
* @ private
* /
_determineLevels ( hubsize ) {
var nodeId , node ;
let nodeId , node ;
// determine hubs
for ( nodeId in this . body . nodes ) {
if ( this . body . nodes . hasOwnProperty ( nodeId ) ) {
node = this . body . nodes [ nodeId ] ;
if ( node . edges . length == hubsize ) {
node . level = 0 ;
this . hierarchicalLevels [ nodeId ] = 0 ;
}
}
}
@ -216,13 +250,39 @@ class LayoutEngine {
for ( nodeId in this . body . nodes ) {
if ( this . body . nodes . hasOwnProperty ( nodeId ) ) {
node = this . body . nodes [ nodeId ] ;
if ( node . level == 0 ) {
if ( this . hierarchicalLevels [ nodeId ] == 0 ) {
this . _setLevel ( 1 , node . edges , node . id ) ;
}
}
}
}
/ * *
* this function is called recursively to enumerate the barnches of the largest hubs and give each node a level .
*
* @ param level
* @ param edges
* @ param parentId
* @ private
* /
_setLevel ( level , edges , parentId ) {
for ( let i = 0 ; i < edges . length ; i ++ ) {
let childNode ;
if ( edges [ i ] . toId == parentId ) {
childNode = edges [ i ] . from ;
}
else {
childNode = edges [ i ] . to ;
}
if ( this . hierarchicalLevels [ childNode . id ] === undefined || this . hierarchicalLevels [ childNode . id ] > level ) {
this . hierarchicalLevels [ childNode . id ] = level ;
if ( childNode . edges . length > 1 ) {
this . _setLevel ( level + 1 , childNode . edges , childNode . id ) ;
}
}
}
}
/ * *
@ -232,70 +292,57 @@ class LayoutEngine {
* @ private
* /
_determineLevelsDirected ( ) {
var nodeId , node , firstNode ;
var minLevel = 10000 ;
let nodeId , firstNode ;
let minLevel = 10000 ;
// set first node to source
firstNode = this . body . nodes [ this . nodeIndices [ 0 ] ] ;
firstNode . level = minLevel ;
this . _setLevelDirected ( minLevel , firstNode . edges , firstNode . id ) ;
firstNode = this . body . nodes [ this . body . nodeIndices [ 0 ] ] ;
this . _setLevelDirected ( minLevel , firstNode ) ;
// get the minimum level
for ( nodeId in this . body . nodes ) {
if ( this . body . nodes . hasOwnProperty ( nodeId ) ) {
node = this . body . nodes [ nodeId ] ;
minLevel = node . level < minLevel ? node . level : minLevel ;
minLevel = this . hierarchicalLevels [ nodeId ] < minLevel ? this . hierarchicalLevels [ nodeId ] : minLevel ;
}
}
// subtract the minimum from the set so we have a range starting from 0
for ( nodeId in this . body . nodes ) {
if ( this . body . nodes . hasOwnProperty ( nodeId ) ) {
node = this . body . nodes [ nodeId ] ;
node . level -= minLevel ;
this . hierarchicalLevels [ nodeId ] -= minLevel ;
}
}
}
/ * *
* Since hierarchical layout does not support :
* - smooth curves ( based on the physics ) ,
* - clustering ( based on dynamic node counts )
*
* We disable both features so there will be no problems .
* this function is called recursively to enumerate the branched of the first node and give each node a level based on edge direction
*
* @ param level
* @ param edges
* @ param parentId
* @ private
* /
_changeConstants ( ) {
this . constants . clustering . enabled = false ;
this . constants . physics . barnesHut . enabled = false ;
this . constants . physics . hierarchicalRepulsion . enabled = true ;
this . _loadSelectedForceSolver ( ) ;
if ( this . constants . smoothCurves . enabled == true ) {
this . constants . smoothCurves . dynamic = false ;
}
this . _configureSmoothCurves ( ) ;
_setLevelDirected ( level , node ) {
if ( this . hierarchicalLevels [ node . id ] !== undefined )
return ;
var config = this . constants . hierarchicalLayout ;
config . levelSeparation = Math . abs ( config . levelSeparation ) ;
if ( config . direction == "RL" || config . direction == "DU" ) {
config . levelSeparation *= - 1 ;
}
let childNode ;
this . hierarchicalLevels [ node . id ] = level ;
if ( config . direction == "RL" || config . direction == "LR" ) {
if ( this . constants . smoothCurves . enabled == true ) {
this . constants . smoothCurves . type = "vertical" ;
for ( let i = 0 ; i < node . edges . length ; i ++ ) {
if ( node . edges [ i ] . toId == node . id ) {
childNode = node . edges [ i ] . from ;
this . _setLevelDirected ( level - 1 , childNode ) ;
}
}
else {
if ( this . constants . smoothCurves . enabled == true ) {
this . constants . smoothCurves . type = "horizontal" ;
else {
childNode = node . edges [ i ] . to ;
this . _setLevelDirected ( level + 1 , childNode ) ;
}
}
}
/ * *
* This is a recursively called function to enumerate the branches from the largest hubs and place the nodes
* on a X position that ensures there will be no overlap .
@ -307,119 +354,43 @@ class LayoutEngine {
* @ private
* /
_placeBranchNodes ( edges , parentId , distribution , parentLevel ) {
for ( var i = 0 ; i < edges . length ; i ++ ) {
var childNode = null ;
for ( let i = 0 ; i < edges . length ; i ++ ) {
let childNode = undefined ;
let parentNode = undefined ;
if ( edges [ i ] . toId == parentId ) {
childNode = edges [ i ] . from ;
parentNode = edges [ i ] . to ;
}
else {
childNode = edges [ i ] . to ;
parentNode = edges [ i ] . from ;
}
let childNodeLevel = this . hierarchicalLevels [ childNode . id ] ;
if ( this . positionedNodes [ childNode . id ] === undefined ) {
// if a node is conneceted to another node on the same level (or higher (means lower level))!, this is not handled here.
if ( childNodeLevel > parentLevel ) {
if ( this . options . hierarchical . direction == "UD" || this . options . hierarchical . direction == "DU" ) {
if ( childNode . x === undefined ) {
childNode . x = Math . max ( distribution [ childNodeLevel ] . distance , parentNode . x ) ;
}
distribution [ childNodeLevel ] . distance = childNode . x + this . nodeSpacing ;
this . positionedNodes [ childNode . id ] = true ;
}
else {
if ( childNode . y === undefined ) {
childNode . y = Math . max ( distribution [ childNodeLevel ] . distance , parentNode . y )
}
distribution [ childNodeLevel ] . distance = childNode . y + this . nodeSpacing ;
}
this . positionedNodes [ childNode . id ] = true ;
// if a node is conneceted to another node on the same level (or higher (means lower level))!, this is not handled here.
var nodeMoved = false ;
if ( this . constants . hierarchicalLayout . direction == "UD" || this . constants . hierarchicalLayout . direction == "DU" ) {
if ( childNode . xFixed && childNode . level > parentLevel ) {
childNode . xFixed = false ;
childNode . x = distribution [ childNode . level ] . minPos ;
nodeMoved = true ;
}
}
else {
if ( childNode . yFixed && childNode . level > parentLevel ) {
childNode . yFixed = false ;
childNode . y = distribution [ childNode . level ] . minPos ;
nodeMoved = true ;
}
}
if ( nodeMoved == true ) {
distribution [ childNode . level ] . minPos += distribution [ childNode . level ] . nodeSpacing ;
if ( childNode . edges . length > 1 ) {
this . _placeBranchNodes ( childNode . edges , childNode . id , distribution , childNode . level ) ;
}
}
}
}
/ * *
* this function is called recursively to enumerate the barnches of the largest hubs and give each node a level .
*
* @ param level
* @ param edges
* @ param parentId
* @ private
* /
_setLevel ( level , edges , parentId ) {
for ( var i = 0 ; i < edges . length ; i ++ ) {
var childNode = null ;
if ( edges [ i ] . toId == parentId ) {
childNode = edges [ i ] . from ;
}
else {
childNode = edges [ i ] . to ;
}
if ( childNode . level == - 1 || childNode . level > level ) {
childNode . level = level ;
if ( childNode . edges . length > 1 ) {
this . _setLevel ( level + 1 , childNode . edges , childNode . id ) ;
if ( childNode . edges . length > 1 ) {
this . _placeBranchNodes ( childNode . edges , childNode . id , distribution , childNodeLevel ) ;
}
}
}
}
}
/ * *
* this function is called recursively to enumerate the branched of the first node and give each node a level based on edge direction
*
* @ param level
* @ param edges
* @ param parentId
* @ private
* /
_setLevelDirected ( level , edges , parentId ) {
this . body . nodes [ parentId ] . hierarchyEnumerated = true ;
var childNode , direction ;
for ( var i = 0 ; i < edges . length ; i ++ ) {
direction = 1 ;
if ( edges [ i ] . toId == parentId ) {
childNode = edges [ i ] . from ;
direction = - 1 ;
}
else {
childNode = edges [ i ] . to ;
}
if ( childNode . level == - 1 ) {
childNode . level = level + direction ;
}
}
for ( var i = 0 ; i < edges . length ; i ++ ) {
if ( edges [ i ] . toId == parentId ) { childNode = edges [ i ] . from ; }
else { childNode = edges [ i ] . to ; }
if ( childNode . edges . length > 1 && childNode . hierarchyEnumerated === false ) {
this . _setLevelDirected ( childNode . level , childNode . edges , childNode . id ) ;
}
}
}
/ * *
* Unfix nodes
*
* @ private
* /
_restoreNodes ( ) {
for ( var nodeId in this . body . nodes ) {
if ( this . body . nodes . hasOwnProperty ( nodeId ) ) {
this . body . nodes [ nodeId ] . xFixed = false ;
this . body . nodes [ nodeId ] . yFixed = false ;
}
}
}
}
export default LayoutEngine ;