Make treemaps from text

Annual Budget ($M)Engineering12 · 49%Sales & Marketing5.8 · 25%Operations3.7 · 16%G&A2.4 · 10%Platform4.2 · 18%Product Eng3.8 · 16%Infrastructure2.1 · 9%Security1.4 · 6%Field Sales3.1 · 13%Demand Gen1.9 · 8%Brand0.8 · 3%Customer Support1.6 · 7%Facilities1.2 · 5%IT0.9 · 4%People1.1 · 5%Finance0.7 · 3%Legal0.6 · 3%BranchEngineeringSales & MarketingOperationsG&A
Annual Budget ($M)Engineering12 · 49%Sales & Marketing5.8 · 25%Operations3.7 · 16%G&A2.4 · 10%Platform4.2 · 18%Product Eng3.8 · 16%Infrastructure2.1 · 9%Security1.4 · 6%Field Sales3.1 · 13%Demand Gen1.9 · 8%Brand0.8 · 3%Customer Support1.6 · 7%Facilities1.2 · 5%IT0.9 · 4%People1.1 · 5%Finance0.7 · 3%Legal0.6 · 3%BranchEngineeringSales & MarketingOperationsG&A
treemap Annual Budget ($M)

Engineering
  Platform 4.2
  Product Eng 3.8
  Infrastructure 2.1
  Security 1.4
Sales & Marketing
  Field Sales 3.1
  Demand Gen 1.9
  Brand 0.8
Operations
  Customer Support 1.6
  IT 0.9
  Facilities 1.2
G&A
  Finance 0.7
  Legal 0.6
  People 1.1

Overview

Treemaps show a hierarchy’s proportions as nested rectangles — each leaf’s area is its value, and parents auto-sum their children. They’re the fastest way to read “what’s big and what’s small” across a whole tree at once: budgets, cloud bills, disk usage, portfolios, headcount, taxonomies. Reach for one when your categories contain sub-categories you also want sized, and the breakdown of the total is the question. The layout is squarified, so cells keep readable aspect ratios instead of thin slivers.

When to use

  • treemap — a hierarchy where the leaves have numbers, and both containment and size matter.
  • pie — one flat, single-level list with no hierarchy, where the reader should read exact shares of a whole.
  • bar — one flat list of a handful of items that need to be read precisely. Rectangle areas are harder to compare than bar lengths.
  • sankey — the money moves from source to destination rather than simply being split up.
  • ring — the layers are levels of scope with no meaningful size attached.
  • mindmap — the leaves are just labels, with no values to size them by.
  • org — the nesting is about authority — who reports to whom — not size and containment.
  • sitemap — the nesting is navigation between real pages, not proportion of a total.

Syntax

treemap Title with units, e.g. "Cloud Spend ($)"

Parent
  // a bare trailing number is the leaf's size
  Leaf 320
  Leaf 180
Another Parent
  Leaf 110

The first line declares the chart type and an optional title. Put units in the title (Cloud Spend ($)) — there is no separate format or currency directive.

Hierarchy & Value

  • Indentation sets the hierarchy (the mind-map / org model). Nest as deep as you like.
  • A bare trailing number on a leaf is its size (Platform 320). Thousands separators are fine — 1,240,000 and 1_240_000 both read as 1240000. To keep a label that ends in a digit, quote it: "Region 2" 400.
  • Parents auto-sum their descendants — a number on a branch line is ignored (with a warning); the sum always wins.
  • A leaf with no value is treated as 0 (and warns); a negative value is an error.

To keep a label that genuinely ends in a digit, quote it: "Region 5" → label Region 5, no value.

Nesting works to any depth — a cloud bill broken down by service and sub-service:

