@ -1,12 +1,108 @@
/ * = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# TODO
- ` edgeReplacedById ` not cleaned up yet on cluster edge removal
- check correct working for everything for clustered clusters ( could use a unit test )
- Handle recursive unclustering on node removal
- ` updateState() ` not complete ; scan TODO ' s there
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
# State Model for Clustering
The total state for clustering is non - trivial . It is useful to have a model
available as to how it works . The following documents the relevant state items .
# # Network State
The following ` network ` - members are relevant to clustering :
- ` body.nodes ` - all nodes actively participating in the network
- ` body.edges ` - same for edges
- ` body.nodeIndices ` - id ' s of nodes that are visible at a given moment
- ` body.edgeIndices ` - same for edges
This includes :
- helper nodes for dragging in ` manipulation `
- helper nodes for edge type ` dynamic `
- cluster nodes and edges
- there may be more than this .
A node / edge may be missing in the ` Indices ` member if :
- it is a helper node
- the node or edge state has option ` hidden ` set
- It is not visible due to clustering
# # Clustering State
For the hashes , the id ' s of the nodes / edges are used as key .
Member ` network.clustering ` contains the following items :
- ` clusteredNodes ` - hash with values : { clusterId : < id of cluster > , node : < node instance > }
- ` clusteredEdges ` - hash with values : restore information for given edge
Due to nesting of clusters , these members can contain cluster nodes and edges as well .
The important thing to note here , is that the clustered nodes and edges also
appear in the members of the cluster nodes . For data update , it is therefore
important to scan these lists as well as the cluster nodes .
# # # Cluster Node
A cluster node has the following extra fields :
- ` isCluster : true ` - indication that this is a cluster node
- ` containedNodes ` - hash of nodes contained in this cluster
- ` containedEdges ` - same for edges
- ` edges ` - hash of cluster edges for this node
* * NOTE : * *
- ` containedEdges ` can also contain edges which are not clustered ; e . g . an edge
connecting two nodes in the same cluster .
# # # Cluster Edge
These are the items in the ` edges ` member of a clustered node . They have the
following relevant members :
- 'clusteringEdgeReplacingIds` - array of id' s of edges replaced by this edge
Note that it ' s possible to nest clusters , so that ` clusteringEdgeReplacingIds `
can contain edge id ' s of other clusters .
# # # Clustered Edge
This is any edge contained by a cluster edge . It gets the following additional
member :
- ` edgeReplacedById ` - id of the cluster edge in which current edge is clustered
=== === === === === === === === === === === === === === === === === === === === === === === === === * /
let util = require ( "../../util" ) ;
var NetworkUtil = require ( '../NetworkUtil' ) . default ;
var Cluster = require ( './components/nodes/Cluster' ) . default ;
var Edge = require ( './components/Edge' ) . default ; // Only needed for check on type!
var Node = require ( './components/Node' ) . default ; // Only needed for check on type!
class ClusterEngine {
constructor ( body ) {
this . body = body ;
this . clusteredNodes = { } ; // Set of all nodes which are in a cluster
this . clusteredEdges = { } ; // Set of all edges replaced by a clustering edge
this . clusteredNodes = { } ; // key: node id, value: { clusterId: <id of cluster>, node: <node instance>}
this . clusteredEdges = { } ; // key: edge id, value: restore information for given edge
this . options = { } ;
this . defaultOptions = { } ;
@ -466,29 +562,7 @@ class ClusterEngine {
// finally put the cluster node into global
this . body . nodes [ clusterNodeProperties . id ] = clusterNode ;
// create the new edges that will connect to the cluster, all self-referencing edges will be added to childEdgesObject here.
this . _createClusterEdges ( childNodesObj , childEdgesObj , clusterNodeProperties , options . clusterEdgeProperties ) ;
// disable the childEdges
for ( let edgeId in childEdgesObj ) {
if ( childEdgesObj . hasOwnProperty ( edgeId ) ) {
if ( this . body . edges [ edgeId ] !== undefined ) {
let edge = this . body . edges [ edgeId ] ;
// cache the options before changing
this . _backupEdgeOptions ( edge ) ;
// disable physics and hide the edge
edge . setOptions ( { physics : false } ) ;
}
}
}
// disable the childNodes
for ( let nodeId in childNodesObj ) {
if ( childNodesObj . hasOwnProperty ( nodeId ) ) {
this . clusteredNodes [ nodeId ] = { clusterId : clusterNodeProperties . id , node : this . body . nodes [ nodeId ] } ;
this . body . nodes [ nodeId ] . setOptions ( { physics : false } ) ;
}
}
this . _clusterEdges ( childNodesObj , childEdgesObj , clusterNodeProperties , options . clusterEdgeProperties ) ;
// set ID to undefined so no duplicates arise
clusterNodeProperties . id = undefined ;
@ -672,10 +746,7 @@ class ClusterEngine {
}
}
edge . cleanup ( ) ;
// this removes the edge from node.edges, which is why edgeIds is formed
edge . disconnect ( ) ;
delete this . body . edges [ edge . id ] ;
edge . remove ( ) ;
}
// handle the releasing of the edges
@ -938,6 +1009,270 @@ class ClusterEngine {
}
/ * *
* Add the passed child nodes and edges to the given cluster node .
*
* @ param childNodes { Object | Node } hash of nodes or single node to add in cluster
* @ param childEdges { Object | Edge } hash of edges or single edge to take into account when clustering
* @ param clusterNode { Node } cluster node to add nodes and edges to
* @ private
* /
_clusterEdges ( childNodes , childEdges , clusterNode , clusterEdgeProperties ) {
if ( childEdges instanceof Edge ) {
let edge = childEdges ;
let obj = { } ;
obj [ edge . id ] = edge ;
childEdges = obj ;
}
if ( childNodes instanceof Node ) {
let node = childNodes ;
let obj = { } ;
obj [ node . id ] = node ;
childNodes = obj ;
}
if ( clusterNode === undefined || clusterNode === null ) {
throw new Error ( "_clusterEdges: parameter clusterNode required" ) ;
}
if ( clusterEdgeProperties === undefined ) {
// Take the required properties from the cluster node
clusterEdgeProperties = clusterNode . clusterEdgeProperties ;
}
// create the new edges that will connect to the cluster.
// All self-referencing edges will be added to childEdges here.
this . _createClusterEdges ( childNodes , childEdges , clusterNode , clusterEdgeProperties ) ;
// disable the childEdges
for ( let edgeId in childEdges ) {
if ( childEdges . hasOwnProperty ( edgeId ) ) {
if ( this . body . edges [ edgeId ] !== undefined ) {
let edge = this . body . edges [ edgeId ] ;
// cache the options before changing
this . _backupEdgeOptions ( edge ) ;
// disable physics and hide the edge
edge . setOptions ( { physics : false } ) ;
}
}
}
// disable the childNodes
for ( let nodeId in childNodes ) {
if ( childNodes . hasOwnProperty ( nodeId ) ) {
this . clusteredNodes [ nodeId ] = { clusterId : clusterNode . id , node : this . body . nodes [ nodeId ] } ;
this . body . nodes [ nodeId ] . setOptions ( { physics : false } ) ;
}
}
}
/ * *
* Determine in which cluster given nodeId resides .
*
* If not in cluster , return undefined .
*
* NOTE : If you know a cleaner way to do this , please enlighten me ( wimrijnders ) .
*
* @ return { Node | undefined } Node instance for cluster , if present
* @ private
* /
_getClusterNodeForNode ( nodeId ) {
if ( nodeId === undefined ) return undefined ;
let clusteredNode = this . clusteredNodes [ nodeId ] ;
// NOTE: If no cluster info found, it should actually be an error
if ( clusteredNode === undefined ) return undefined ;
let clusterId = clusteredNode . clusterId ;
if ( clusterId === undefined ) return undefined ;
return this . body . nodes [ clusterId ] ;
}
/ * *
* Internal helper function for conditionally removing items in array
*
* Done like this because Array . filter ( ) is not fully supported by all IE ' s .
* @ private
* /
_filter ( arr , callback ) {
let ret = [ ] ;
for ( var n in arr ) {
if ( callback ( arr [ n ] ) ) {
ret . push ( arr [ n ] ) ;
}
}
return ret ;
}
/ * *
* Scan all edges for changes in clustering and adjust this if necessary .
*
* Call this ( internally ) after there has been a change in node or edge data .
* /
_updateState ( ) {
// Pre: States of this.body.nodes and this.body.edges consistent
// Pre: this.clusteredNodes and this.clusteredEdge consistent with containedNodes and containedEdges
// of cluster nodes.
let nodeId ;
let edgeId ;
let m , n ;
let deletedNodeIds = [ ] ;
let deletedEdgeIds = [ ] ;
let self = this ;
let eachClusterNode = ( callback ) => {
for ( nodeId in this . body . nodes ) {
let node = this . body . nodes [ nodeId ] ;
if ( node . isCluster !== true ) continue ;
callback ( node ) ;
}
} ;
//
// Remove deleted regular nodes from clustering
//
// Determine the deleted nodes
for ( nodeId in this . clusteredNodes ) {
let node = this . body . nodes [ nodeId ] ;
if ( node === undefined ) {
deletedNodeIds . push ( nodeId ) ;
}
}
// Remove nodes from cluster nodes
eachClusterNode ( function ( clusterNode ) {
for ( n in deletedNodeIds ) {
delete clusterNode . containedNodes [ deletedNodeIds [ n ] ] ;
}
} ) ;
// Remove nodes from cluster list
for ( n in deletedNodeIds ) {
delete this . clusteredNodes [ deletedNodeIds [ n ] ] ;
}
//
// Remove deleted edges from clustering
//
// Add the deleted clustered edges to the list
for ( edgeId in this . clusteredEdges ) {
let edge = this . body . edges [ edgeId ] ;
if ( edge === undefined || ! edge . endPointsValid ( ) ) {
deletedEdgeIds . push ( edgeId ) ;
}
}
// Cluster nodes can also contain edges which are not clustered,
// i.e. nodes 1-2 within cluster with an edge in between.
// So the cluster nodes also need to be scanned for invalid edges
eachClusterNode ( function ( clusterNode ) {
for ( edgeId in clusterNode . containedEdges ) {
let edge = clusterNode . containedEdges [ edgeId ] ;
if ( ! edge . endPointsValid ( ) && deletedEdgeIds . indexOf ( edgeId ) === - 1 ) {
deletedEdgeIds . push ( edgeId ) ;
}
}
} ) ;
// Also scan for cluster edges which need to be removed in the active list.
// Regular edges have been removed beforehand, so this only picks up the cluster edges.
for ( edgeId in this . body . edges ) {
let edge = this . body . edges [ edgeId ] ;
if ( ! edge . endPointsValid ( ) ) {
deletedEdgeIds . push ( edgeId ) ;
}
}
// Remove edges from cluster nodes
eachClusterNode ( function ( clusterNode ) {
for ( n in deletedEdgeIds ) {
let deletedEdgeId = deletedEdgeIds [ n ] ;
delete clusterNode . containedEdges [ deletedEdgeId ] ;
for ( m in clusterNode . edges ) {
let edge = clusterNode . edges [ m ] ;
if ( edge . id === deletedEdgeId ) {
clusterNode . edges [ m ] = null ; // Don't want to directly delete here, because in the loop
continue ;
}
edge . clusteringEdgeReplacingIds = self . _filter ( edge . clusteringEdgeReplacingIds , function ( id ) {
return deletedEdgeIds . indexOf ( id ) === - 1 ;
} ) ;
}
// Clean up the nulls
clusterNode . edges = self . _filter ( clusterNode . edges , function ( item ) { return item !== null } ) ;
}
} ) ;
// Remove from cluster list
for ( n in deletedEdgeIds ) {
delete this . clusteredEdges [ deletedEdgeIds [ n ] ] ;
}
// Remove cluster edges from active list (this.body.edges).
// deletedEdgeIds still contains id of regular edges, but these should all
// be gone upon entering this method
for ( n in deletedEdgeIds ) {
delete this . body . edges [ deletedEdgeIds [ n ] ] ;
}
//
// Check changed cluster state of edges
//
// Iterating over keys here, because edges may be removed in the loop
let ids = Object . keys ( this . body . edges ) ;
for ( n in ids ) {
let edgeId = ids [ n ] ;
let edge = this . body . edges [ edgeId ] ;
let shouldBeClustered = this . _isClusteredNode ( edge . fromId ) || this . _isClusteredNode ( edge . toId ) ;
if ( shouldBeClustered === this . _isClusteredEdge ( edge . id ) ) {
continue ; // all is well
}
if ( shouldBeClustered ) {
// add edge to clustering
let clusterFrom = this . _getClusterNodeForNode ( edge . fromId ) ;
if ( clusterFrom !== undefined ) {
this . _clusterEdges ( this . body . nodes [ edge . fromId ] , edge , clusterFrom ) ;
}
let clusterTo = this . _getClusterNodeForNode ( edge . toId ) ;
if ( clusterTo !== undefined ) {
this . _clusterEdges ( this . body . nodes [ edge . toId ] , edge , clusterTo ) ;
}
// TODO: check that it works for both edges clustered
} else {
// undo clustering for this edge
throw new Error ( 'remove edge from clustering not implemented!' ) ;
}
}
// TODO: Cluster nodes may now be empty or because of selected options may not be allowed to contain 1 node
// Remove these cluster nodes if necessary.
}
/ * *
* Determine if node with given id is part of a cluster .
*