Treemap

Area-encoded topography: which parts of a structure carry the weight, proportionally. Answers 'which area is worst' where ClusterMap answers 'who is affected and why'.

rendered, live
the wire — click a line to scrub
the contract — Treemap in catalog.json
{
  "type": "object",
  "description": "Area-encoded topography: which parts of a structure carry the weight, proportionally. Answers 'which area is worst' where ClusterMap answers 'who is affected and why'.",
  "properties": {
    "component": {
      "const": "Treemap"
    },
    "label": {
      "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString",
      "description": "Title and accessible name, e.g. 'Support volume by product area'. Required."
    },
    "valueLabel": {
      "type": "string",
      "description": "What the area means, e.g. 'cases' or 'ARR at risk'. Required — an area encoding with no stated unit cannot be read."
    },
    "subjectKind": {
      "type": "string",
      "description": "What one rectangle is, in your domain's words, e.g. 'product_area'. Default 'area'; merged into itemAction context with the item's id."
    },
    "windowStart": {
      "type": "string",
      "description": "ISO 8601 start of the observation window. The renderer formats it in the host locale — keep the period out of the label prose."
    },
    "windowEnd": {
      "type": "string",
      "description": "ISO 8601 end of the observation window."
    },
    "items": {
      "oneOf": [
        {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "label": {
                "type": "string",
                "description": "Short name of the area, e.g. 'Reporting'."
              },
              "value": {
                "type": "number",
                "description": "What the area encodes. Raw number in the units of valueLabel. When children are present this should be their total; if it disagrees, the children win and this is ignored."
              },
              "intent": {
                "enum": [
                  "good",
                  "bad",
                  "warning",
                  "info",
                  "neutral"
                ]
              },
              "trend": {
                "enum": [
                  "up",
                  "down",
                  "flat"
                ],
                "description": "Direction of the underlying signal — describes, never judges. Independent of intent: volume rising is 'up' and usually 'bad'; the same rise in revenue is 'up' and 'good'. Without it, a worsening area and a merely large one look identical."
              },
              "children": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string",
                      "description": "Short name, e.g. 'Export totals'."
                    },
                    "value": {
                      "type": "number",
                      "description": "What the area encodes. Raw number in the units of valueLabel — never a formatted string."
                    },
                    "intent": {
                      "enum": [
                        "good",
                        "bad",
                        "warning",
                        "info",
                        "neutral"
                      ]
                    },
                    "trend": {
                      "enum": [
                        "up",
                        "down",
                        "flat"
                      ],
                      "description": "Direction of the underlying signal — describes, never judges. Independent of intent: volume rising is 'up' and usually 'bad'; the same rise in revenue is 'up' and 'good'. Without it, a worsening area and a merely large one look identical."
                    }
                  },
                  "required": [
                    "id",
                    "label",
                    "value"
                  ],
                  "additionalProperties": false
                },
                "description": "One level of nesting, and only one — an area's parts. Deeper trees are not expressible: past two levels the rectangles are too small to label honestly."
              }
            },
            "required": [
              "id",
              "label",
              "value"
            ],
            "additionalProperties": false
          }
        },
        {
          "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DataBinding"
        }
      ],
      "description": "The areas, sized by value. Order does not matter — the layout sorts by value. Bind with {\"path\": ...} to stream."
    },
    "itemAction": {
      "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/Action",
      "description": "Fired on rectangle activation. subjectKind, subjectId (the item's id), itemLabel, and — for a child — parentId are merged into its context automatically."
    }
  },
  "required": [
    "label",
    "valueLabel",
    "items"
  ]
}
the prompt-pack — paste into your agent
### Treemap — which parts carry the weight

Area-encoded topography. Answers _which part of the product hurts, proportionally_ — where
ClusterMap answers _who is affected, and why_. Reach for it when the question is about structure
rather than entities, or when there are too many groups for cards to stay readable.

| prop          | type                           | notes                                                       |
| ------------- | ------------------------------ | ----------------------------------------------------------- |
| `label`       | string, required               | title and accessible name                                   |
| `valueLabel`  | string, required               | what the area means — `"cases"`, `"ARR at risk"`            |
| `items`       | item[] \| `{"path"}`, required | shape below                                                 |
| `subjectKind` | string                         | what one rectangle is; default `"area"`                     |
| `windowStart` | ISO 8601 string                | observation window — keep the period out of the label prose |
| `windowEnd`   | ISO 8601 string                | ditto; the renderer formats both in the host locale         |
| `itemAction`  | action                         | subject + `itemLabel` (+ `parentId` for a child) merged in  |

Item shape — `id`, `label`, `value` required. `value` is a raw number in the units of
`valueLabel`, never a formatted string. `children` gives **one** level of nesting and only one; a
parent's `value` should equal its children's total.

`intent` and `trend` are independent axes, and an area that is _getting worse_ needs both — put
the direction in `trend`, not in the title text. `intent` says how bad it is now; `trend` says
which way it is moving (`up` / `down` / `flat`, describing the value, never judging it). Omit
either one when you weren't told: an unstated intent is not `"neutral"`, and an unstated direction
is not `"flat"` — inventing one puts a claim on screen that nobody made.

```
{"id":"reporting","label":"Reporting","value":312,"intent":"bad","trend":"up","children":[{"id":"export-totals","label":"Export totals","value":190,"intent":"bad","trend":"up"},{"id":"scheduling","label":"Scheduled sends","value":122,"trend":"flat"}]}
```

```
{"id":"friction","component":"Treemap","label":"Support volume by product area","valueLabel":"cases","subjectKind":"product_area","windowStart":"2026-08-01T00:00:00Z","items":{"path":"/areas"},"itemAction":{"event":{"name":"area_drilled","context":{"view":"cases"}}}}
```

Drilling fires the event and nothing else — what happens next is your next turn. Push a
`DrillStack` level by writing its `activeIndex`, or send the components for the deeper view.