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,668