visjs-network
Version:
A dynamic, browser-based network visualization library.
127 lines (112 loc) • 3.58 kB
JavaScript
/**
* Hierarchical Spring Solver
*/
class HierarchicalSpringSolver {
/**
* @param {Object} body
* @param {{physicsNodeIndices: Array, physicsEdgeIndices: Array, forces: {}, velocities: {}}} physicsBody
* @param {Object} options
*/
constructor(body, physicsBody, options) {
this.body = body
this.physicsBody = physicsBody
this.setOptions(options)
}
/**
*
* @param {Object} options
*/
setOptions(options) {
this.options = options
}
/**
* This function calculates the springforces on the nodes, accounting for the support nodes.
*
* @private
*/
solve() {
var edgeLength, edge
var dx, dy, fx, fy, springForce, distance
var edges = this.body.edges
var factor = 0.5
var edgeIndices = this.physicsBody.physicsEdgeIndices
var nodeIndices = this.physicsBody.physicsNodeIndices
var forces = this.physicsBody.forces
// initialize the spring force counters
for (let i = 0; i < nodeIndices.length; i++) {
let nodeId = nodeIndices[i]
forces[nodeId].springFx = 0
forces[nodeId].springFy = 0
}
// forces caused by the edges, modelled as springs
for (let i = 0; i < edgeIndices.length; i++) {
edge = edges[edgeIndices[i]]
if (edge.connected === true) {
edgeLength =
edge.options.length === undefined
? this.options.springLength
: edge.options.length
dx = edge.from.x - edge.to.x
dy = edge.from.y - edge.to.y
distance = Math.sqrt(dx * dx + dy * dy)
distance = distance === 0 ? 0.01 : distance
// the 1/distance is so the fx and fy can be calculated without sine or cosine.
springForce =
(this.options.springConstant * (edgeLength - distance)) / distance
fx = dx * springForce
fy = dy * springForce
if (edge.to.level != edge.from.level) {
if (forces[edge.toId] !== undefined) {
forces[edge.toId].springFx -= fx
forces[edge.toId].springFy -= fy
}
if (forces[edge.fromId] !== undefined) {
forces[edge.fromId].springFx += fx
forces[edge.fromId].springFy += fy
}
} else {
if (forces[edge.toId] !== undefined) {
forces[edge.toId].x -= factor * fx
forces[edge.toId].y -= factor * fy
}
if (forces[edge.fromId] !== undefined) {
forces[edge.fromId].x += factor * fx
forces[edge.fromId].y += factor * fy
}
}
}
}
// normalize spring forces
springForce = 1
var springFx, springFy
for (let i = 0; i < nodeIndices.length; i++) {
let nodeId = nodeIndices[i]
springFx = Math.min(
springForce,
Math.max(-springForce, forces[nodeId].springFx)
)
springFy = Math.min(
springForce,
Math.max(-springForce, forces[nodeId].springFy)
)
forces[nodeId].x += springFx
forces[nodeId].y += springFy
}
// retain energy balance
var totalFx = 0
var totalFy = 0
for (let i = 0; i < nodeIndices.length; i++) {
let nodeId = nodeIndices[i]
totalFx += forces[nodeId].x
totalFy += forces[nodeId].y
}
var correctionFx = totalFx / nodeIndices.length
var correctionFy = totalFy / nodeIndices.length
for (let i = 0; i < nodeIndices.length; i++) {
let nodeId = nodeIndices[i]
forces[nodeId].x -= correctionFx
forces[nodeId].y -= correctionFy
}
}
}
export default HierarchicalSpringSolver