Skip to content
Logo Theodo

Introduction to D3js Force Layout

JR Beaudoin5 min read

// <![CDATA[ var w = 580, h = 400, backgroundColor = ‘black’; // ]]>

The code shown below comes from the D3js website and the tons of examples it provides.

What is the force layout?

D3’s layouts are methods that let you easily display complex datasets in relevant charts with little effort.
You can learn more about all of D3’s layouts in the library’s documenation.
The force layout, like all other layouts, provides us with tools that let us arrange our data on a chart. But it does so using physical simulation.
How is physical simulation relevant in dataviz?
Take the messy chart below. All its points are randomly positioned inside the black container. But if you click on that mess…

… it triggers a physical simulation that gives sense to the previous mess, gently revealing the relations between all our datapoints. Noone had to set up the points’ positions. They just figured out where to go by themselves.

Inserting points

Now you understand why D3’s force layout can be useful. Let’s take some time to understand how it works. I will will walk you through a few examples showing what the force layout is made of and why it serves our purpose of displaying complex graphs. But, what’s a graph? A graph, in mathematics, is a set of vertices connected by edges. So, let’s generate a few vertices inside our container. In order to make the following examples interactive, we will add these points by moving our cursor over the workspace instead of, say, loading them from a file (like in the first example above).

There shall be movement

We would like to give these points some movement. We will give each point an impulsion based on our cursor’s current and previous positions. To do so, we set each nodes’ px and py properties with the variation of our cursor’s x and y coordinates.
Based on these properties, the force layout will handle the animation of all the points.

Obeying Newton’s first law, our point float towards infinity… So let’s set up a little friction to prevent them from going too far.

D3 actually sets the friction parameter to 0.9 by default. So setting it yourself is not mandatory.

Our scene is getting messy. There is no interaction between the points so they are overlapping each another. We can fix that by setting a repulsive force that will keep points apart. We do so by using the force layout’s charge parameter and setting it to a negative value.

Once again, D3 sets the charge parameter to -30 by default. Now, our points keep pushing each other far outside our small SVG container. We can prevent this by using another handy tool: the gravity parameter. It simulates a force that attracts all the points towards the center of the container.

I don’t think Mike Bostock has implemented gravity into D3’s force layout for the beauty of science. It allows us to keep our data points floating towards the center of our document instead of spreading randomly. That’s pretty handy!
Gravity defaults to 0.1 so you don’t necessarily have to set it yourself.

In the loop

Ok, now that we have grasped the basics of the force layout, I would like to take a step back and give you a hint of how this works.
The force layout runs a simulation that changes the positions of nodes according to a few laws of physics.
At every step, the new position of a node is computed based on its previous position and the various interactions it has with it’s environment (friction, repulsion, gravity, etc.).
You can pause and resume this simulation whenever you want.
In the next example, the layout is stopped and started at every mouse click by using force.stop() and force.start() methods.

This looks quite good. But our data is not yet very interesting. There actually is no data at all. Let’s add two random properties to each of our nodes: size and value. We will visualize these properties respectively as size and color.

Nodes are overlapping again. How can we fix this? We could set each node’s charge depending on it’s size but the nodes would still overlap for a short time before the layout stabilises. Plus there’s a cooler thing I would like to show you.
We will add a collision detection function.

To achieve this effect, we hooked into the simulation loop, taking advantage of the tick event. This event is triggered at every step of the simulation and allows you to take control of everything that’s happening in the force layout.

force.on("tick", function(e) {
  var nodes = force.nodes();
  var k = e.alpha * 0.1;
  var q = d3.geom.quadtree(nodes),
    i = 0,
    n = nodes.length;
  while (++i < n) q.visit(collide(nodes[i]));
  svgContainer
    .selectAll("circle")
    .attr("cx", function(d) {
      return d.x;
    })
    .attr("cy", function(d) {
      return d.y;
    });
});

As I said earlier, a graph is a set of vertices connected by edges. We have the vertices. Let’s add the edges, or links, as they are called in D3. Every time we insert a new point in the graph below, we randomly connect it to an existing point.

To add a link in the simulation, you have to create an object with two straightforward properties:

link = {
  source: node,
  target: force.nodes()[Math.floor(Math.random() * force.nodes().length)]
};

And then you must insert the new link into the list of links handled by the simulation:

force.links().push(link);

If you wish the new link to show up on your screen, you need to bind it to an SVG line:

svgContainer
  .append("svg:line")
  .data([link])
  .attr("stroke", "red")
  .attr("stroke-width", 1.5);

And finally you need to update each line’s ends on every tick:

svgContainer
  .selectAll("line")
  .attr("x1", function(d) {
    return d.source.x;
  })
  .attr("y1", function(d) {
    return d.source.y;
  })
  .attr("x2", function(d) {
    return d.target.x;
  })
  .attr("y2", function(d) {
    return d.target.y;
  });

Conclusion

I wrote this article after a presentation I gave at a recent Meetup about D3 in Paris. I had also made a quick comparison between svg and canvas when using the force layout. Stay tuned for a short article on that subject in the near future.

Thanks for reading and feel free to leave your comments below!

Liked this article?