@ -17,7 +17,7 @@ exports.startWithClustering = function() {
// this is called here because if clusterin is disabled, the start and stabilize are called in
// this is called here because if clusterin is disabled, the start and stabilize are called in
// the setData function.
// the setData function.
if ( this . stabilize ) {
if ( this . constants . stabilize == tru e ) {
this . _stabilize ( ) ;
this . _stabilize ( ) ;
}
}
this . start ( ) ;
this . start ( ) ;
@ -32,24 +32,22 @@ exports.startWithClustering = function() {
exports . clusterToFit = function ( maxNumberOfNodes , reposition ) {
exports . clusterToFit = function ( maxNumberOfNodes , reposition ) {
var numberOfNodes = this . nodeIndices . length ;
var numberOfNodes = this . nodeIndices . length ;
var maxLevels = 2 ;
var maxLevels = 50 ;
var level = 0 ;
var level = 0 ;
// we first cluster the hubs, then we pull in the outliers, repeat
// we first cluster the hubs, then we pull in the outliers, repeat
while ( numberOfNodes > maxNumberOfNodes && level < maxLevels ) {
while ( numberOfNodes > maxNumberOfNodes && level < maxLevels ) {
console . log ( "Performing clustering:" , level , numberOfNodes , this . clusterSession ) ;
if ( level % 3 == 0.0 ) {
if ( level % 3 == 0.0 ) {
//this.forceAggregateHubs(true);
//this.normalizeClusterLevels();
this . forceAggregateHubs ( true ) ;
this . normalizeClusterLevels ( ) ;
}
}
else {
else {
//this.increaseClusterLevel(); // this also includes a cluster normalization
this . increaseClusterLevel ( ) ; // this also includes a cluster normalization
}
}
//this.forceAggregateHubs(true);
this . forceAggregateHubs ( true ) ;
numberOfNodes = this . nodeIndices . length ;
numberOfNodes = this . nodeIndices . length ;
level += 1 ;
level += 1 ;
}
}
console . log ( "finished" )
// after the clustering we reposition the nodes to reduce the initial chaos
// after the clustering we reposition the nodes to reduce the initial chaos
if ( level > 0 && reposition == true ) {
if ( level > 0 && reposition == true ) {
@ -59,7 +57,7 @@ exports.clusterToFit = function(maxNumberOfNodes, reposition) {
} ;
} ;
/ * *
/ * *
* This function can be called to open up a specific cluster . It is only called by
* This function can be called to open up a specific cluster .
* It will unpack the cluster back one level .
* It will unpack the cluster back one level .
*
*
* @ param node | Node object : cluster to open .
* @ param node | Node object : cluster to open .
@ -82,9 +80,8 @@ exports.openCluster = function(node) {
else {
else {
this . _expandClusterNode ( node , false , true ) ;
this . _expandClusterNode ( node , false , true ) ;
// update the index list, dynamic edges and labels
// update the index list and labels
this . _updateNodeIndexList ( ) ;
this . _updateNodeIndexList ( ) ;
this . _updateDynamicEdges ( ) ;
this . _updateCalculationNodes ( ) ;
this . _updateCalculationNodes ( ) ;
this . updateLabels ( ) ;
this . updateLabels ( ) ;
}
}
@ -142,18 +139,21 @@ exports.updateClusters = function(zoomDirection,recursive,force,doNotStart) {
var isMovingBeforeClustering = this . moving ;
var isMovingBeforeClustering = this . moving ;
var amountOfNodes = this . nodeIndices . length ;
var amountOfNodes = this . nodeIndices . length ;
var detectedZoomingIn = ( this . previousScale < this . scale && zoomDirection == 0 ) ;
var detectedZoomingOut = ( this . previousScale > this . scale && zoomDirection == 0 ) ;
// on zoom out collapse the sector if the scale is at the level the sector was made
// on zoom out collapse the sector if the scale is at the level the sector was made
if ( this . previousScale > this . scale && zoomDirection == 0 ) {
if ( detectedZoomingOut == true ) {
this . _collapseSector ( ) ;
this . _collapseSector ( ) ;
}
}
// check if we zoom in or out
// check if we zoom in or out
if ( this . previousScale > this . scal e || zoomDirection == - 1 ) { // zoom out
if ( detectedZoomingOut == tru e || zoomDirection == - 1 ) { // zoom out
// forming clusters when forced pulls outliers in. When not forced, the edge length of the
// forming clusters when forced pulls outliers in. When not forced, the edge length of the
// outer nodes determines if it is being clustered
// outer nodes determines if it is being clustered
this . _formClusters ( force ) ;
this . _formClusters ( force ) ;
}
}
else if ( this . previousScale < this . scal e || zoomDirection == 1 ) { // zoom in
else if ( detectedZoomingIn == tru e || zoomDirection == 1 ) { // zoom in
if ( force == true ) {
if ( force == true ) {
// _openClusters checks for each node if the formationScale of the cluster is smaller than
// _openClusters checks for each node if the formationScale of the cluster is smaller than
// the current scale and if so, declusters. When forced, all clusters are reduced by one step
// the current scale and if so, declusters. When forced, all clusters are reduced by one step
@ -161,27 +161,27 @@ exports.updateClusters = function(zoomDirection,recursive,force,doNotStart) {
}
}
else {
else {
// if a cluster takes up a set percentage of the active window
// if a cluster takes up a set percentage of the active window
this . _openClustersBySize ( ) ;
//this._openClustersBySize();
this . _openClusters ( recursive , false ) ;
}
}
}
}
this . _updateNodeIndexList ( ) ;
this . _updateNodeIndexList ( ) ;
// if a cluster was NOT formed and the user zoomed out, we try clustering by hubs
// if a cluster was NOT formed and the user zoomed out, we try clustering by hubs
if ( this . nodeIndices . length == amountOfNodes && ( this . previousScale > this . scal e || zoomDirection == - 1 ) ) {
if ( this . nodeIndices . length == amountOfNodes && ( detectedZoomingOut == tru e || zoomDirection == - 1 ) ) {
this . _aggregateHubs ( force ) ;
this . _aggregateHubs ( force ) ;
this . _updateNodeIndexList ( ) ;
this . _updateNodeIndexList ( ) ;
}
}
// we now reduce chains.
// we now reduce chains.
if ( this . previousScale > this . scal e || zoomDirection == - 1 ) { // zoom out
if ( detectedZoomingOut == tru e || zoomDirection == - 1 ) { // zoom out
this . handleChains ( ) ;
this . handleChains ( ) ;
this . _updateNodeIndexList ( ) ;
this . _updateNodeIndexList ( ) ;
}
}
this . previousScale = this . scale ;
this . previousScale = this . scale ;
// rest of the update the index list, dynamic edges and labels
this . _updateDynamicEdges ( ) ;
// update labels
this . updateLabels ( ) ;
this . updateLabels ( ) ;
// if a cluster was formed, we increase the clusterSession
// if a cluster was formed, we increase the clusterSession
@ -237,10 +237,10 @@ exports.forceAggregateHubs = function(doNotStart) {
// update the index list, dynamic edges and labels
// update the index list, dynamic edges and labels
this . _updateNodeIndexList ( ) ;
this . _updateNodeIndexList ( ) ;
this . _updateCalculationNodes ( ) ;
this . _updateDynamicEdges ( ) ;
this . updateLabels ( ) ;
this . updateLabels ( ) ;
this . _updateCalculationNodes ( ) ;
// if a cluster was formed, we increase the clusterSession
// if a cluster was formed, we increase the clusterSession
if ( this . nodeIndices . length != amountOfNodes ) {
if ( this . nodeIndices . length != amountOfNodes ) {
this . clusterSession += 1 ;
this . clusterSession += 1 ;
@ -260,13 +260,15 @@ exports.forceAggregateHubs = function(doNotStart) {
* @ private
* @ private
* /
* /
exports . _openClustersBySize = function ( ) {
exports . _openClustersBySize = function ( ) {
for ( var nodeId in this . nodes ) {
if ( this . nodes . hasOwnProperty ( nodeId ) ) {
var node = this . nodes [ nodeId ] ;
if ( node . inView ( ) == true ) {
if ( ( node . width * this . scale > this . constants . clustering . screenSizeThreshold * this . frame . canvas . clientWidth ) ||
( node . height * this . scale > this . constants . clustering . screenSizeThreshold * this . frame . canvas . clientHeight ) ) {
this . openCluster ( node ) ;
if ( this . constants . clustering . clusterByZoom == true ) {
for ( var nodeId in this . nodes ) {
if ( this . nodes . hasOwnProperty ( nodeId ) ) {
var node = this . nodes [ nodeId ] ;
if ( node . inView ( ) == true ) {
if ( ( node . width * this . scale > this . constants . clustering . screenSizeThreshold * this . frame . canvas . clientWidth ) ||
( node . height * this . scale > this . constants . clustering . screenSizeThreshold * this . frame . canvas . clientHeight ) ) {
this . openCluster ( node ) ;
}
}
}
}
}
}
}
@ -302,12 +304,12 @@ exports._openClusters = function(recursive,force) {
exports . _expandClusterNode = function ( parentNode , recursive , force , openAll ) {
exports . _expandClusterNode = function ( parentNode , recursive , force , openAll ) {
// first check if node is a cluster
// first check if node is a cluster
if ( parentNode . clusterSize > 1 ) {
if ( parentNode . clusterSize > 1 ) {
// this means that on a double tap event or a zoom event, the cluster fully unpacks if it is smaller than 20
if ( parentNode . clusterSize < this . constants . clustering . sectorThreshold ) {
openAll = true ;
if ( openAll === undefined ) {
openAll = false ;
}
}
recursive = openAll ? true : recursive ;
// this means that on a double tap event or a zoom event, the cluster fully unpacks if it is smaller than 20
recursive = openAll || recursive ;
// if the last child has been added on a smaller scale than current scale decluster
// if the last child has been added on a smaller scale than current scale decluster
if ( parentNode . formationScale < this . scale || force == true ) {
if ( parentNode . formationScale < this . scale || force == true ) {
// we will check if any of the contained child nodes should be removed from the cluster
// we will check if any of the contained child nodes should be removed from the cluster
@ -373,7 +375,6 @@ exports._expelChildFromParent = function(parentNode, containedNodeId, recursive,
parentNode . options . mass -= childNode . options . mass ;
parentNode . options . mass -= childNode . options . mass ;
parentNode . clusterSize -= childNode . clusterSize ;
parentNode . clusterSize -= childNode . clusterSize ;
parentNode . options . fontSize = Math . min ( this . constants . clustering . maxFontSize , this . constants . nodes . fontSize + this . constants . clustering . fontSizeMultiplier * ( parentNode . clusterSize - 1 ) ) ;
parentNode . options . fontSize = Math . min ( this . constants . clustering . maxFontSize , this . constants . nodes . fontSize + this . constants . clustering . fontSizeMultiplier * ( parentNode . clusterSize - 1 ) ) ;
parentNode . dynamicEdgesLength = parentNode . dynamicEdges . length ;
// place the child node near the parent, not at the exact same location to avoid chaos in the system
// place the child node near the parent, not at the exact same location to avoid chaos in the system
childNode . x = parentNode . x + parentNode . growthIndicator * ( 0.5 - Math . random ( ) ) ;
childNode . x = parentNode . x + parentNode . growthIndicator * ( 0.5 - Math . random ( ) ) ;
@ -441,7 +442,9 @@ exports._repositionBezierNodes = function(node) {
* /
* /
exports . _formClusters = function ( force ) {
exports . _formClusters = function ( force ) {
if ( force == false ) {
if ( force == false ) {
this . _formClustersByZoom ( ) ;
if ( this . constants . clustering . clusterByZoom == true ) {
this . _formClustersByZoom ( ) ;
}
}
}
else {
else {
this . _forceClustersByZoom ( ) ;
this . _forceClustersByZoom ( ) ;
@ -455,8 +458,8 @@ exports._formClusters = function(force) {
* @ private
* @ private
* /
* /
exports . _formClustersByZoom = function ( ) {
exports . _formClustersByZoom = function ( ) {
var dx , dy , length ,
minLength = this . constants . clustering . clusterEdgeThreshold / this . scale ;
var dx , dy , length ;
var minLength = this . constants . clustering . clusterEdgeThreshold / this . scale ;
// check if any edges are shorter than minLength and start the clustering
// check if any edges are shorter than minLength and start the clustering
// the clustering favours the node with the larger mass
// the clustering favours the node with the larger mass
@ -479,10 +482,10 @@ exports._formClustersByZoom = function() {
childNode = edge . from ;
childNode = edge . from ;
}
}
if ( childNode . dynamicEdgesL ength == 1 ) {
if ( childNode . dynamicEdges . l ength == 1 ) {
this . _addToCluster ( parentNode , childNode , false ) ;
this . _addToCluster ( parentNode , childNode , false ) ;
}
}
else if ( parentNode . dynamicEdgesL ength == 1 ) {
else if ( parentNode . dynamicEdges . l ength == 1 ) {
this . _addToCluster ( childNode , parentNode , false ) ;
this . _addToCluster ( childNode , parentNode , false ) ;
}
}
}
}
@ -505,8 +508,7 @@ exports._forceClustersByZoom = function() {
var childNode = this . nodes [ nodeId ] ;
var childNode = this . nodes [ nodeId ] ;
// the edges can be swallowed by another decrease
// the edges can be swallowed by another decrease
if ( childNode . dynamicEdgesLength == 1 && childNode . dynamicEdges . length != 0 ) {
if ( childNode . dynamicEdges . length == 1 ) {
var edge = childNode . dynamicEdges [ 0 ] ;
var edge = childNode . dynamicEdges [ 0 ] ;
var parentNode = ( edge . toId == childNode . id ) ? this . nodes [ edge . fromId ] : this . nodes [ edge . toId ] ;
var parentNode = ( edge . toId == childNode . id ) ? this . nodes [ edge . fromId ] : this . nodes [ edge . toId ] ;
// group to the largest node
// group to the largest node
@ -588,14 +590,13 @@ exports._formClusterFromHub = function(hubNode, force, onlyEqual, absorptionSize
if ( absorptionSizeOffset === undefined ) {
if ( absorptionSizeOffset === undefined ) {
absorptionSizeOffset = 0 ;
absorptionSizeOffset = 0 ;
}
}
if ( hubNode . dynamicEdgesLength < 0 ) {
console . error ( hubNode . dynamicEdgesLength , this . hubThreshold , onlyEqual )
}
//this.hubThreshold = 43
//if (hubNode.dynamicEdgesLength < 0) {
// console.error(hubNode.dynamicEdgesLength, this.hubThreshold, onlyEqual)
//}
// we decide if the node is a hub
// we decide if the node is a hub
if ( ( hubNode . dynamicEdgesLength >= this . hubThreshold && onlyEqual == false ) ||
( hubNode . dynamicEdgesLength == this . hubThreshold && onlyEqual == true ) ) {
if ( ( hubNode . dynamicEdges . length >= this . hubThreshold && onlyEqual == false ) ||
( hubNode . dynamicEdges . length == this . hubThreshold && onlyEqual == true ) ) {
// initialize variables
// initialize variables
var dx , dy , length ;
var dx , dy , length ;
var minLength = this . constants . clustering . clusterEdgeThreshold / this . scale ;
var minLength = this . constants . clustering . clusterEdgeThreshold / this . scale ;
@ -651,8 +652,13 @@ exports._formClusterFromHub = function(hubNode, force, onlyEqual, absorptionSize
if ( ( childNode . dynamicEdges . length <= ( this . hubThreshold + absorptionSizeOffset ) ) &&
if ( ( childNode . dynamicEdges . length <= ( this . hubThreshold + absorptionSizeOffset ) ) &&
( childNode . id != hubNode . id ) ) {
( childNode . id != hubNode . id ) ) {
this . _addToCluster ( hubNode , childNode , force ) ;
this . _addToCluster ( hubNode , childNode , force ) ;
}
else {
//console.log("WILL NOT MERGE:",childNode.dynamicEdges.length , (this.hubThreshold + absorptionSizeOffset))
}
}
}
}
}
}
}
}
} ;
} ;
@ -730,40 +736,6 @@ exports._addToCluster = function(parentNode, childNode, force) {
} ;
} ;
/ * *
* This function will apply the changes made to the remainingEdges during the formation of the clusters .
* This is a seperate function to allow for level - wise collapsing of the node barnesHutTree .
* It has to be called if a level is collapsed . It is called by _formClusters ( ) .
* @ private
* /
exports . _updateDynamicEdges = function ( ) {
for ( var i = 0 ; i < this . nodeIndices . length ; i ++ ) {
var node = this . nodes [ this . nodeIndices [ i ] ] ;
node . dynamicEdgesLength = node . dynamicEdges . length ;
// this corrects for multiple edges pointing at the same other node
var correction = 0 ;
if ( node . dynamicEdgesLength > 1 ) {
for ( var j = 0 ; j < node . dynamicEdgesLength - 1 ; j ++ ) {
var edgeToId = node . dynamicEdges [ j ] . toId ;
var edgeFromId = node . dynamicEdges [ j ] . fromId ;
for ( var k = j + 1 ; k < node . dynamicEdgesLength ; k ++ ) {
if ( ( node . dynamicEdges [ k ] . toId == edgeToId && node . dynamicEdges [ k ] . fromId == edgeFromId ) ||
( node . dynamicEdges [ k ] . fromId == edgeToId && node . dynamicEdges [ k ] . toId == edgeFromId ) ) {
correction += 1 ;
}
}
}
}
if ( node . dynamicEdgesLength < correction ) {
console . error ( "overshoot" , node . dynamicEdgesLength , correction )
}
node . dynamicEdgesLength -= correction ;
}
} ;
/ * *
/ * *
* This adds an edge from the childNode to the contained edges of the parent node
* This adds an edge from the childNode to the contained edges of the parent node
*
*
@ -774,7 +746,7 @@ exports._updateDynamicEdges = function() {
* /
* /
exports . _addToContainedEdges = function ( parentNode , childNode , edge ) {
exports . _addToContainedEdges = function ( parentNode , childNode , edge ) {
// create an array object if it does not yet exist for this childNode
// create an array object if it does not yet exist for this childNode
if ( ! ( parentNode . containedEdges . hasOwnProperty ( childNode . id ) ) ) {
if ( parentNode . containedEdges [ childNode . id ] === undefined ) {
parentNode . containedEdges [ childNode . id ] = [ ]
parentNode . containedEdges [ childNode . id ] = [ ]
}
}
// add this edge to the list
// add this edge to the list
@ -813,7 +785,6 @@ exports._connectEdgeToCluster = function(parentNode, childNode, edge) {
edge . toId = parentNode . id ;
edge . toId = parentNode . id ;
}
}
else { // edge connected to other node with the "from" side
else { // edge connected to other node with the "from" side
edge . originalFromId . push ( childNode . id ) ;
edge . originalFromId . push ( childNode . id ) ;
edge . from = parentNode ;
edge . from = parentNode ;
edge . fromId = parentNode . id ;
edge . fromId = parentNode . id ;
@ -989,7 +960,7 @@ exports.updateLabels = function() {
// for (nodeId in this.nodes) {
// for (nodeId in this.nodes) {
// if (this.nodes.hasOwnProperty(nodeId)) {
// if (this.nodes.hasOwnProperty(nodeId)) {
// node = this.nodes[nodeId];
// node = this.nodes[nodeId];
// node.label = String(node.level );
// node.label = String(node.clusterSize + ":" + node.dynamicEdges.length );
// }
// }
// }
// }
@ -1029,7 +1000,6 @@ exports.normalizeClusterLevels = function() {
}
}
}
}
this . _updateNodeIndexList ( ) ;
this . _updateNodeIndexList ( ) ;
this . _updateDynamicEdges ( ) ;
// if a cluster was formed, we increase the clusterSession
// if a cluster was formed, we increase the clusterSession
if ( this . nodeIndices . length != amountOfNodes ) {
if ( this . nodeIndices . length != amountOfNodes ) {
this . clusterSession += 1 ;
this . clusterSession += 1 ;
@ -1090,11 +1060,11 @@ exports._getHubSize = function() {
for ( var i = 0 ; i < this . nodeIndices . length ; i ++ ) {
for ( var i = 0 ; i < this . nodeIndices . length ; i ++ ) {
var node = this . nodes [ this . nodeIndices [ i ] ] ;
var node = this . nodes [ this . nodeIndices [ i ] ] ;
if ( node . dynamicEdgesL ength > largestHub ) {
largestHub = node . dynamicEdgesL ength ;
if ( node . dynamicEdges . l ength > largestHub ) {
largestHub = node . dynamicEdges . l ength;
}
}
average += node . dynamicEdgesL ength ;
averageSquared += Math . pow ( node . dynamicEdgesL ength , 2 ) ;
average += node . dynamicEdges . l ength;
averageSquared += Math . pow ( node . dynamicEdges . l ength, 2 ) ;
hubCounter += 1 ;
hubCounter += 1 ;
}
}
average = average / hubCounter ;
average = average / hubCounter ;
@ -1128,7 +1098,7 @@ exports._reduceAmountOfChains = function(fraction) {
var reduceAmount = Math . floor ( this . nodeIndices . length * fraction ) ;
var reduceAmount = Math . floor ( this . nodeIndices . length * fraction ) ;
for ( var nodeId in this . nodes ) {
for ( var nodeId in this . nodes ) {
if ( this . nodes . hasOwnProperty ( nodeId ) ) {
if ( this . nodes . hasOwnProperty ( nodeId ) ) {
if ( this . nodes [ nodeId ] . dynamicEdgesLength == 2 && this . nodes [ nodeId ] . dynamicEdges . length > = 2 ) {
if ( this . nodes [ nodeId ] . dynamicEdges . length = = 2 ) {
if ( reduceAmount > 0 ) {
if ( reduceAmount > 0 ) {
this . _formClusterFromHub ( this . nodes [ nodeId ] , true , true , 1 ) ;
this . _formClusterFromHub ( this . nodes [ nodeId ] , true , true , 1 ) ;
reduceAmount -= 1 ;
reduceAmount -= 1 ;
@ -1149,7 +1119,7 @@ exports._getChainFraction = function() {
var total = 0 ;
var total = 0 ;
for ( var nodeId in this . nodes ) {
for ( var nodeId in this . nodes ) {
if ( this . nodes . hasOwnProperty ( nodeId ) ) {
if ( this . nodes . hasOwnProperty ( nodeId ) ) {
if ( this . nodes [ nodeId ] . dynamicEdgesLength == 2 && this . nodes [ nodeId ] . dynamicEdges . length > = 2 ) {
if ( this . nodes [ nodeId ] . dynamicEdges . length = = 2 ) {
chains += 1 ;
chains += 1 ;
}
}
total += 1 ;
total += 1 ;