/** * Calculate the forces the nodes apply on eachother based on a repulsion field. * This field is linearly approximated. * * @private */ exports._calculateNodeForces = function () { var dx, dy, distance, fx, fy, repulsingForce, node1, node2, i, j; var nodes = this.calculationNodes; var nodeIndices = this.calculationNodeIndices; // repulsing forces between nodes var nodeDistance = this.constants.physics.hierarchicalRepulsion.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]]; // nodes only affect nodes on their level if (node1.level == node2.level) { dx = node2.x - node1.x; dy = node2.y - node1.y; distance = Math.sqrt(dx * dx + dy * dy); var steepness = 0.05; if (distance < nodeDistance) { repulsingForce = -Math.pow(steepness*distance,2) + Math.pow(steepness*nodeDistance,2); } else { repulsingForce = 0; } // normalize force with if (distance == 0) { distance = 0.01; } else { repulsingForce = repulsingForce / distance; } fx = dx * repulsingForce; fy = dy * repulsingForce; node1.fx -= fx; node1.fy -= fy; node2.fx += fx; node2.fy += fy; } } } }; /** * this function calculates the effects of the springs in the case of unsmooth curves. * * @private */ exports._calculateHierarchicalSpringForces = function () { var edgeLength, edge, edgeId; var dx, dy, fx, fy, springForce, distance; var edges = this.edges; var nodes = this.calculationNodes; var nodeIndices = this.calculationNodeIndices; for (var i = 0; i < nodeIndices.length; i++) { var node1 = nodes[nodeIndices[i]]; node1.springFx = 0; node1.springFy = 0; } // forces caused by the edges, modelled as springs for (edgeId in edges) { if (edges.hasOwnProperty(edgeId)) { edge = edges[edgeId]; if (edge.connected) { // only calculate forces if nodes are in the same sector if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) { edgeLength = edge.customLength ? edge.length : this.constants.physics.springLength; // this implies that the edges between big clusters are longer edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth; dx = (edge.from.x - edge.to.x); dy = (edge.from.y - edge.to.y); distance = Math.sqrt(dx * dx + dy * dy); if (distance == 0) { distance = 0.01; } // the 1/distance is so the fx and fy can be calculated without sine or cosine. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance; fx = dx * springForce; fy = dy * springForce; if (edge.to.level != edge.from.level) { edge.to.springFx -= fx; edge.to.springFy -= fy; edge.from.springFx += fx; edge.from.springFy += fy; } else { var factor = 0.5; edge.to.fx -= factor*fx; edge.to.fy -= factor*fy; edge.from.fx += factor*fx; edge.from.fy += factor*fy; } } } } } // normalize spring forces var springForce = 1; var springFx, springFy; for (i = 0; i < nodeIndices.length; i++) { var node = nodes[nodeIndices[i]]; springFx = Math.min(springForce,Math.max(-springForce,node.springFx)); springFy = Math.min(springForce,Math.max(-springForce,node.springFy)); node.fx += springFx; node.fy += springFy; } // retain energy balance var totalFx = 0; var totalFy = 0; for (i = 0; i < nodeIndices.length; i++) { var node = nodes[nodeIndices[i]]; totalFx += node.fx; totalFy += node.fy; } var correctionFx = totalFx / nodeIndices.length; var correctionFy = totalFy / nodeIndices.length; for (i = 0; i < nodeIndices.length; i++) { var node = nodes[nodeIndices[i]]; node.fx -= correctionFx; node.fy -= correctionFy; } };