|
/**
|
|
* Calculate the forces the nodes apply on each other based on a repulsion field.
|
|
* This field is linearly approximated.
|
|
*
|
|
* @private
|
|
*/
|
|
exports._calculateNodeForces = function () {
|
|
var dx, dy, angle, distance, fx, fy, combinedClusterSize,
|
|
repulsingForce, node1, node2, i, j;
|
|
|
|
var nodes = this.calculationNodes;
|
|
var nodeIndices = this.calculationNodeIndices;
|
|
|
|
// approximation constants
|
|
var a_base = -2 / 3;
|
|
var b = 4 / 3;
|
|
|
|
// repulsing forces between nodes
|
|
var nodeDistance = this.constants.physics.repulsion.nodeDistance;
|
|
var minimumDistance = nodeDistance;
|
|
|
|
// we loop from i over all but the last entree in the array
|
|
// j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j
|
|
for (i = 0; i < nodeIndices.length - 1; i++) {
|
|
node1 = nodes[nodeIndices[i]];
|
|
for (j = i + 1; j < nodeIndices.length; j++) {
|
|
node2 = nodes[nodeIndices[j]];
|
|
combinedClusterSize = node1.clusterSize + node2.clusterSize - 2;
|
|
|
|
dx = node2.x - node1.x;
|
|
dy = node2.y - node1.y;
|
|
distance = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
minimumDistance = (combinedClusterSize == 0) ? nodeDistance : (nodeDistance * (1 + combinedClusterSize * this.constants.clustering.distanceAmplification));
|
|
var a = a_base / minimumDistance;
|
|
if (distance < 2 * minimumDistance) {
|
|
if (distance < 0.5 * minimumDistance) {
|
|
repulsingForce = 1.0;
|
|
}
|
|
else {
|
|
repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness))
|
|
}
|
|
// amplify the repulsion for clusters.
|
|
repulsingForce *= (combinedClusterSize == 0) ? 1 : 1 + combinedClusterSize * this.constants.clustering.forceAmplification;
|
|
repulsingForce = repulsingForce / Math.max(distance,0.01*minimumDistance);
|
|
|
|
fx = dx * repulsingForce;
|
|
fy = dy * repulsingForce;
|
|
|
|
node1.fx -= fx;
|
|
node1.fy -= fy;
|
|
node2.fx += fx;
|
|
node2.fy += fy;
|
|
|
|
}
|
|
}
|
|
}
|
|
};
|