AWS Bill ($/mo)Compute11k · 46%Database5.2k · 21%Storage4.3k · 18%Networking3.7k · 15%EC29.1k · 37%Fargate1.3k · 5%Lambda940 · 4%RDS3.1k · 13%DynamoDB1.4k · 6%ElastiCache680 · 3%S32.6k · 11%EBS1.4k · 6%Glacier3201%Data Transfer2.2k · 9%CloudFront1.4k · 5%Route 53On-Demand4.2k · 17%Savings Plans3.1k · 13%Spot1.8k · 7%BranchComputeStorageDatabaseNetworking
AWS Bill ($/mo)Compute11k · 46%Database5.2k · 21%Storage4.3k · 18%Networking3.7k · 15%EC29.1k · 37%Fargate1.3k · 5%Lambda940 · 4%RDS3.1k · 13%DynamoDB1.4k · 6%ElastiCache680 · 3%S32.6k · 11%EBS1.4k · 6%Glacier3201%Data Transfer2.2k · 9%CloudFront1.4k · 5%Route 53On-Demand4.2k · 17%Savings Plans3.1k · 13%Spot1.8k · 7%BranchComputeStorageDatabaseNetworking
treemap AWS Bill ($/mo)

Compute
  EC2
    On-Demand 4200
    Spot 1800
    Savings Plans 3100
  Lambda 940
  Fargate 1250
Storage
  S3 2600
  EBS 1400
  Glacier 320
Database
  RDS 3100
  DynamoDB 1450
  ElastiCache 680
Networking
  Data Transfer 2200
  CloudFront 1350
  Route 53 180

Tags — color by category

Declare a tag group before the content and apply it inline; children inherit a tagged ancestor’s value, so you tag the group once and the whole subtree picks up its color.

Q3 Budget ($k)Engineering640 · 55%Go-to-Market260 · 22%Operations180 · 16%G&A80 · 7%Platform320 · 28%Mobile180 · 16%Data140 · 12%Field Sales130 · 11%Ads90 · 8%Events40 · 3%Cloud110 · 9%Support70 · 6%People45 · 4%Finance35 · 3%TeamEngSalesOpsG&A
Q3 Budget ($k)Engineering640 · 55%Go-to-Market260 · 22%Operations180 · 16%G&A80 · 7%Platform320 · 28%Mobile180 · 16%Data140 · 12%Field Sales130 · 11%Ads90 · 8%Events40 · 3%Cloud110 · 9%Support70 · 6%People45 · 4%Finance35 · 3%TeamEngSalesOpsG&A
treemap Q3 Budget ($k)

tag Team as t
  Eng blue
  Sales green
  Ops orange
  G&A purple

Engineering t: Eng
  Platform 320
  Mobile 180
  Data 140
Go-to-Market t: Sales
  Ads 90
  Field Sales 130
  Events 40
Operations t: Ops
  Cloud 110
  Support 70
G&A t: G&A
  Finance 35
  People 45

Heat — color by value

A per-node heat: metric is a second numeric channel (distinct from size — negatives and floats are fine) that drives a value ramp, named with a heat <Label> [low] [high] directive. Here size = position, color = the day’s move, so a glance shows both how big a holding is and how it’s doing:

Portfolio — todayTech655 · 65%Crypto145 · 14%Financials110 · 11%Energy105 · 10%NVDA220 · 22%AAPL180 · 18%MSFT160 · 16%GOOG95 · 9%BTC90 · 9%ETH55 · 5%JPM70 · 7%BAC40 · 4%XOM60 · 6%CVX45 · 4%Day change %-3.16.7
Portfolio — todayTech655 · 65%Crypto145 · 14%Financials110 · 11%Energy105 · 10%NVDA220 · 22%AAPL180 · 18%MSFT160 · 16%GOOG95 · 9%BTC90 · 9%ETH55 · 5%JPM70 · 7%BAC40 · 4%XOM60 · 6%CVX45 · 4%Day change %-3.16.7
treemap Portfolio  today

heat Day change %

Tech
  AAPL 180 heat: -2.4
  NVDA 220 heat: 4.1
  MSFT 160 heat: 0.8
  GOOG 95 heat: 1.6
