D3.js: Force simulations 4: treemap
So far, I’ve given you an introduction to a few features of the off-the-shelf D3.js forces in these articles
D3.js: Force simulations 4: treemap
So far, I’ve given you an introduction to a few features of the off-the-shelf D3.js forces in these articles
There is lots more documentation and examples to be found on ObservableHQ to explore these further.
In this article, I’m going to walk you through a solution I sometimes implement when a client wants to clearly define the position of nodes by their group.
It’s heavily inspired by force-in-a-box by John Alexis Guerra Gomez and is not a substitute for this or the many great forceCluster examples including Nadieh Bremer’s — it’s just a different approach I sometimes use, either as a solution or a starting point and potentially may be useful to someone else.
It’s also a great example of why D3.js is such a brilliant tool — learn the basics, combine and adapt these to the individual project specifications.

https://observablehq.com/d/221e076aa77263db
What’s going on?
The basic premise here is combining two D3.js functions — treemap + forceSimulation.
As you’ll see in the notebook, the final simulation uses 3 forces
- forceX — group position as defined by the treemap
- forceY — group position as defined by the treemap
- forceCollide — separating the nodes so that they don’t overlap
In this example, I’ve chosen to show the treemap group boundries and labels but you may want to hide these and/or add links or other forces to meet the requirements of your brief.
The dataset
I’ve used countries and flags for the demo. You’ll find more about pattern images in other articles. The dataset is super simple:
type country = {
country: string;
code: string;
region: string;
}
The code is ISO2 and I chose it because it maps to my **go-to site **for flag SVGs.
As you’ll see in the code, I reference the svg as follows:
.attr(
"xlink:href",
(d) => `https://hatscripts.github.io/circle-flags/flags/${d.code}.svg`
)
For the treemap, I reduced the data to a set of unique regions with a country count.
NB: I’ve used the default hierarchy fieldnames name + value here so I can pass it into d3.hierarchy() without any further lines of code.
const regionsWithCount = [
{"name":"Europe","value":47},
{"name":"Americas","value":35},
{"name":"Middle East","value":18},
{"name":"Africa","value":50},
{"name":"Asia-Pacific","value":46}
]
Treemap hierarchy
d3.treemap() expects a hierarchy format which involves a quick conversion
const treeData = { name: "root", children: regionsWithCount };
const root = d3.hierarchy(treeData);
root.sum((d) => d.value);
The 3rd line root.sum((d) => d.value) is CRUCIAL — for the treemap to work, all the descendants need a ‘value’. If you find your treemap isn’t working, this is the first thing to check!
For each parent in the hierarchy, root.sum((d) => d.value) computes a total of the value field of it’s children:
47 + 35 + 18 + 50 + 46 = 196

This will allow the treemap to proportion the areas correctly which will mean our groups will be optimally placed with enough space for the larger + smaller groups to achieve our goal.
Next step, pass the hierarchy into d3.treemap() along with the chart dimensions — in this case the width + height of the svg.
I’ve used some padding for this particular example but it’s optional.
d3.treemap()
.padding(18)
.size([width, height])
(root);
Fetching the Region Positions
There will be different ways of doing this, but I decided to build an object using a reduce which will return the values I need for the simulation when I pass in the regions
const regionPositions = root
.descendants()
.filter((f) => f.depth > 0)
.reduce((acc, treeRoot) => {
// use d3.treemap() results to fetch the width + height of the tree rect
const treeRectWidth = treeRoot.x1 - treeRoot.x0;
const treeRectHeight = treeRoot.y1 - treeRoot.y0;
// calculate the tree rect area
const totalWidthHeight = treeRectWidth + treeRectHeight;
// return the positions
acc[treeRoot.data.name] = {
// start + width/2
xPos: treeRoot.x0 + treeRectWidth / 2,
// start + height/2
yPos: treeRoot.y0 + treeRectHeight / 2,
// width / area (less if tall and thin)
xStrength: (treeRectHeight / totalWidthHeight) * 0.5,
// height / area (less if short and wide)
yStrength: (treeRectWidth / totalWidthHeight) * 0.5
};
return acc;
}, {});
You’ll notice I’ve added a calculation on strength relative to the width or height/area. This makes the cluster oval (landscape or portrait) if the aspect ratio is higher or lower than 1 which will use the space more efficiently.
When combining forceX/forceY with another simulation, you’ll need to adjust the strength so that each force plays it’s part.
0.5 seemed optimal for this dataset.
Defining the Simulation
As mentioned above, there are 3 forces at play in the simulation.
- forceX + forceY — use the object we defined earlier (regionPositions) to set the ‘starting’ x and y position for these position forces.
- forceX + forceY — also use regionPositions to define the appropriate strength based on the proportions of their treemap ‘area’.
- forceCollide — makes sure the nodes don’t overlap once they’ve been placed in their initial positions. I’ve kept the radius tight (x 1.15) as I want a small amount of space but want to keep them clearly clustered. This is the most important force as overlap is not an option so the strength is 1. I’ve gone quite high on the iterations (10) to make sure it’s got time to get it right.
const simulation = d3
.forceSimulation()
.force("x",
d3
.forceX((d) => regionPositions[d.region].xPos)
.strength((d) => regionPositions[d.region].xStrength)
)
.force("y",
d3
.forceY((d) => regionPositions[d.region].yPos)
.strength((d) => regionPositions[d.region].yStrength)
)
.force("collide",
d3
.forceCollide()
.radius(circleRadius * 1.15)
.iterations(10)
.strength(1)
);
Drawing the nodes
You can see how this is done in the notebook. It’s a group data join which is what I always use — more here.
Thanks for reading and please do reach out if you’re looking for a d3 solution — big or small — www.bmdata.co.uk
메타데이터
- post_id
- e5898bfb40ea
- slug
- d3-js-force-simulations-4-treemap-e5898bfb40ea
- url
- https://medium.com/itnext/d3-js-force-simulations-4-treemap-e5898bfb40ea
- canonical_url
- https://medium.com/itnext/d3-js-force-simulations-4-treemap-e5898bfb40ea
- author_url
- https://medium.com/@bryony_17728
- status
- ok
- fetched_at
- 2026-06-16 19:09:56