Halloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix NowHalloween DealsAmazon USOctober picks are moving - compare before checkoutAmazon US: current deals, useful picks and tech finds.See Picks×
FREEDOM251

Freedom251 › Blog › D3.js

Build a Drill-Down Squarified Treemap from Scratch

By Freedom251 Team · · 9 min read

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A squarified treemap can drill down perfectly well when you separate two jobs: the layout algorithm calculates each node’s rectangle, while your application changes the focused node, redraws its descendants, and provides a way back up. Building both pieces yourself gives you control over hierarchy, sorting, keyboard behavior, labels, animation, and edge cases instead of tying navigation to an undocumented chart default.

What a squarified treemap represents

A treemap turns a hierarchy into nested rectangles. A parent rectangle is partitioned among its children, and each child’s area is proportional to its quantitative value within that parent. Drilling into a node means making that node the new focus and laying out its children as the next visible level; it does not change the underlying values or geometry rules. D3 describes the same model: a treemap assigns every node four edges, x0, y0, x1, and y1, after the hierarchy has been aggregated.

As an Amazon Associate I earn from qualifying purchases.

Use a treemap when part-to-whole relationships matter at several levels: storage by folder, budget by department, incidents by service, or sales by region and product. A large tile means a large value relative to its siblings, not necessarily a large value compared with every tile in the complete data set. Color, labels, and breadcrumbs should supply the identity and context that area alone cannot provide.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The word “squarified” describes the packing objective. Instead of filling a row with very long, thin strips, the algorithm greedily chooses rows whose rectangles have better aspect ratios. The original paper by Mark Bruls, Kees Huizing, and Jarke J. van Wijk was published in 2000; its abstract explains that the standard method often produces “thin, elongated rectangles” that are hard to compare and select, and proposes layouts whose rectangles approximate squares. The paper also discusses shaded frames that make nesting easier to see. Read the Eurographics record for “Squarified Treemaps”.

#1 Best Overall
Sale
Storytelling with Data: A Data Visualization Guide for Business Professionals
  • Wiley
  • Language: english
  • Book - storytelling with data: a data visualization guide for business professionals

The packing algorithm, step by step

1. Build and validate the hierarchy

Represent each item as a node with a children array or a numeric leaf value. Aggregate a parent by summing its descendants. Values used for area must be finite and non-negative. A negative measure has no meaningful rectangle area, so reject it or transform the metric before layout; silently taking an absolute value changes the meaning of the data.

2. Sort siblings

Sort children in descending value before packing. Large items then appear early in the sequence, making the greedy decision more predictable and usually easier to read. If business order is more important than visual packing, retain a separate display order and choose a strip-style layout instead; do not claim that squarification preserves an arbitrary source order.

3. Grow a candidate row

At each recursion level, look at the remaining rectangle and take its shorter side as the row’s limiting dimension. Start a row with the next largest child. Tentatively add the next child and calculate the row’s worst aspect ratio. Keep the child only when the worst ratio improves (or stays equal); otherwise, finalize the current row and begin another.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For a row with item areas aᵢ, let S be their sum, s the available short side, m the smallest area, and M the largest. A common quality test is max((s²M)/S², S²/(s²m)). Lower is better, with 1 representing a square. The test compares candidates; it does not promise that every final tile reaches a chosen ratio.

4. Lay out the row and recurse

Convert the row’s total value to area using the current rectangle’s area divided by the remaining value. The row thickness is row area divided by the short side. If the available rectangle is wider than it is tall, place the row across the width and consume height; otherwise place it down the height and consume width. Recurse into each child’s rectangle. This guarantees that children partition their parent, apart from any intentional padding or rounding.

A small, controllable JavaScript implementation

The following implementation is deliberately explicit. It rejects invalid leaves, sorts positive-value children, collapses zero-value nodes to zero area, and recalculates the scale after every row. It returns coordinates you can render with SVG, Canvas, or HTML.

function aggregate(node) {
  if (Array.isArray(node.children) && node.children.length) {
    node.value = node.children.reduce((sum, child) => sum + aggregate(child), 0);
    return node.value;
  }
  const value = Number(node.value);
  if (!Number.isFinite(value) || value < 0) {
    throw new Error('Leaf values must be finite and non-negative');
  }
  node.value = value;
  return value;
}

