|
@ -32,22 +32,24 @@ 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 = 50; |
|
|
|
|
|
|
|
|
var maxLevels = 2; |
|
|
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) { |
|
|
if (level % 3 == 0) { |
|
|
|
|
|
this.forceAggregateHubs(true); |
|
|
|
|
|
this.normalizeClusterLevels(); |
|
|
|
|
|
|
|
|
console.log("Performing clustering:", level, numberOfNodes, this.clusterSession); |
|
|
|
|
|
if (level % 3 == 0.0) { |
|
|
|
|
|
//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);
|
|
|
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) { |
|
@ -98,7 +100,7 @@ exports.openCluster = function(node) { |
|
|
* This calls the updateClustes with default arguments |
|
|
* This calls the updateClustes with default arguments |
|
|
*/ |
|
|
*/ |
|
|
exports.updateClustersDefault = function() { |
|
|
exports.updateClustersDefault = function() { |
|
|
if (this.constants.clustering.enabled == true) { |
|
|
|
|
|
|
|
|
if (this.constants.clustering.enabled == true && this.constants.clustering.clusterByZoom == true) { |
|
|
this.updateClusters(0,false,false); |
|
|
this.updateClusters(0,false,false); |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
@ -224,7 +226,7 @@ exports._aggregateHubs = function(force) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* This function is fired by keypress. It forces hubs to form. |
|
|
|
|
|
|
|
|
* This function forces hubs to form. |
|
|
* |
|
|
* |
|
|
*/ |
|
|
*/ |
|
|
exports.forceAggregateHubs = function(doNotStart) { |
|
|
exports.forceAggregateHubs = function(doNotStart) { |
|
@ -235,6 +237,7 @@ 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._updateDynamicEdges(); |
|
|
this.updateLabels(); |
|
|
this.updateLabels(); |
|
|
|
|
|
|
|
@ -347,7 +350,7 @@ exports._expandClusterNode = function(parentNode, recursive, force, openAll) { |
|
|
* @private |
|
|
* @private |
|
|
*/ |
|
|
*/ |
|
|
exports._expelChildFromParent = function(parentNode, containedNodeId, recursive, force, openAll) { |
|
|
exports._expelChildFromParent = function(parentNode, containedNodeId, recursive, force, openAll) { |
|
|
var childNode = parentNode.containedNodes[containedNodeId]; |
|
|
|
|
|
|
|
|
var childNode = parentNode.containedNodes[containedNodeId] |
|
|
|
|
|
|
|
|
// if child node has been added on smaller scale than current, kick out
|
|
|
// if child node has been added on smaller scale than current, kick out
|
|
|
if (childNode.formationScale < this.scale || force == true) { |
|
|
if (childNode.formationScale < this.scale || force == true) { |
|
@ -502,10 +505,10 @@ 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.dynamicEdgesLength == 1 && childNode.dynamicEdges.length != 0) { |
|
|
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
|
|
|
if (childNode.id != parentNode.id) { |
|
|
if (childNode.id != parentNode.id) { |
|
|
if (parentNode.options.mass > childNode.options.mass) { |
|
|
if (parentNode.options.mass > childNode.options.mass) { |
|
@ -585,9 +588,14 @@ 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) |
|
|
|
|
|
} |
|
|
// we decide if the node is a hub
|
|
|
// we decide if the node is a hub
|
|
|
if ((hubNode.dynamicEdgesLength >= this.hubThreshold && onlyEqual == false) || |
|
|
if ((hubNode.dynamicEdgesLength >= this.hubThreshold && onlyEqual == false) || |
|
|
(hubNode.dynamicEdgesLength == this.hubThreshold && onlyEqual == true)) { |
|
|
(hubNode.dynamicEdgesLength == 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; |
|
@ -600,7 +608,7 @@ exports._formClusterFromHub = function(hubNode, force, onlyEqual, absorptionSize |
|
|
edgesIdarray.push(hubNode.dynamicEdges[j].id); |
|
|
edgesIdarray.push(hubNode.dynamicEdges[j].id); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// if the hub clustering is not forces, we check if one of the edges connected
|
|
|
|
|
|
|
|
|
// if the hub clustering is not forced, we check if one of the edges connected
|
|
|
// to a cluster is small enough based on the constants.clustering.clusterEdgeThreshold
|
|
|
// to a cluster is small enough based on the constants.clustering.clusterEdgeThreshold
|
|
|
if (force == false) { |
|
|
if (force == false) { |
|
|
allowCluster = false; |
|
|
allowCluster = false; |
|
@ -625,17 +633,24 @@ exports._formClusterFromHub = function(hubNode, force, onlyEqual, absorptionSize |
|
|
|
|
|
|
|
|
// start the clustering if allowed
|
|
|
// start the clustering if allowed
|
|
|
if ((!force && allowCluster) || force) { |
|
|
if ((!force && allowCluster) || force) { |
|
|
// we loop over all edges INITIALLY connected to this hub
|
|
|
|
|
|
|
|
|
var children = []; |
|
|
|
|
|
var childrenIds = {}; |
|
|
|
|
|
// we loop over all edges INITIALLY connected to this hub to get a list of the childNodes
|
|
|
for (j = 0; j < amountOfInitialEdges; j++) { |
|
|
for (j = 0; j < amountOfInitialEdges; j++) { |
|
|
edge = this.edges[edgesIdarray[j]]; |
|
|
edge = this.edges[edgesIdarray[j]]; |
|
|
// the edge can be clustered by this function in a previous loop
|
|
|
|
|
|
if (edge !== undefined) { |
|
|
|
|
|
var childNode = this.nodes[(edge.fromId == hubNode.id) ? edge.toId : edge.fromId]; |
|
|
|
|
|
// we do not want hubs to merge with other hubs nor do we want to cluster itself.
|
|
|
|
|
|
if ((childNode.dynamicEdges.length <= (this.hubThreshold + absorptionSizeOffset)) && |
|
|
|
|
|
(childNode.id != hubNode.id)) { |
|
|
|
|
|
this._addToCluster(hubNode,childNode,force); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
var childNode = this.nodes[(edge.fromId == hubNode.id) ? edge.toId : edge.fromId]; |
|
|
|
|
|
if (childrenIds[childNode.id] === undefined) { |
|
|
|
|
|
childrenIds[childNode.id] = true; |
|
|
|
|
|
children.push(childNode); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for (j = 0; j < children.length; j++) { |
|
|
|
|
|
var childNode = children[j]; |
|
|
|
|
|
// we do not want hubs to merge with other hubs nor do we want to cluster itself.
|
|
|
|
|
|
if ((childNode.dynamicEdges.length <= (this.hubThreshold + absorptionSizeOffset)) && |
|
|
|
|
|
(childNode.id != hubNode.id)) { |
|
|
|
|
|
this._addToCluster(hubNode,childNode,force); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
@ -655,14 +670,16 @@ exports._formClusterFromHub = function(hubNode, force, onlyEqual, absorptionSize |
|
|
exports._addToCluster = function(parentNode, childNode, force) { |
|
|
exports._addToCluster = function(parentNode, childNode, force) { |
|
|
// join child node in the parent node
|
|
|
// join child node in the parent node
|
|
|
parentNode.containedNodes[childNode.id] = childNode; |
|
|
parentNode.containedNodes[childNode.id] = childNode; |
|
|
|
|
|
|
|
|
|
|
|
//console.log(parentNode.id, childNode.id)
|
|
|
// manage all the edges connected to the child and parent nodes
|
|
|
// manage all the edges connected to the child and parent nodes
|
|
|
for (var i = 0; i < childNode.dynamicEdges.length; i++) { |
|
|
for (var i = 0; i < childNode.dynamicEdges.length; i++) { |
|
|
var edge = childNode.dynamicEdges[i]; |
|
|
var edge = childNode.dynamicEdges[i]; |
|
|
if (edge.toId == parentNode.id || edge.fromId == parentNode.id) { // edge connected to parentNode
|
|
|
if (edge.toId == parentNode.id || edge.fromId == parentNode.id) { // edge connected to parentNode
|
|
|
|
|
|
//console.log("COLLECT",parentNode.id, childNode.id, edge.toId, edge.fromId)
|
|
|
this._addToContainedEdges(parentNode,childNode,edge); |
|
|
this._addToContainedEdges(parentNode,childNode,edge); |
|
|
} |
|
|
} |
|
|
else { |
|
|
else { |
|
|
|
|
|
//console.log("REWIRE",parentNode.id, childNode.id, edge.toId, edge.fromId)
|
|
|
this._connectEdgeToCluster(parentNode,childNode,edge); |
|
|
this._connectEdgeToCluster(parentNode,childNode,edge); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
@ -690,7 +707,6 @@ exports._addToCluster = function(parentNode, childNode, force) { |
|
|
|
|
|
|
|
|
// forced clusters only open from screen size and double tap
|
|
|
// forced clusters only open from screen size and double tap
|
|
|
if (force == true) { |
|
|
if (force == true) { |
|
|
// parentNode.formationScale = Math.pow(1 - (1.0/11.0),this.clusterSession+3);
|
|
|
|
|
|
parentNode.formationScale = 0; |
|
|
parentNode.formationScale = 0; |
|
|
} |
|
|
} |
|
|
else { |
|
|
else { |
|
@ -739,6 +755,10 @@ exports._updateDynamicEdges = function() { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
if (node.dynamicEdgesLength < correction) { |
|
|
|
|
|
console.error("overshoot", node.dynamicEdgesLength, correction) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
node.dynamicEdgesLength -= correction; |
|
|
node.dynamicEdgesLength -= correction; |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
@ -894,12 +914,14 @@ exports._connectEdgeBackToChild = function(parentNode, childNode) { |
|
|
* @private |
|
|
* @private |
|
|
*/ |
|
|
*/ |
|
|
exports._validateEdges = function(parentNode) { |
|
|
exports._validateEdges = function(parentNode) { |
|
|
|
|
|
var dynamicEdges = [] |
|
|
for (var i = 0; i < parentNode.dynamicEdges.length; i++) { |
|
|
for (var i = 0; i < parentNode.dynamicEdges.length; i++) { |
|
|
var edge = parentNode.dynamicEdges[i]; |
|
|
var edge = parentNode.dynamicEdges[i]; |
|
|
if (parentNode.id != edge.toId && parentNode.id != edge.fromId) { |
|
|
|
|
|
parentNode.dynamicEdges.splice(i,1); |
|
|
|
|
|
|
|
|
if (parentNode.id == edge.toId || parentNode.id == edge.fromId) { |
|
|
|
|
|
dynamicEdges.push(edge); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
parentNode.dynamicEdges = dynamicEdges; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|