|
@ -4,8 +4,8 @@ |
|
|
* |
|
|
* |
|
|
* A dynamic, browser-based visualization library. |
|
|
* A dynamic, browser-based visualization library. |
|
|
* |
|
|
* |
|
|
* @version 0.3.0-SNAPSHOT |
|
|
|
|
|
* @date 2014-01-08 |
|
|
|
|
|
|
|
|
* @version @@version |
|
|
|
|
|
* @date @@date |
|
|
* |
|
|
* |
|
|
* @license |
|
|
* @license |
|
|
* Copyright (C) 2011-2013 Almende B.V, http://almende.com
|
|
|
* Copyright (C) 2011-2013 Almende B.V, http://almende.com
|
|
@ -12498,11 +12498,12 @@ function Node(properties, imagelist, grouplist, constants) { |
|
|
this.imagelist = imagelist; |
|
|
this.imagelist = imagelist; |
|
|
this.grouplist = grouplist; |
|
|
this.grouplist = grouplist; |
|
|
|
|
|
|
|
|
|
|
|
this.setProperties(properties, constants); |
|
|
|
|
|
|
|
|
// creating the variables for clustering
|
|
|
// creating the variables for clustering
|
|
|
this.resetCluster(); |
|
|
this.resetCluster(); |
|
|
this.remaining_edges = 0; |
|
|
this.remaining_edges = 0; |
|
|
|
|
|
|
|
|
this.setProperties(properties, constants); |
|
|
|
|
|
|
|
|
this.remaining_edges_unapplied = 0; |
|
|
|
|
|
|
|
|
// mass, force, velocity
|
|
|
// mass, force, velocity
|
|
|
this.mass = 50; // kg (mass is adjusted for the number of connected edges)
|
|
|
this.mass = 50; // kg (mass is adjusted for the number of connected edges)
|
|
@ -12534,7 +12535,7 @@ Node.prototype.attachEdge = function(edge) { |
|
|
this.edges.push(edge); |
|
|
this.edges.push(edge); |
|
|
} |
|
|
} |
|
|
this.remaining_edges = this.edges.length; |
|
|
this.remaining_edges = this.edges.length; |
|
|
this.remaining_edges_tmp = this.edges.length; |
|
|
|
|
|
|
|
|
this.remaining_edges_unapplied = this.edges.length; |
|
|
this._updateMass(); |
|
|
this._updateMass(); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
@ -12548,7 +12549,7 @@ Node.prototype.detachEdge = function(edge) { |
|
|
this.edges.splice(index, 1); |
|
|
this.edges.splice(index, 1); |
|
|
} |
|
|
} |
|
|
this.remaining_edges = this.edges.length; |
|
|
this.remaining_edges = this.edges.length; |
|
|
this.remaining_edges_tmp = this.edges.length; |
|
|
|
|
|
|
|
|
this.remaining_edges_unapplied = this.edges.length; |
|
|
this._updateMass(); |
|
|
this._updateMass(); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
@ -14125,8 +14126,9 @@ function Graph (container, data, options) { |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
clustering: { |
|
|
clustering: { |
|
|
clusterLength: 30, // threshold length for clustering
|
|
|
|
|
|
zoomOffset: 0.1, |
|
|
|
|
|
|
|
|
clusterLength: 50, // threshold length for clustering
|
|
|
|
|
|
fontSizeMultiplier: 2, // how much the fontsize grows
|
|
|
|
|
|
edgeGrowth: 10, |
|
|
widthGrowth: 15, // growth factor = ((parent_size + child_size) / parent_size) * widthGrowthFactor
|
|
|
widthGrowth: 15, // growth factor = ((parent_size + child_size) / parent_size) * widthGrowthFactor
|
|
|
heightGrowth: 15, // growth factor = ((parent_size + child_size) / parent_size) * heightGrowthFactor
|
|
|
heightGrowth: 15, // growth factor = ((parent_size + child_size) / parent_size) * heightGrowthFactor
|
|
|
massTransferCoefficient: 0.1 // parent.mass += massTransferCoefficient * child.mass
|
|
|
massTransferCoefficient: 0.1 // parent.mass += massTransferCoefficient * child.mass
|
|
@ -14141,7 +14143,7 @@ function Graph (container, data, options) { |
|
|
this.nodes = {}; // object with Node objects
|
|
|
this.nodes = {}; // object with Node objects
|
|
|
this.edges = {}; // object with Edge objects
|
|
|
this.edges = {}; // object with Edge objects
|
|
|
this.scale = 1; // defining the global scale variable in the constructor
|
|
|
this.scale = 1; // defining the global scale variable in the constructor
|
|
|
this.previous_scale = 1; // use this to check if the zoom operation is in or out
|
|
|
|
|
|
|
|
|
this.previous_scale = this.scale; // this is used to check if the zoom operation is zooming in or out
|
|
|
// TODO: create a counter to keep track on the number of nodes having values
|
|
|
// TODO: create a counter to keep track on the number of nodes having values
|
|
|
// TODO: create a counter to keep track on the number of nodes currently moving
|
|
|
// TODO: create a counter to keep track on the number of nodes currently moving
|
|
|
// TODO: create a counter to keep track on the number of edges having values
|
|
|
// TODO: create a counter to keep track on the number of edges having values
|
|
@ -14202,6 +14204,7 @@ function Graph (container, data, options) { |
|
|
this.setData(data); |
|
|
this.setData(data); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* This function checks if the zoom action is in or out. |
|
|
* This function checks if the zoom action is in or out. |
|
|
* If out, check if we can form clusters, if in, check if we can open clusters. |
|
|
* If out, check if we can form clusters, if in, check if we can open clusters. |
|
@ -14210,22 +14213,28 @@ function Graph (container, data, options) { |
|
|
* @private |
|
|
* @private |
|
|
*/ |
|
|
*/ |
|
|
Graph.prototype._updateClusters = function() { |
|
|
Graph.prototype._updateClusters = function() { |
|
|
|
|
|
var moving_before_clustering = this.moving; |
|
|
|
|
|
|
|
|
if (this.previous_scale > this.scale) { // zoom out
|
|
|
if (this.previous_scale > this.scale) { // zoom out
|
|
|
this._formClusters(); |
|
|
|
|
|
|
|
|
this._formClusters(false); |
|
|
} |
|
|
} |
|
|
else if (this.previous_scale < this.scale) { // zoom out
|
|
|
else if (this.previous_scale < this.scale) { // zoom out
|
|
|
this._openClusters(); |
|
|
this._openClusters(); |
|
|
} |
|
|
} |
|
|
this._updateNodeIndexList(); |
|
|
|
|
|
|
|
|
|
|
|
for (var nid in this.nodes) { |
|
|
for (var nid in this.nodes) { |
|
|
var node = this.nodes[nid]; |
|
|
var node = this.nodes[nid]; |
|
|
node.label = String(node.remaining_edges); |
|
|
|
|
|
|
|
|
node.label = String(node.remaining_edges).concat(":",String(node.cluster_size)); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
this.previous_scale = this.scale; |
|
|
this.previous_scale = this.scale; |
|
|
|
|
|
|
|
|
|
|
|
if (this.moving != moving_before_clustering) { |
|
|
|
|
|
this.start(); |
|
|
|
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* This function loops over all nodes in the node_indices list. For each node it checks if it is a cluster and if it |
|
|
* This function loops over all nodes in the node_indices list. For each node it checks if it is a cluster and if it |
|
|
* has to be opened based on the current zoom level. |
|
|
* has to be opened based on the current zoom level. |
|
@ -14235,10 +14244,13 @@ Graph.prototype._updateClusters = function() { |
|
|
Graph.prototype._openClusters = function() { |
|
|
Graph.prototype._openClusters = function() { |
|
|
for (var i = 0; i < this.node_indices.length; i++) { |
|
|
for (var i = 0; i < this.node_indices.length; i++) { |
|
|
var node = this.nodes[this.node_indices[i]]; |
|
|
var node = this.nodes[this.node_indices[i]]; |
|
|
this._expandClusterNode(node,true); |
|
|
|
|
|
|
|
|
this._expandClusterNode(node,false); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this._updateNodeIndexList(); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* This function checks if a node has to be opened. This is done by checking the zoom level. |
|
|
* This function checks if a node has to be opened. This is done by checking the zoom level. |
|
|
* If the node contains child nodes, this function is recursively called on the child nodes as well. |
|
|
* If the node contains child nodes, this function is recursively called on the child nodes as well. |
|
@ -14250,6 +14262,7 @@ Graph.prototype._openClusters = function() { |
|
|
*/ |
|
|
*/ |
|
|
Graph.prototype._expandClusterNode = function(node, recursive) { |
|
|
Graph.prototype._expandClusterNode = function(node, recursive) { |
|
|
if (node.formation_scale != undefined) { |
|
|
if (node.formation_scale != undefined) { |
|
|
|
|
|
console.log(this.scale,node.formation_scale) |
|
|
if (this.scale > node.formation_scale) { |
|
|
if (this.scale > node.formation_scale) { |
|
|
for (var contained_node_id in node.contained_nodes) { |
|
|
for (var contained_node_id in node.contained_nodes) { |
|
|
if (node.contained_nodes.hasOwnProperty(contained_node_id)) { |
|
|
if (node.contained_nodes.hasOwnProperty(contained_node_id)) { |
|
@ -14262,11 +14275,9 @@ Graph.prototype._expandClusterNode = function(node, recursive) { |
|
|
node.mass -= this.constants.clustering.massTransferCoefficient * this.nodes[contained_node_id].mass; |
|
|
node.mass -= this.constants.clustering.massTransferCoefficient * this.nodes[contained_node_id].mass; |
|
|
|
|
|
|
|
|
// decrease the size again
|
|
|
// decrease the size again
|
|
|
node.cluster_size -= child_node.cluster_size; |
|
|
|
|
|
var grow_coefficient = this.constants.clustering.zoomOffset + (node.cluster_size + child_node.cluster_size) / node.cluster_size; |
|
|
|
|
|
node.width -= grow_coefficient * this.constants.clustering.widthGrowth; |
|
|
|
|
|
node.height -= grow_coefficient * this.constants.clustering.heightGrowth; |
|
|
|
|
|
node.fontSize -= 1 * child_node.cluster_size; |
|
|
|
|
|
|
|
|
node.width -= child_node.cluster_size * this.constants.clustering.widthGrowth; |
|
|
|
|
|
node.height -= child_node.cluster_size * this.constants.clustering.heightGrowth; |
|
|
|
|
|
node.fontSize -= this.constants.clustering.fontSizeMultiplier * child_node.cluster_size; |
|
|
|
|
|
|
|
|
// check if a further expansion step is possible if recursivity is enabled
|
|
|
// check if a further expansion step is possible if recursivity is enabled
|
|
|
if (recursive == true) { |
|
|
if (recursive == true) { |
|
@ -14285,17 +14296,23 @@ Graph.prototype._expandClusterNode = function(node, recursive) { |
|
|
|
|
|
|
|
|
// reset the cluster settings of this node
|
|
|
// reset the cluster settings of this node
|
|
|
node.resetCluster(); |
|
|
node.resetCluster(); |
|
|
|
|
|
|
|
|
|
|
|
// restart the simulation to reorganise all nodes
|
|
|
|
|
|
this.moving = true; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* This function checks if any nodes at the end of their trees have edges below a threshold length |
|
|
* This function checks if any nodes at the end of their trees have edges below a threshold length |
|
|
* This function is called only from _updateClusters() |
|
|
* This function is called only from _updateClusters() |
|
|
|
|
|
* forceLevelCollapse ignores the length and |
|
|
* |
|
|
* |
|
|
* @private |
|
|
* @private |
|
|
|
|
|
* @param force_level_collapse | Boolean |
|
|
*/ |
|
|
*/ |
|
|
Graph.prototype._formClusters = function() { |
|
|
|
|
|
|
|
|
Graph.prototype._formClusters = function(force_level_collapse) { |
|
|
var min_length = this.constants.clustering.clusterLength/this.scale; |
|
|
var min_length = this.constants.clustering.clusterLength/this.scale; |
|
|
|
|
|
|
|
|
var dx,dy,length, |
|
|
var dx,dy,length, |
|
@ -14319,8 +14336,8 @@ Graph.prototype._formClusters = function() { |
|
|
length = Math.sqrt(dx * dx + dy * dy); |
|
|
length = Math.sqrt(dx * dx + dy * dy); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (length < min_length) { |
|
|
|
|
|
// checking for clustering possiblities
|
|
|
|
|
|
|
|
|
if (length < min_length || force_level_collapse == true) { |
|
|
|
|
|
// checking for clustering possibilities
|
|
|
|
|
|
|
|
|
// first check which node is larger
|
|
|
// first check which node is larger
|
|
|
if (edge.to.mass > edge.from.mass) { |
|
|
if (edge.to.mass > edge.from.mass) { |
|
@ -14337,18 +14354,24 @@ Graph.prototype._formClusters = function() { |
|
|
// clusters. This will also have to be altered in the force calculation and rendering.
|
|
|
// clusters. This will also have to be altered in the force calculation and rendering.
|
|
|
// This method is non-destructive and does not require a second set of data.
|
|
|
// This method is non-destructive and does not require a second set of data.
|
|
|
if (child_node.remaining_edges == 1) { |
|
|
if (child_node.remaining_edges == 1) { |
|
|
this._addToCluster(parent_node,child_node,edge); |
|
|
|
|
|
|
|
|
this._addToCluster(parent_node,child_node,edge,force_level_collapse); |
|
|
delete this.edges[edges_id_array[i]]; |
|
|
delete this.edges[edges_id_array[i]]; |
|
|
} |
|
|
} |
|
|
else if (parent_node.remaining_edges == 1) { |
|
|
else if (parent_node.remaining_edges == 1) { |
|
|
this._addToCluster(child_node,parent_node,edge); |
|
|
|
|
|
|
|
|
this._addToCluster(child_node,parent_node,edge,force_level_collapse); |
|
|
delete this.edges[edges_id_array[i]]; |
|
|
delete this.edges[edges_id_array[i]]; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this._updateNodeIndexList(); |
|
|
|
|
|
|
|
|
|
|
|
if (force_level_collapse == true) |
|
|
|
|
|
this._applyClusterLevel(); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* This function adds the childnode to the parentnode, creating a cluster if it is not already. |
|
|
* This function adds the childnode to the parentnode, creating a cluster if it is not already. |
|
|
* This function is called only from _updateClusters() |
|
|
* This function is called only from _updateClusters() |
|
@ -14358,7 +14381,7 @@ Graph.prototype._formClusters = function() { |
|
|
* @param edge | Edge object: this edge will be deleted from the global this.edges and stored in the parent node |
|
|
* @param edge | Edge object: this edge will be deleted from the global this.edges and stored in the parent node |
|
|
* @private |
|
|
* @private |
|
|
*/ |
|
|
*/ |
|
|
Graph.prototype._addToCluster = function(parent_node, child_node, edge) { |
|
|
|
|
|
|
|
|
Graph.prototype._addToCluster = function(parent_node, child_node, edge, force_level_collapse) { |
|
|
// join child node and edge in parent node
|
|
|
// join child node and edge in parent node
|
|
|
parent_node.contained_nodes[child_node.id] = child_node; |
|
|
parent_node.contained_nodes[child_node.id] = child_node; |
|
|
parent_node.contained_edges[child_node.id] = edge; |
|
|
parent_node.contained_edges[child_node.id] = edge; |
|
@ -14368,14 +14391,28 @@ Graph.prototype._addToCluster = function(parent_node, child_node, edge) { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var grow_coefficient = this.constants.clustering.zoomOffset + (parent_node.cluster_size + child_node.cluster_size) / parent_node.cluster_size; |
|
|
|
|
|
|
|
|
//var grow_coefficient = (parent_node.cluster_size + child_node.cluster_size) / parent_node.cluster_size;
|
|
|
parent_node.mass += this.constants.clustering.massTransferCoefficient * child_node.mass; |
|
|
parent_node.mass += this.constants.clustering.massTransferCoefficient * child_node.mass; |
|
|
parent_node.width += grow_coefficient * this.constants.clustering.widthGrowth; |
|
|
|
|
|
parent_node.height += grow_coefficient * this.constants.clustering.heightGrowth; |
|
|
|
|
|
parent_node.remaining_edges -= 1; |
|
|
|
|
|
|
|
|
parent_node.width += child_node.cluster_size * this.constants.clustering.widthGrowth; |
|
|
|
|
|
parent_node.height += child_node.cluster_size * this.constants.clustering.heightGrowth; |
|
|
parent_node.formation_scale = this.scale; |
|
|
parent_node.formation_scale = this.scale; |
|
|
parent_node.cluster_size += child_node.cluster_size; |
|
|
parent_node.cluster_size += child_node.cluster_size; |
|
|
parent_node.fontSize += 1 * child_node.cluster_size; |
|
|
|
|
|
|
|
|
parent_node.fontSize += this.constants.clustering.fontSizeMultiplier * child_node.cluster_size; |
|
|
|
|
|
if (force_level_collapse == true) |
|
|
|
|
|
parent_node.remaining_edges_unapplied -= 1; |
|
|
|
|
|
else |
|
|
|
|
|
parent_node.remaining_edges -= 1; |
|
|
|
|
|
|
|
|
|
|
|
// restart the simulation to reorganise all nodes
|
|
|
|
|
|
this.moving = true; |
|
|
|
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
Graph.prototype._applyClusterLevel = function() { |
|
|
|
|
|
for (var i = 0; i < this.node_indices.length; i++) { |
|
|
|
|
|
var node = this.nodes[this.node_indices[i]]; |
|
|
|
|
|
node.remaining_edges = node.remaining_edges_unapplied; |
|
|
|
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -15885,6 +15922,8 @@ Graph.prototype._calculateForces = function() { |
|
|
//edgeLength = (edge.from.width + edge.to.width)/2 || edge.length; // TODO: dmin
|
|
|
//edgeLength = (edge.from.width + edge.to.width)/2 || edge.length; // TODO: dmin
|
|
|
//edgeLength = 20 + ((edge.from.width + edge.to.width) || 0) / 2;
|
|
|
//edgeLength = 20 + ((edge.from.width + edge.to.width) || 0) / 2;
|
|
|
edgeLength = edge.length; |
|
|
edgeLength = edge.length; |
|
|
|
|
|
// this implies that the edges between big clusters are longer
|
|
|
|
|
|
edgeLength += (edge.to.cluster_size + edge.from.cluster_size - 2) * this.constants.clustering.edgeGrowth; |
|
|
length = Math.sqrt(dx * dx + dy * dy); |
|
|
length = Math.sqrt(dx * dx + dy * dy); |
|
|
angle = Math.atan2(dy, dx); |
|
|
angle = Math.atan2(dy, dx); |
|
|
|
|
|
|
|
@ -15991,20 +16030,20 @@ Graph.prototype.start = function() { |
|
|
graph.timer = undefined; |
|
|
graph.timer = undefined; |
|
|
|
|
|
|
|
|
// benchmark the calculation
|
|
|
// benchmark the calculation
|
|
|
var start = window.performance.now(); |
|
|
|
|
|
|
|
|
// var start = window.performance.now();
|
|
|
graph.start(); |
|
|
graph.start(); |
|
|
// Optionally call this twice for faster convergence
|
|
|
// Optionally call this twice for faster convergence
|
|
|
// graph.start();
|
|
|
// graph.start();
|
|
|
var end = window.performance.now(); |
|
|
|
|
|
var time = end - start; |
|
|
|
|
|
// console.log('Simulation time: ' + time);
|
|
|
|
|
|
|
|
|
// var end = window.performance.now();
|
|
|
|
|
|
// var time = end - start;
|
|
|
|
|
|
// console.log('Simulation time: ' + time);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
start = window.performance.now(); |
|
|
|
|
|
|
|
|
// start = window.performance.now();
|
|
|
graph._redraw(); |
|
|
graph._redraw(); |
|
|
end = window.performance.now(); |
|
|
|
|
|
time = end - start; |
|
|
|
|
|
// console.log('Drawing time: ' + time);
|
|
|
|
|
|
|
|
|
// end = window.performance.now();
|
|
|
|
|
|
// time = end - start;
|
|
|
|
|
|
// console.log('Drawing time: ' + time);
|
|
|
|
|
|
|
|
|
}, this.refreshRate); |
|
|
}, this.refreshRate); |
|
|
} |
|
|
} |
|
|