Three treemap libraries, side by side
Same data, three renderers: Plotly.js, D3, and gp-treemap. Each section walks the same canonical hierarchy and translates it into whatever each library wants (labels/parents/values arrays for Plotly and gp-treemap; a nested object for D3). No re-walking, no separate data pipelines — just three different renderings.
Up front: we love Plotly. It's our go-to for everything else, and its treemap defaults are some of the nicest out of the box. This comparison is about scale, not quality.
1. A 24-row toy dataset
Coffee-shop sales by region → category → product. Tiny enough that every library renders comfortably; useful as a baseline for visual style and default behavior.
What's different at this scale
- Plotly ships click-to-zoom and a path bar by default. Each node — leaf and parent — gets its own bordered rect with a label, so you can hover or click any level of the hierarchy.
- D3 is a layout primitive, not a widget — you draw the rects
yourself. The version here uses
paddingTopfor parent label gutters, hover tooltips, and depth-shaded colors; out of the box it's just a layout function and an empty<svg>. - gp-treemap renders the GrandPerspective raised-tile shading on a single canvas. Cell shading shows the tree structure without label gutters, so the data gets every pixel.
The gp-treemap tradeoff: only nodes with their own pixel area get an explicit rect. Interior nodes (folders, categories) don't get a separate "container" rect or a top-strip label — their boundary is implied by the seam-and-shading between children. That means there's no parent rect to click on. Instead, gp-treemap surfaces parents through the breadcrumb at the bottom: hover or click any leaf and the full ancestor path lights up, with the stats bar showing each ancestor's aggregate. Click a breadcrumb segment to focus that ancestor; double-click to zoom to it. (Outside an iframe the mouse wheel also walks the focus up and down the ancestor chain — try the full-page versions linked above.)
Their tradeoff in return: every parent strip Plotly and D3 reserve for a label is pixels that came out of that parent's area. The children inside still fill the rectangle, but the rectangle itself is smaller than the parent's true share of the total. The distortion compounds with depth — at five levels of nesting, a leaf's visible area can be noticeably smaller than its proportional value. gp-treemap doesn't reserve gutters, so cell area is exactly proportional to leaf size all the way down.
2. Disk usage of this repo
17,561 files and folders,
321 MB total — including .git,
which dominates by byte count. Same hierarchy in all three frames.
Why the gap widens
- Plotly creates an SVG node per rect plus per label, and runs a per-text-layout pass on initial render and on every zoom. The cost grows linearly but the per-node constant is high. Above ~10k nodes interaction starts to feel sluggish; above ~100k it can stall the tab.
- D3's layout itself is fast, but bare-SVG rendering bogs
the DOM as the rect count climbs — thousands of
<rect>+<text>elements add up. Workarounds (Canvas, WebGL, virtualized SVG) exist, but they're your code to write. - gp-treemap draws every cell with a per-pixel raised-tile
painter into a single
<canvas>. Paint cost scales with pixels, not nodes — 8M files render in under a second.
3. A 137,262-node dataset
California city-government wages, 2024 (Government Compensation in California), rolled up by department → county → employer → position → individual row. 100,000 rows, 137,262 tree nodes — an order of magnitude bigger than the repo above.
At this scale the divergence is dramatic: Plotly and D3 may take tens of seconds, freeze, or fail to load at all; gp-treemap renders the whole tree in well under a second.
Takeaways
- Plotly is the right choice for a small treemap embedded in a dashboard with the rest of the Plotly toolkit — great defaults, interactive out of the box, but not suited to large hierarchies.
- D3 is the right choice when you want the layout primitive and full control of the render surface. Plan to write your own rendering if you need more than a few thousand cells.
- gp-treemap is the right choice when the tree is large (thousands to millions of nodes) and you want GrandPerspective-style shading. It's a Web Component — drop it in, hand it labels/parents/values, done.
Render times are measured live in your browser — each iframe reports
back when its first paint completes. Regenerate everything with
node scripts/build-comparison.mjs.