Force directed graph with layered gravity and physically modeled collisions
An animated SVG force-directed graph displays nodes organized into horizontal layers with custom gravity pulling each node toward its designated row. Collisions between nodes are physically modeled using conservation of momentum and energy equations, with boundary constraints reflecting velocities and a recovery mechanism steering escaped nodes back into view. The d3.js force layout runs on a tick callback, with metrics shown live for alpha, tick time, and frame rate, while user-adjustable inputs control node count, recovery speed, and windup factors for resolving overlaps and un-mixing blocked nodes.
AI-generated descriptionForce Directed Graph with Custom Gravity
Features
<a id="metrics">Metrics display and inputs</a>
The metrics panel across the top of the svg element gives a live display of the layout state. The inputs on the left allow for the number of nodes, the maximum recovery velocity for escaped nodes and the windup factor for clearing overlaps to be adjusted live. The currentalphavalue for the layout is displayed along with instantaneous and averaged tick time and the average frame rate of the layout. Changing the number of nodes re-starts the layout.<a id="gravity">Custom gravity (layers)</a>
The nodes have acymember which is its target y value, thegravityfunction, which is called from theforce.on("tick"...)callback has behaviour to move the nodes toward theircyposition on each tick.<a id="collisions">Collisions between nodes</a> Based on this example but enhanced to accurately model relative "mass" the rebound velocities are solved for using the equations for conservation of momentum and conservation of energy. The "efficiency" of the collisions - as defined by the ratio of the actual final velocities divided by the ideal, perfectly elastic system - is set internally.
<a id="constraints">Boundary constraints and collisions</a>
This behaviour is also included in thegravityfunction and uses basic geometry to reflect the incident velocity in the plane of the boundary. It does this by using the Node velocity API to manipulate thed.pxandd.pyvalues of the nodes. It is possible, however, for nodes to penetrate the boundaries due to limitations in the temporal resolution of the layout.<a id="escaped">Recovering escaped nodes</a>
If a node escapes the boundaries of the bounding box, the velocity is still reflected in the plane of the penetrated boundary. If the escaped node has no velocity (p.q.x/y == p.q.px/py) then the node is steered back toward the boundary, with constant speed, or a random speed (between 0 and recover speed) if it has come to rest. After it is fully recovered, the velocity of the node will be naturally determined by the distance it was last moved by the recovery behaviour.<a id="velocity">Node velocity API</a>
Behaviour is added to the nodes to allow easy setting and getting of node velocity vector. This is accomplished by manipulating the previous value of the node position vector.<a id="velocityindicators">Node velocity indicators</a>
Velocity vector tracers are toggled by a button at the bottom of the graph. A line is simply drawn from the present position to the present position minus the velocity, times the magnification factor, to indicate the node velocity.<a id="quantisation">Quantisation of screen position</a>
It is possible for nodes with different values to be stationary due to quantisation ofd.xandd.pxby the rendering process. This means that decisions in the code based on position may not reflect the rendered state properly. This is fixed by adding a getter to each data point that returns a rounded version ofd.x/yandd.px/y:d.q.x/yandd.q.px/y. The quantised versions are used for the decision making in the boundary collisions and recovery behaviour.<a id="un-mixing">Guaranteed un-mixing of nodes</a>
It's possible for nodes to be blocked from reaching they'recypositions by other, more massive nodes in adjacent layers, in order to overcome this, a frustration factor is applied to the isolated nodes which has the effect of increasing they're effective mass with each tick that they are outside they're designated band. The increase in mass factor per tick is the windup which is internally set to 0.01. As soon as the nodes make it to they're layer they're anxiety is switched back to 1.<a id="overlap">Clearing node overlaps</a>
Smaller nodes can be squashed in the matrix of bigger nodes, because they have mass is insignificant, the matrix of bigger nodes is not moved and the smallest nodes remain overlapped. In order to clear this issue, the overlaps are monitored and the effective mass of the smaller node in the overlapping pair is increased by fear factor, every collision where the overlap increases. The increase per collision is set by the windUp control in the metrics panel. Nodes with a fear greater than one are highlighted with a lighter stroke color. If the overlap decreases, the fear factor is also decreased by the wind-up value.<a id="cooling">Extended cooling time</a>
The cooling time for the force layout is determined by the evolution of itsalphavalue. This is set to 0.1 when the layout is started and is normally reduced by 1% each tick until it reaches 0.005, at which point the layout will stop updating. It is possible to manipulate the cooling rate so that it cools at x% by dividing the current alpha value by the standard factor of 0.99 and multiplying it by (1 - x%) at the end of the tick callback:
force.alpha(a/0.99*(1 - x))
d3 features used
forked from <a href='http://bl.ocks.org/cool-Blue/'>cool-Blue</a>'s block: <a href='http://bl.ocks.org/cool-Blue/e51945135f05d3cba663'>Force directed graph with layered gravity and physically modeled collisions</a>