function worst(row, shortSide) {
  const values = row.map(d => d.value);
  const sum = values.reduce((a, b) => a + b, 0);
  const min = Math.min(...values);
  const max = Math.max(...values);
  if (sum === 0 || min === 0) return Infinity;
  return Math.max((shortSide * shortSide * max) / (sum * sum),
                  (sum * sum) / (shortSide * shortSide * min));
}

function packChildren(children, x0, y0, x1, y1) {
  const positive = children.filter(d => d.value > 0)
                         .slice().sort((a, b) => b.value - a.value);
  const rectangles = new Map();
  children.filter(d => d.value === 0).forEach(d => rectangles.set(d, [x0, y0, x0, y0]));
  let remaining = positive.slice();
  let X = x0, Y = y0, W = x1 - x0, H = y1 - y0;

  while (remaining.length && W > 0 && H > 0) {
    const total = remaining.reduce((s, d) => s + d.value, 0);
    const scale = (W * H) / total;
    const shortSide = Math.min(W, H);
    const row = [remaining.shift()];
    while (remaining.length) {
      const candidate = remaining[0];
      if (worst(row.concat(candidate), shortSide) <= worst(row, shortSide)) {
        row.push(remaining.shift());
      } else {
        break;
      }
    }

    const rowValue = row.reduce((s, d) => s + d.value, 0);
    const thickness = (rowValue * scale) / shortSide;
    let cursor = W >= H ? X : Y;
    row.forEach(d => {
      const length = (d.value * scale) / thickness;
      if (W >= H) {
        rectangles.set(d, [cursor, Y, cursor + length, Y + thickness]);
        cursor += length;
      } else {
        rectangles.set(d, [X, cursor, X + thickness, cursor + length]);
        cursor += length;
      }
    });
    if (W >= H) { Y += thickness; H -= thickness; }
    else { X += thickness; W -= thickness; }
  }
  return rectangles;
}

function layout(node, x0, y0, x1, y1) {
  node.x0 = x0; node.y0 = y0; node.x1 = x1; node.y1 = y1;
  if (!node.children || !node.children.length || node.value === 0) return;
  const rectangles = packChildren(node.children, x0, y0, x1, y1);
  node.children.forEach(child => {
    const [a, b, c, d] = rectangles.get(child);
    layout(child, a, b, c, d);
  });
}

aggregate(data);
layout(data, 0, 0, 960, 600);

This is a learning-oriented implementation, not a drop-in replacement for every production tiler. Add inner padding by shrinking the rectangle passed to packChildren, and keep the parent’s original coordinates for borders and breadcrumbs. If you round coordinates for Canvas or CSS, round only at draw time; rounding during recursion can create gaps or overlaps that do not exist in the mathematical layout.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Adding drill-down without confusing it with layout

Keep a focus reference in application state. The renderer draws the focused node and its descendants; the layout function remains responsible only for coordinates. A double-click, Enter key, or an explicit “Open” button can set focus to a child. A breadcrumb, an Up button, and the keyboard Up key can set focus to its parent.

let focus = data;
let selected = data;

function open(node) {
  if (!node.children || !node.children.length) return;
  focus = node;
  layout(focus, 0, 0, 960, 600);
  render(focus);                 // your SVG, Canvas, or DOM renderer
  updateBreadcrumb(focus);
}

function close() {
  if (focus.parent) open(focus.parent);
}

svg.addEventListener('dblclick', event => {
  const node = event.target.__data__;
  if (node) open(node);
});

svg.addEventListener('keydown', event => {
  if ((event.key === 'Enter' || event.key === ' ') && selected) open(selected);
  if (event.key === 'ArrowUp') close();
});

Give each rendered tile a focusable element, an accessible name containing its path and value, and a visible focus ring. Announce the new path after navigation so keyboard and screen-reader users know that the view changed. If you animate between levels, keep the old and new focus states available until the transition ends; otherwise clicks can target stale rectangles.

Double-click and keyboard navigation are documented behaviors in one treemap application: its guide describes double-clicking or pressing Down to enter a grouped node, and using another drill action at the bottom or Up to return. That demonstrates a workable interaction model, not a universal convention. Decide whether your product uses single click, double click, keyboard activation, or an explicit control, and document it in the interface. See the TreeMap user guide’s drill-down description.

Using D3 while keeping your own interaction layer

D3 already supplies the geometry. Create a hierarchy, call sum before the layout, optionally sort siblings, then read the assigned edges:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const root = d3.hierarchy(data)
  .sum(d => Math.max(0, Number(d.value) || 0))
  .sort((a, b) => b.value - a.value);

