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.
A code sandbox is provided for the final result, but explanations target what's different compared to a classic circular packing based on some concepts described in the network diagram section.
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
value
property of the data item. - All circles are close to each other but do not collide. They are also attracted by the
y=0
horizontal 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 y=0
baseline.
Using d3-force
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
strength
in use is positive. - forceCenter center the whole chart on the canvas.
- forceY aligns bubble on a horizontal line.
Bubble Size
As explained in the bubble chart section, it is very important to have the bubble area being proportional to the numericvalue
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 scaleSqrt()
function.
const sizeScale = scaleSqrt()
.domain([min, max])
.range([BUBBLE_MIN_SIZE, BUBBLE_MAX_SIZE]);
Contact
👋 Hey, I'm Yan and I'm currently working on this project!
Feedback is welcome ❤️. You can fill an issue on Github, drop me a message on Twitter, or even send me an email pasting yan.holtz.data
with gmail.com
. You can also subscribe to the newsletter to know when I publish more content!