Energy
  XOM 60 heat: -1.2
  CVX 45 heat: -0.6
Financials
  JPM 70 heat: 1.9
  BAC 40 heat: -3.1
Crypto
  BTC 90 heat: 6.7
  ETH 55 heat: 3.2

Ramp colors are optional and data-aware: signed data (crossing 0) gets a diverging red · neutral · green ramp with the midpoint pinned at 0; one-sign data gets a sequential neutral · accent ramp. Override with heat <Label> low high (named palette colors only — hex is rejected).

A diagram can carry both tags and heat. The resting color mode resolves heat → tag → branch — a value ramp outranks a tag group when both are present. In the desktop app the legend offers a switcher to recolor live between them; active-tag <Group|HeatLabel> pins the resting dimension in the source so the choice travels with the file.

Directives

DirectiveEffect
heat <Label> [low] [high]Name (and optionally color) the value ramp; pairs with the heat: key.
depth NRender N levels deep; deeper subtrees collapse to a drillable solid block. A render budget, not a data limit — the full tree is always drillable, and static export renders it whole.
active-tag <Group|HeatLabel|none>Pin which dimension colors the tiles at rest — a tag group name, the heat label to force the ramp, or none to suppress tinting. Defaults to heat when present, else the first tag group.
no-valueHide value labels.
no-percentHide percentage labels.
no-headersHide parent header bars.
no-legendHide the legend.

Everything is on by default; subtract with no-*. Numbers auto-compact (1.2M, 940k).

depth keeps a deep tree readable — collapse the leaves into solid blocks you can drill into:

Monorepo Size (KB)apps8.7k · 45%services5.4k · 28%packages5.1k · 27%web4.2k · 22%mobile3.1k · 16%admin1.4k · 7%api3.3k · 17%worker1.5k · 8%scheduler640 · 3%core2.6k · 13%ui1.8k · 9%utils720 · 4%Branchappspackagesservices
Monorepo Size (KB)apps8.7k · 45%services5.4k · 28%packages5.1k · 27%web4.2k · 22%mobile3.1k · 16%admin1.4k · 7%api3.3k · 17%worker1.5k · 8%scheduler640 · 3%core2.6k · 13%ui1.8k · 9%utils720 · 4%Branchappspackagesservices
treemap Monorepo Size (KB)

depth 2

apps
  web 4200
  mobile 3100
  admin 1400
packages
  ui 1800
  core 2600
  utils 720
services
  api 3300
  worker 1500
  scheduler 640

Color Modes

The default mode is resolved from the source — tags declared → tag; else any heat present → heat; else branch (each top-level branch a distinct hue, tinted with depth). The desktop app adds a runtime tag/heat/branch switcher in the legend — live preview only, it never edits the source.

Radial mode — sunburst / hierarchical pie

Add a bare radial line and the same hierarchy renders as a sunburst — a hierarchical, multi-ring pie. The center disc is the whole (the chart title + grand total), the first ring is your top-level groups, and each outer ring breaks a group into its parts. It’s the familiar “slices of a circle” read of the exact same data a treemap shows as rectangles:

Sailing & Rigging640 · 62%Cannon Battery220 · 21%Provisions180 · 17%Rigging320 · 31%Helm180 · 17%Lookout140 · 13%Powder90 · 9%Shot130 · 13%Rum110 · 11%Hardtack70 · 7%Plunder Spend ($k)1,040CrewDeckGunsStores
Sailing & Rigging640 · 62%Cannon Battery220 · 21%Provisions180 · 17%Rigging320 · 31%Helm180 · 17%Lookout140 · 13%Powder90 · 9%Shot130 · 13%Rum110 · 11%Hardtack70 · 7%Plunder Spend ($k)1,040CrewDeckGunsStores
treemap Plunder Spend ($k)
radial

tag Crew as t
  Deck blue
  Guns orange
  Stores green

