Circle Packing with d3-force
This tutorial is a variation around the general introduction to circle packing with react and d3.js. You should probably understand the concepts described there before reading here.
Instead of relying on the
pack() function of d3.js to compute the best node positions, this example suggests to rely on the d3-force plugin to apply physical forces on each node.
Plot and code
Here is the final plot we're trying to achieve here, together with its code:🙇♂️
It is a circular packing chart where all circles represent an item of the dataset.
- The circle area is proportional to the
valueproperty of the data item.
- All circles are close to each other but do not collide. They are also attracted by the
y=0horizontal axis, what aligns them horizontally
To understand how this chart works, you need the concepts described in the Network diagram and Circle pack sections.
A circle packing chart made using the d3-force plugin, with bubbles being attracted by the
This example is actually a variation of a network diagram, but with no links between nodes.
Some physical forces are applied to each node to compute their position through an iterative simulation:
d3.forceSimulation(nodes) .force( 'collide', d3.forceCollide().radius((node) => sizeScale(node.value) + 1) ) .force('charge', d3.forceManyBody().strength(80)) .force('center', d3.forceCenter(width / 2, height / 2)) .force('charge', d3.forceY(0).strength(0.05))
Here is a reminder:
- collide avoids circle overlap. It uses each node radius to make sure there is no collision.
- manyBody makes sure that nodes are attracted one to each other since the
strengthin use is positive.
- forceCenter center the whole chart on the canvas.
- forceY aligns bubble on a horizontal line.
As explained in the bubble chart section, it is very important to have the bubble area being proportional to the numeric
value of the data point.
It is a very common mistake to make the radius proportional to numeric value, which leads to a misleading visualization.
Fortunately, it is very straightforward to scale the bubble appropriately thanks to the
const sizeScale = scaleSqrt() .domain([min, max]) .range([BUBBLE_MIN_SIZE, BUBBLE_MAX_SIZE]);