Browse Source

clustering added comments, fixed bugs

css_transitions
Alex de Mulder 10 years ago
parent
commit
c875715ee4
3 changed files with 150 additions and 72 deletions
  1. +69
    -31
      src/graph/Graph.js
  2. +5
    -4
      src/graph/Node.js
  3. +76
    -37
      vis.js

+ 69
- 31
src/graph/Graph.js View File

@ -72,8 +72,9 @@ function Graph (container, data, options) {
}
},
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
heightGrowth: 15, // growth factor = ((parent_size + child_size) / parent_size) * heightGrowthFactor
massTransferCoefficient: 0.1 // parent.mass += massTransferCoefficient * child.mass
@ -88,7 +89,7 @@ function Graph (container, data, options) {
this.nodes = {}; // object with Node objects
this.edges = {}; // object with Edge objects
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 currently moving
// TODO: create a counter to keep track on the number of edges having values
@ -149,6 +150,7 @@ function Graph (container, data, options) {
this.setData(data);
};
/**
* 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.
@ -157,22 +159,28 @@ function Graph (container, data, options) {
* @private
*/
Graph.prototype._updateClusters = function() {
var moving_before_clustering = this.moving;
if (this.previous_scale > this.scale) { // zoom out
this._formClusters();
this._formClusters(false);
}
else if (this.previous_scale < this.scale) { // zoom out
this._openClusters();
}
this._updateNodeIndexList();
for (var nid in this.nodes) {
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;
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
* has to be opened based on the current zoom level.
@ -182,10 +190,13 @@ Graph.prototype._updateClusters = function() {
Graph.prototype._openClusters = function() {
for (var i = 0; i < this.node_indices.length; 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.
* If the node contains child nodes, this function is recursively called on the child nodes as well.
@ -197,6 +208,7 @@ Graph.prototype._openClusters = function() {
*/
Graph.prototype._expandClusterNode = function(node, recursive) {
if (node.formation_scale != undefined) {
console.log(this.scale,node.formation_scale)
if (this.scale > node.formation_scale) {
for (var contained_node_id in node.contained_nodes) {
if (node.contained_nodes.hasOwnProperty(contained_node_id)) {
@ -209,11 +221,9 @@ Graph.prototype._expandClusterNode = function(node, recursive) {
node.mass -= this.constants.clustering.massTransferCoefficient * this.nodes[contained_node_id].mass;
// 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
if (recursive == true) {
@ -232,17 +242,23 @@ Graph.prototype._expandClusterNode = function(node, recursive) {
// reset the cluster settings of this node
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 is called only from _updateClusters()
* forceLevelCollapse ignores the length and
*
* @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 dx,dy,length,
@ -266,8 +282,8 @@ Graph.prototype._formClusters = function() {
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
if (edge.to.mass > edge.from.mass) {
@ -284,18 +300,24 @@ Graph.prototype._formClusters = function() {
// 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.
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]];
}
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]];
}
}
}
}
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 is called only from _updateClusters()
@ -305,7 +327,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
* @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
parent_node.contained_nodes[child_node.id] = child_node;
parent_node.contained_edges[child_node.id] = edge;
@ -315,14 +337,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.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.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;
}
};
@ -1832,6 +1868,8 @@ Graph.prototype._calculateForces = function() {
//edgeLength = (edge.from.width + edge.to.width)/2 || edge.length; // TODO: dmin
//edgeLength = 20 + ((edge.from.width + edge.to.width) || 0) / 2;
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);
angle = Math.atan2(dy, dx);
@ -1938,20 +1976,20 @@ Graph.prototype.start = function() {
graph.timer = undefined;
// benchmark the calculation
var start = window.performance.now();
// var start = window.performance.now();
graph.start();
// Optionally call this twice for faster convergence
// 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();
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);
}

+ 5
- 4
src/graph/Node.js View File

@ -51,11 +51,12 @@ function Node(properties, imagelist, grouplist, constants) {
this.imagelist = imagelist;
this.grouplist = grouplist;
this.setProperties(properties, constants);
// creating the variables for clustering
this.resetCluster();
this.remaining_edges = 0;
this.setProperties(properties, constants);
this.remaining_edges_unapplied = 0;
// mass, force, velocity
this.mass = 50; // kg (mass is adjusted for the number of connected edges)
@ -87,7 +88,7 @@ Node.prototype.attachEdge = function(edge) {
this.edges.push(edge);
}
this.remaining_edges = this.edges.length;
this.remaining_edges_tmp = this.edges.length;
this.remaining_edges_unapplied = this.edges.length;
this._updateMass();
};
@ -101,7 +102,7 @@ Node.prototype.detachEdge = function(edge) {
this.edges.splice(index, 1);
}
this.remaining_edges = this.edges.length;
this.remaining_edges_tmp = this.edges.length;
this.remaining_edges_unapplied = this.edges.length;
this._updateMass();
};

+ 76
- 37
vis.js View File

@ -4,8 +4,8 @@
*
* A dynamic, browser-based visualization library.
*
* @version 0.3.0-SNAPSHOT
* @date 2014-01-08
* @version @@version
* @date @@date
*
* @license
* Copyright (C) 2011-2013 Almende B.V, http://almende.com
@ -12498,11 +12498,12 @@ function Node(properties, imagelist, grouplist, constants) {
this.imagelist = imagelist;
this.grouplist = grouplist;
this.setProperties(properties, constants);
// creating the variables for clustering
this.resetCluster();
this.remaining_edges = 0;
this.setProperties(properties, constants);
this.remaining_edges_unapplied = 0;
// mass, force, velocity
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.remaining_edges = this.edges.length;
this.remaining_edges_tmp = this.edges.length;
this.remaining_edges_unapplied = this.edges.length;
this._updateMass();
};
@ -12548,7 +12549,7 @@ Node.prototype.detachEdge = function(edge) {
this.edges.splice(index, 1);
}
this.remaining_edges = this.edges.length;
this.remaining_edges_tmp = this.edges.length;
this.remaining_edges_unapplied = this.edges.length;
this._updateMass();
};
@ -14125,8 +14126,9 @@ function Graph (container, data, options) {
}
},
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
heightGrowth: 15, // growth factor = ((parent_size + child_size) / parent_size) * heightGrowthFactor
massTransferCoefficient: 0.1 // parent.mass += massTransferCoefficient * child.mass
@ -14141,7 +14143,7 @@ function Graph (container, data, options) {
this.nodes = {}; // object with Node objects
this.edges = {}; // object with Edge objects
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 currently moving
// 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 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.
@ -14210,22 +14213,28 @@ function Graph (container, data, options) {
* @private
*/
Graph.prototype._updateClusters = function() {
var moving_before_clustering = this.moving;
if (this.previous_scale > this.scale) { // zoom out
this._formClusters();
this._formClusters(false);
}
else if (this.previous_scale < this.scale) { // zoom out
this._openClusters();
}
this._updateNodeIndexList();
for (var nid in this.nodes) {
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;
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
* has to be opened based on the current zoom level.
@ -14235,10 +14244,13 @@ Graph.prototype._updateClusters = function() {
Graph.prototype._openClusters = function() {
for (var i = 0; i < this.node_indices.length; 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.
* 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) {
if (node.formation_scale != undefined) {
console.log(this.scale,node.formation_scale)
if (this.scale > node.formation_scale) {
for (var contained_node_id in node.contained_nodes) {
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;
// 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
if (recursive == true) {
@ -14285,17 +14296,23 @@ Graph.prototype._expandClusterNode = function(node, recursive) {
// reset the cluster settings of this node
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 is called only from _updateClusters()
* forceLevelCollapse ignores the length and
*
* @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 dx,dy,length,
@ -14319,8 +14336,8 @@ Graph.prototype._formClusters = function() {
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
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.
// This method is non-destructive and does not require a second set of data.
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]];
}
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]];
}
}
}
}
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 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
* @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
parent_node.contained_nodes[child_node.id] = child_node;
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.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.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 = 20 + ((edge.from.width + edge.to.width) || 0) / 2;
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);
angle = Math.atan2(dy, dx);
@ -15991,20 +16030,20 @@ Graph.prototype.start = function() {
graph.timer = undefined;
// benchmark the calculation
var start = window.performance.now();
// var start = window.performance.now();
graph.start();
// Optionally call this twice for faster convergence
// 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();
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);
}

Loading…
Cancel
Save