Sailing & Rigging t: Deck
  Rigging 320
  Helm 180
  Lookout 140
Cannon Battery t: Guns
  Powder 90
  Shot 130
Provisions t: Stores
  Rum 110
  Hardtack 70

Everything carries over: tags, the heat ramp, active-tag, and no-value / no-percent / no-legend. no-headers has no effect (a sunburst has no header bars). An arc’s angle is its share of the whole (value ÷ grand total); the radius is just depth and carries no magnitude — so slices are laid out in source order (not value-sorted), and very thin arcs drop their inline label to avoid clutter.

When to reach for radial vs. rectangular: use the sunburst for shallow trees (≈3 levels or fewer) and when you want the “part of a whole” circle. For deep trees or precise magnitude comparison, prefer the rectangular treemap — because radius carries no magnitude, deep sunbursts thin out and the eye over-weights the outer rings. For flat, single-level data with no hierarchy, use the pie chart instead.

Interactivity

In the desktop app, containers carry a focus icon and are drillable — click anywhere in a region to zoom into it (its box stays as context), and click its header again to zoom back out. A drilled branch keeps the color it had when expanded. Static export renders the whole tree.

Complete Example — tags + heat together

Size = effort, color (by default) = squad; flip the legend to the heat ramp to see which work is ahead of or behind plan:

Engineering Portfolio (effort, pts)Product320 · 40%Platform260 · 33%Infra215 · 27%Onboarding140 · 18%Checkout110 · 14%Search70 · 9%API Gateway120 · 15%Auth80 · 10%Service Mesh60 · 8%CI Pipeline90 · 11%Observability75 · 9%Cost Controls50 · 6%Δ vs plan %-6.58.1
Engineering Portfolio (effort, pts)Product320 · 40%Platform260 · 33%Infra215 · 27%Onboarding140 · 18%Checkout110 · 14%Search70 · 9%API Gateway120 · 15%Auth80 · 10%Service Mesh60 · 8%CI Pipeline90 · 11%Observability75 · 9%Cost Controls50 · 6%Δ vs plan %-6.58.1
treemap Engineering Portfolio (effort, pts)

tag Squad as sq
  Platform blue
  Product green
  Infra orange

heat Δ vs plan %

Platform sq: Platform
  API Gateway 120 heat: 4.0
  Auth 80 heat: -6.5
  Service Mesh 60 heat: 1.2
Product sq: Product
  Onboarding 140 heat: 8.1
  Checkout 110 heat: -3.4
  Search 70 heat: 0.5
Infra sq: Infra
  CI Pipeline 90 heat: -1.8
  Observability 75 heat: 5.6
  Cost Controls 50 heat: 2.3

Appearance

Alongside the treemap-specific no-* opt-outs, every chart accepts the universal appearance directives:

DirectiveEffect
fill-tintSoft tinted fills (default).
fill-solidSaturated solid fills.
fill-outlineOutline only, no fill.
no-legendHide the legend.

Colors come from the active palette — see Colors. Set the palette and light/dark theme at render time with --palette <name> and --theme light|dark|transparent.

Common mistakes

  • Indent a leaf one step too deep and its sibling becomes a branch. A branch’s size is the sum of its children, so the sibling’s own number stops counting and the parent total silently shrinks. The warning says the trailing number is ignored — that is the symptom, and a misplaced indent is usually the cause.
  • A trailing // note leaves the leaf with no value, and a valueless leaf renders at zero size — present in the model, invisible on screen. The warning quotes the number back at you because it read the whole line, comment included, as one long label.
  • In same-line metadata, a comma separates pairs. heat: 1,200 reads as a heat of 1. Use heat: 1_200. Grouped numbers are fine in ordinary values.
  • Adding a heat column recolors the whole chart. Heat outranks tags and branch inheritance, so introducing it overrides colors you set elsewhere.
  • Anything that renders but looks wrong: Troubleshooting is organised by symptom.

Next