Browse Source

added force calculation features for clustering

css_transitions
Alex de Mulder 11 years ago
parent
commit
b84ceaf9d7
2 changed files with 44 additions and 16 deletions
  1. +22
    -8
      src/graph/Graph.js
  2. +22
    -8
      vis.js

+ 22
- 8
src/graph/Graph.js View File

@ -72,12 +72,14 @@ function Graph (container, data, options) {
} }
}, },
clustering: { clustering: {
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
clusterLength: 50, // threshold length for clustering
fontSizeMultiplier: 2, // how much the cluster font size grows per node (in px)
forceAmplification: 0.7, // amount of cluster_size between two nodes multiply this value (+1) with the repulsion force
distanceAmplification: 0.2, // amount of cluster_size between two nodes multiply this value (+1) with the repulsion force
edgeGrowth: 10, // amount of cluster_size connected to the edge is multiplied with this and added to edgeLength
widthGrowth: 10, // growth factor = ((parent_size + child_size) / parent_size) * widthGrowthFactor
heightGrowth: 10, // growth factor = ((parent_size + child_size) / parent_size) * heightGrowthFactor
massTransferCoefficient: 0.1 // parent.mass += massTransferCoefficient * child.mass
}, },
minForce: 0.05, minForce: 0.05,
minVelocity: 0.02, // px/s minVelocity: 0.02, // px/s
@ -163,7 +165,7 @@ Graph.prototype.collapseClusterLevel = function() {
/** /**
* This function can be called to decrease the cluster level. This means that the nodes with only one edge connection will * This function can be called to decrease the cluster level. This means that the nodes with only one edge connection will
* be unpacked if they are a cluster. This can be repeated as many times as needed. * be unpacked if they are a cluster. This can be repeated as many times as needed.
* This can be called externally (by a keybind for instance) to look into clusters without zooming.
* This can be called externally (by a key-bind for instance) to look into clusters without zooming.
*/ */
Graph.prototype.expandClusterLevel = function() { Graph.prototype.expandClusterLevel = function() {
for (var i = 0; i < this.node_indices.length; i++) { for (var i = 0; i < this.node_indices.length; i++) {
@ -267,7 +269,7 @@ Graph.prototype._updateNodeLabels = function() {
for (var node_id in this.nodes) { for (var node_id in this.nodes) {
var node = this.nodes[node_id]; var node = this.nodes[node_id];
if (node.cluster_size == 1) { if (node.cluster_size == 1) {
node.label = "[".concat(String(node.cluster_size),"]");
node.label = String(node.id);
} }
} }
}; };
@ -370,6 +372,10 @@ Graph.prototype._expelChildFromParent = function(node, contained_node_id, recurs
node.cluster_size -= child_node.cluster_size; node.cluster_size -= child_node.cluster_size;
node.remaining_edges += 1; node.remaining_edges += 1;
// place the child node near the parent, not at the exact same location to avoid chaos in the system
child_node.x = node.x + Math.floor(Math.random() * node.width);
child_node.y = node.y + Math.floor(Math.random() * node.height);
// remove node from the list // remove node from the list
delete node.contained_nodes[contained_node_id]; delete node.contained_nodes[contained_node_id];
delete node.contained_edges[contained_node_id]; delete node.contained_edges[contained_node_id];
@ -1951,6 +1957,7 @@ Graph.prototype._calculateForces = function() {
var node1 = nodes[this.node_indices[i]]; var node1 = nodes[this.node_indices[i]];
for (var j = i+1; j < this.node_indices.length; j++) { for (var j = i+1; j < this.node_indices.length; j++) {
var node2 = nodes[this.node_indices[j]]; var node2 = nodes[this.node_indices[j]];
var cluster_size = (node1.cluster_size + node2.cluster_size - 2);
dx = node2.x - node1.x; dx = node2.x - node1.x;
dy = node2.y - node1.y; dy = node2.y - node1.y;
distance = Math.sqrt(dx * dx + dy * dy); distance = Math.sqrt(dx * dx + dy * dy);
@ -1966,8 +1973,15 @@ Graph.prototype._calculateForces = function() {
// TODO: correct factor for repulsing force // TODO: correct factor for repulsing force
//repulsingForce = 2 * Math.exp(-5 * (distance * distance) / (dmin * dmin) ); // TODO: customize the repulsing force //repulsingForce = 2 * Math.exp(-5 * (distance * distance) / (dmin * dmin) ); // TODO: customize the repulsing force
//repulsingForce = Math.exp(-1 * (distance * distance) / (dmin * dmin) ); // TODO: customize the repulsing force //repulsingForce = Math.exp(-1 * (distance * distance) / (dmin * dmin) ); // TODO: customize the repulsing force
// clusters have a larger region of influence
minimumDistance = (cluster_size == 0) ? this.constants.nodes.distance : (this.constants.nodes.distance * 1 + cluster_size * this.constants.clustering.distanceAmplification);
repulsingForce = 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness)); // TODO: customize the repulsing force repulsingForce = 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness)); // TODO: customize the repulsing force
} }
// amplify the repulsion for clusters.
repulsingForce *= (cluster_size == 0) ? 1 : 1 + cluster_size * this.constants.clustering.forceAmplification;
fx = Math.cos(angle) * repulsingForce; fx = Math.cos(angle) * repulsingForce;
fy = Math.sin(angle) * repulsingForce; fy = Math.sin(angle) * repulsingForce;

+ 22
- 8
vis.js View File

@ -14127,12 +14127,14 @@ function Graph (container, data, options) {
} }
}, },
clustering: { clustering: {
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
clusterLength: 50, // threshold length for clustering
fontSizeMultiplier: 2, // how much the cluster font size grows per node (in px)
forceAmplification: 0.7, // amount of cluster_size between two nodes multiply this value (+1) with the repulsion force
distanceAmplification: 0.2, // amount of cluster_size between two nodes multiply this value (+1) with the repulsion force
edgeGrowth: 10, // amount of cluster_size connected to the edge is multiplied with this and added to edgeLength
widthGrowth: 10, // growth factor = ((parent_size + child_size) / parent_size) * widthGrowthFactor
heightGrowth: 10, // growth factor = ((parent_size + child_size) / parent_size) * heightGrowthFactor
massTransferCoefficient: 0.1 // parent.mass += massTransferCoefficient * child.mass
}, },
minForce: 0.05, minForce: 0.05,
minVelocity: 0.02, // px/s minVelocity: 0.02, // px/s
@ -14218,7 +14220,7 @@ Graph.prototype.collapseClusterLevel = function() {
/** /**
* This function can be called to decrease the cluster level. This means that the nodes with only one edge connection will * This function can be called to decrease the cluster level. This means that the nodes with only one edge connection will
* be unpacked if they are a cluster. This can be repeated as many times as needed. * be unpacked if they are a cluster. This can be repeated as many times as needed.
* This can be called externally (by a keybind for instance) to look into clusters without zooming.
* This can be called externally (by a key-bind for instance) to look into clusters without zooming.
*/ */
Graph.prototype.expandClusterLevel = function() { Graph.prototype.expandClusterLevel = function() {
for (var i = 0; i < this.node_indices.length; i++) { for (var i = 0; i < this.node_indices.length; i++) {
@ -14322,7 +14324,7 @@ Graph.prototype._updateNodeLabels = function() {
for (var node_id in this.nodes) { for (var node_id in this.nodes) {
var node = this.nodes[node_id]; var node = this.nodes[node_id];
if (node.cluster_size == 1) { if (node.cluster_size == 1) {
node.label = "[".concat(String(node.cluster_size),"]");
node.label = String(node.id);
} }
} }
}; };
@ -14425,6 +14427,10 @@ Graph.prototype._expelChildFromParent = function(node, contained_node_id, recurs
node.cluster_size -= child_node.cluster_size; node.cluster_size -= child_node.cluster_size;
node.remaining_edges += 1; node.remaining_edges += 1;
// place the child node near the parent, not at the exact same location to avoid chaos in the system
child_node.x = node.x + Math.floor(Math.random() * node.width);
child_node.y = node.y + Math.floor(Math.random() * node.height);
// remove node from the list // remove node from the list
delete node.contained_nodes[contained_node_id]; delete node.contained_nodes[contained_node_id];
delete node.contained_edges[contained_node_id]; delete node.contained_edges[contained_node_id];
@ -16006,6 +16012,7 @@ Graph.prototype._calculateForces = function() {
var node1 = nodes[this.node_indices[i]]; var node1 = nodes[this.node_indices[i]];
for (var j = i+1; j < this.node_indices.length; j++) { for (var j = i+1; j < this.node_indices.length; j++) {
var node2 = nodes[this.node_indices[j]]; var node2 = nodes[this.node_indices[j]];
var cluster_size = (node1.cluster_size + node2.cluster_size - 2);
dx = node2.x - node1.x; dx = node2.x - node1.x;
dy = node2.y - node1.y; dy = node2.y - node1.y;
distance = Math.sqrt(dx * dx + dy * dy); distance = Math.sqrt(dx * dx + dy * dy);
@ -16021,8 +16028,15 @@ Graph.prototype._calculateForces = function() {
// TODO: correct factor for repulsing force // TODO: correct factor for repulsing force
//repulsingForce = 2 * Math.exp(-5 * (distance * distance) / (dmin * dmin) ); // TODO: customize the repulsing force //repulsingForce = 2 * Math.exp(-5 * (distance * distance) / (dmin * dmin) ); // TODO: customize the repulsing force
//repulsingForce = Math.exp(-1 * (distance * distance) / (dmin * dmin) ); // TODO: customize the repulsing force //repulsingForce = Math.exp(-1 * (distance * distance) / (dmin * dmin) ); // TODO: customize the repulsing force
// clusters have a larger region of influence
minimumDistance = (cluster_size == 0) ? this.constants.nodes.distance : (this.constants.nodes.distance * 1 + cluster_size * this.constants.clustering.distanceAmplification);
repulsingForce = 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness)); // TODO: customize the repulsing force repulsingForce = 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness)); // TODO: customize the repulsing force
} }
// amplify the repulsion for clusters.
repulsingForce *= (cluster_size == 0) ? 1 : 1 + cluster_size * this.constants.clustering.forceAmplification;
fx = Math.cos(angle) * repulsingForce; fx = Math.cos(angle) * repulsingForce;
fy = Math.sin(angle) * repulsingForce; fy = Math.sin(angle) * repulsingForce;

Loading…
Cancel
Save