Composition Chart
What a whole is made of, as a donut, 100% bars, a waffle or a treemap.
Pattern · Shares patterns with Chart, KPI Card
≈ 1,408 tokens per use (tool 877 · guidance 374 · typical payload 157)How token costs are estimated
Example: Composition Chart
How much of October's spending went on eating out?
October spending
Card spend, 1–31 Oct
- Rent$1,85055%
- Groceries$62019%
- Eating out$41012%
- Transport$2307%
- Other$2357%
Rent took over half; eating out came third at $410, more than transport and gifts together.
When to use
- The question is what a whole is made of: spend by category, traffic by source, how a budget is used, who makes up a team.
- The user wants to compare the mix across a few groups: traffic sources by region, plan mix by quarter as separate wholes.
When not to use
- The parts don't add up to a meaningful whole: separate metrics, or multi-select survey answers whose percentages overlap. Use a bar Chart.
- The mix changes over many periods. Use a stacked bar Chart.
- There is one share to report ("38% of spend went to rent"). Say it in a sentence.
- For a compact business metric with deltas and a footer total, use a KPI Card.
How the agent should use it
- Pass amounts in one unit that add up to the whole. Never percentages you worked out, never overlapping shares. Rows with the same part are added together.
- Pick the form for the job:
donutfor one whole with up to 5 parts. The centre shows the total, or the share of thehighlightpart.barto compare the mix across 2–8 groups (one 100% bar each), or to read one whole precisely.wafflefor "N in 100" with up to 4 parts: 100 squares, each 1%.treemapfor 6–20 parts of one whole, labels inside the tiles.
- Don't fold small parts yourself. Donut, bar and waffle keep the 4 largest and fold the rest into "Other"; the treemap shows every part.
- Set
totalonly when the parts don't fill the whole, like a budget and what's committed so far, with aremainderLabelsuch as "Left to spend". The rest shows as a hollow part. - Set
highlightto the part the user asked about. - In
summary, name the biggest part and anything surprising.
Design
- Colours by rank. Parts take the categorical colours in order (vermilion, cool blue, cassis, sage, wasabi: muted hues after Pinterest's 2026 palette), validated as neighbours in both themes; "Other" is a neutral grey and a remainder is hollow with a dashed edge. Parts are separated by 2px gaps. A treemap stays one colour (the tone), since its labels sit inside the tiles.
- The legend is the data. For one whole, every part is listed with its amount and share, in the same order as the chart; hovering a row lights up its part. Grouped bars show a compact key, with each group's total at the end of its bar.
- Waffle squares are rounded by largest remainder, so they always add up to exactly 100.
- Treemap tiles follow the squarified layout, so they stay close to square; tiles too small for a label show it on hover and in the legend.
- Hover any slice, segment, square or tile for its amount and share (of its group, for grouped bars).
Accessibility
The marks are hidden from screen readers. For one whole the legend is a real list with every part's amount and share; grouped bars add a visually hidden table of every group and part. Amounts and shares are formatted with Intl, so they follow the user's locale.
Installation
npx shadcn@latest add https://your-site.com/r/composition-chart.json
Usage
The Composition Chart is display-only, so the tool can return immediately:
import { tool } from "ai"
import { compositionChartTool } from "@/components/genui/composition-chart.schema"
const tools = {
[compositionChartTool.name]: tool({
description: compositionChartTool.description,
inputSchema: compositionChartTool.inputSchema,
execute: async () => ({ shown: true }),
}),
}Render it through the Renderer under show_composition, or directly:
<CompositionChart {...part.input} status={part.state === "input-streaming" ? "streaming" : "ready"} />Schema
| Prop | Type | Notes |
|---|---|---|
title | string | The whole being split, e.g. "October spending" |
description | string? | Scope or source |
type | "donut" | "bar" | "waffle" | "treemap" | The form, see above |
part | { key, label? } | Field naming each row's part |
value | { key, label, format?, currency?, unit? } | The amounts. format: number, currency or percent |
group | { key, label? }? | Bar only: one 100% bar per group, up to 8 |
total | number? | The whole, when the parts don't fill it; one whole only |
remainderLabel | string? | Name for what's left of total. Default "Remaining" |
highlight | string? | The part the user asked about |
data | Record<string, string | number>[] | Data-bound. 1–200 rows with the part and value keys (and the group key) |
summary | string? | One-sentence takeaway, shown under the chart |
Host props: status (streaming or ready) and className.