Use the Force
Simulating the physical world
Vasco Asturiano
github.com/vasturiano | bl.ocks.org/vasturiano | twitter.com/vastur
September 22-23, 2017 | San Francisco
d3.forceSimulation()
- D3 layout: pure JS
- Manipulate coordinates (x, y) on a data model
- Agnostic on how objects are rendered
- Iterative engine running Verlet velocity integration
- Force "plugins" influence node velocities (vx, vy)
- Intensity cooldown via "alphaDecay"
Lifecycle of a force simulation
F1
F2
...
Fn
Forces
Calc (\(x\),\(y\)) coords
(\(\Delta x = vx\))
alpha < min
stop
y
alpha decay
n
Init nodes
\(x\),\(y\)
\(vx\),\(vy\)
tick
(raf)
(still phyllotaxis)
onTick()
Lifecycle of a force simulation
function tick() {
var i, n = nodes.length, node;
alpha += (alphaTarget - alpha) * alphaDecay;
forces.each(function(force) {
force(alpha);
});
for (i = 0; i < n; ++i) {
node = nodes[i];
if (node.fx == null) node.x += node.vx *= velocityDecay;
else node.x = node.fx, node.vx = 0;
if (node.fy == null) node.y += node.vy *= velocityDecay;
else node.y = node.fy, node.vy = 0;
}
}
F1
F2
...
Fn
Forces
Calc (\(x\),\(y\)) coords
(\(\Delta x = vx\))
alpha < min
stop
y
alpha decay
n
Init nodes
\(x\),\(y\)
\(vx\),\(vy\)
tick
(raf)
(still phyllotaxis)
onTick()
Inside a force
function myForce(alpha) {
const k = alpha * 1e-4;
nodes.forEach(node => {
node.vx -= node.x * k;
node.vy -= node.y * k;
});
}
function myForce(alpha) {
const k = alpha * 1e-4;
nodes.forEach(node => {
node.vx -= node.x * k;
node.vy -= node.y * k;
});
}
const myNodes = [ /* nodes data */ ];
d3.forceSimulation()
.nodes(myNodes)
.force('toZero', myForce)
.on('tick', () => {
// Draw nodes
const dot = domNode
.selectAll('circle')
.data(myNodes);
dot.merge(
dot.enter
.append('circle')
.attr('r', 5)
)
.attr('cx', d => d.x)
.attr('cy', d => d.y);
});
Graph Drawing
d3.forceSimulation()
.force("link", d3.forceLink(links)) // distance: 30
.force("charge", d3.forceManyBody()) // strength: -30
.force("center", d3.forceCenter(width/2, height/2));
d3-force
- d3.forceManyBody()
- d3.forceLink([links])
- d3.forceX([x])
- d3.forceY([y])
- d3.forceCenter([x, y])
- d3.forceRadial(radius, [x], [y]) *
- d3.forceCollide([radius])
positioning
charge
linked
collision
Positioning Forces
Charge forces
-
d3.forceManyBody (core)
- Inverse-linear attraction/repulsion
-
d3.forceMagnetic
- Inverse-square attraction/repulsion
Linked forces
-
d3.forceLink (core)
- Spring-like (linear) symmetrical link
-
d3.forceMagnetic
- Inverse-square asymmetrical link
Collision forces
-
d3.forceCollide (core)
- Prevent circular nodes overlap
-
d3.forceBounce
- Elastic collisions
- Configurable elasticity
-
d3.bboxCollide
- Collision within rectangular shapes
-
d3.forceSurface
- Elastic collision with straight lines
D3 force registry
Compilation of all-things D3-force
Entropy
a
a
a
aaa
a
a
a
Newton's Cradle
a
a
a
aaa
a
a
a
Accretion
a
a
a
aaa
a
a
a
Plasma
a
a
a
aaa
a
a
a
Orbital Trajectory
a
a
a
aaa
a
a
a
Hierarchical Orbits (forceLink)
a
a
a
aaa
a
a
a
Hierarchical Orbits (forceMagnetic)
a
a
a
aaa
a
a
a
Solar System
a
a
a
aaa
a
a
a
Quad Pong
a
a
a
aaa
a
a
a
d3-force-pod - DIY Simulation
d3ForcePod()(document.body)
.genNodes()
.addForce(d3.forceBounce()
.radius(d => d.r)
)
.addForce(d3.forceX()
.x(width/2)
.strength(0.002)
);
a
a
aaa
a
a
a
The lost dimension
- Extended version to support 3D (& 1D)
- Forces manipulate 'x,y,z' according to internal logic
- Drop-in replacement
d3.forceSimulation()
.numDimensions(/* 1, 2 or 3*/)
d3-force-3d
a
a
a
aaa
a
a
a
Internet Topology
a
a
a
aaa
a
a
a
Fabric Data
a
a
a
aaa
a
a
a
Thanks!
and may the force be with you...
Vasco Asturiano
github.com/vasturiano | bl.ocks.org/vasturiano | twitter.com/vastur
<vastur@gmail.com>
Use the Force - d3.unconf 2017
By Vasco Asturiano
Use the Force - d3.unconf 2017
- 2,418