const treemap = d3.treemap()
  .size([960, 600])
  .paddingInner(2);

treemap(root);
root.descendants().forEach(node => {
  // node.x0, node.y0, node.x1, node.y1 are ready to render
});

D3’s default tiling is squarified and uses a golden-ratio target as a hint. You can make the target explicit with d3.treemapSquarify.ratio((1 + Math.sqrt(5)) / 2), but the target is not a guarantee that every rectangle will have that ratio. D3 also exposes tiling as an extension point, so supplying your own function is possible when you need a different ordering or padding policy. Read the D3 treemap API documentation.

Interaction is still yours. D3 returns coordinates; it does not decide which node a double click opens, how focus is announced, or how the user returns to the root. Keeping those concerns separate lets you replace D3’s tiler with the hand-built one above without rewriting navigation.

Choose a tiling strategy deliberately

Strategy Shape and readability Ordering or update behavior When it fits
Squarified Greedily seeks rectangles close to square; avoids many long, thin tiles. Fresh packing is optimized for the current values. General hierarchical comparison and selection.
Slice-and-dice Alternating horizontal and vertical slices are simple but can create poor aspect ratios. Easy to reason about and implement. When implementation simplicity outweighs tile shape.
Strip Uses strips while preserving a predictable sequence. Ordering is emphasized over the best possible aspect ratio. Content that must follow a known order.
Resquarify Starts from a squarified arrangement. Preserves prior topology and adjacency for more stable updates, but later layouts are less optimal because only the first layout uses the Bruls et al. squarified algorithm. Animated dashboards where visual continuity matters more than repacking quality.

The trade-off is not “good package versus bad package.” It is shape quality, ordering, and temporal stability versus the interaction behavior you implement around the geometry. D3 documents both squarify and resquarify, while the TreeMap guide describes strip-style ordering as a choice for predictable sequences. Compare D3’s tiling options.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Test the implementation before trusting the picture

Check value conservation

For every internal node, sum child areas and compare that total with the parent’s drawable area after accounting for the padding policy. A discrepancy indicates a scaling or rounding bug, not an unusual data pattern.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Exercise pathological data

  • An empty hierarchy should render an intentional empty state rather than divide by zero.
  • A single child should occupy the parent’s drawable rectangle.
  • Zero-value children should receive a documented zero-area treatment and should not enter the aspect-ratio formula.
  • Negative, NaN, and infinite leaves should be rejected with a useful validation message.
  • One enormous value beside many tiny values should not produce negative widths or heights after repeated subtraction.

Verify interaction state

After drilling down, confirm that the breadcrumb, focus order, keyboard target, labels, and Back/Up action all refer to the same node. Test a leaf: opening it should do nothing or show a clear “no children” response. Test an update while focused inside a branch that was deleted; move focus to the nearest surviving ancestor instead of leaving a blank view.

Measure aspect ratios, not impressions

For every nonzero rectangle, calculate max(width / height, height / width) and inspect the worst values. A golden-ratio target is a preference used during row decisions, not a promise. Narrow shapes can still be unavoidable when one value dominates its siblings or when the parent is extremely wide or tall.

What a hand-built version buys you

Writing the tiler exposes the exact decisions that packaged charts often hide: how invalid values are treated, whether source order or value order wins, where padding is applied, and whether updates repack or preserve adjacency. Writing navigation beside it lets you choose the focus model, URL or application-state integration, breadcrumb semantics, keyboard shortcuts, and accessibility announcements. You can still use a library for scales, text measurement, transitions, or rendering; “by hand” need not mean abandoning every reusable component.

The durable design is the boundary: a pure layout function accepts a hierarchy and rectangle and returns coordinates; an interaction controller changes focus; a renderer turns coordinates into visible, labeled, accessible marks. With that boundary in place, a squarified treemap can teach its algorithm clearly and provide the drill-down workflow your readers actually need.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Quick Recap

SaleBestseller No. 1
Storytelling with Data: A Data Visualization Guide for Business Professionals
Storytelling with Data: A Data Visualization Guide for Business Professionals
Wiley; Language: english; Book - storytelling with data: a data visualization guide for business professionals
$15.74

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

D3.jsData visualizationhierarchical dataJavaScriptsquarified layoutTreeMap

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.