Charts
WaterfallChart
How a total got from one figure to another.
Usage
When to reach for it
Ships from@misoto22/design/charts
import { WaterfallChart } from '@misoto22/design/charts'Notes
How a total got from one figure to another — an opening balance, the signed contributions that moved it, and where it closed.
The form for "why did this change", which a pair of bars cannot answer and a pie chart answers wrongly, because contributions can be NEGATIVE and a slice cannot. Reach for <BarChart> when the parts do not have to add up to the gap between two totals, and for <FunnelChart> when the quantity only ever shrinks.
The connectors are the claim to be careful about. They draw the steps as a sequence — this happened, then this — and most breakdowns are not sequential at all: churn and expansion in the same month are simultaneous, and the order they are listed in is an editorial choice. The arithmetic survives any order; the STORY does not, and a reader will take the leftmost bar as the first cause. Two related traps: the intermediate bars are floating lengths read against no baseline, so a small step high up the cascade is hard to compare with a large one near zero; and any step that is itself a net of two larger opposing movements is invisible as such. Where the order is arbitrary, say so in the description.
Recharts earns its place for the axes, grid and tooltip. Each bar is a floating range — from the running total to the new one — with a custom shape over it, because Recharts has no waterfall mark and the connectors have to be drawn from the same geometry as the bars they join.
Anatomy
| Element | Description |
|---|---|
| Figure framerequired | ChartFigure’s <figure>, with an empty state at zero steps. description is where the ordering caveat goes, and it is sr-only until showTitle is set. |
| Stepsrequired | <WaterfallChart.Bars>: one floating range bar per step, running from the previous total to the new one, with a custom shape over it. Increases and totals take the solid series fill, decreases the 45 degree hatch, so direction survives greyscale and forced colours. |
| Connectors | connectors, on by default, joining each bar’s closing edge to where the next one starts. Without them a waterfall is a row of bars floating at unrelated heights and the reader has to reconstruct the cascade. |
| Zero baselinerequired | A reference line at zero that <WaterfallChart.Bars> draws itself. It is what the total bars stand on, and a waterfall with no visible zero asks the reader to take every floating bar on trust. |
| Step labels | showValues, and it is OFF by default. It prints each step’s signed change beside its bar — worth more here than anywhere else, because an intermediate bar has no baseline under it and its length is the one thing the axis cannot give back. |
| Hidden data table | The sr-only table carries the CHANGE and the RUNNING TOTAL per step, which is exactly the pair the picture encodes as a length and a position. |
Best practices
Do
- Turn showValues on. It is off by default and it is the relief for this form’s central weakness: an intermediate bar floats, so a reader can see that a step was small and cannot see how small.
- Leave value off the closing total step. Omitted, it is computed from the deltas above it; typed by hand it can disagree with them, and the chart will draw the disagreement without saying a word.
- Say in description when the step order is editorial, and pass showTitle so the sentence is actually printed. The connectors draw the steps as a sequence and most breakdowns are not one — churn and expansion in the same month are simultaneous — and a reader takes the leftmost bar as the first cause.
Don’t
- Do not net two opposing movements into one step. A bar reading minus twenty that is really plus one hundred and eighty against minus two hundred is drawn exactly like a quiet month, and showing what moved is the entire purpose of the form.
- Do not compare an intermediate bar with a total bar by eye. Only the totals sit on the zero line; everything between them is a length at an arbitrary height, so a small step high in the cascade and a large one near zero are not on comparable ground.
- Do not reach for a pie when the contributions are signed. This is the form that exists because a pie cannot hold a negative slice — and a BarChart is the right one instead when the parts need not add up to the gap between two totals.
Examples
default
An opening total, four signed contributions, and a closing bar that carries no value of its own: it is whatever the deltas add up to, so it can never disagree with the steps above it. Decreases take the 45 degree hatch and increases the solid fill — texture rather than a second colour, so the reading survives greyscale and forced colours — and a total is told from an increase by geometry, since it is the only kind of bar standing on the baseline. The steps here are simultaneous and their order is editorial, which is what the description says out loud: the arithmetic survives any order, but the connectors draw a sequence and a reader will take the leftmost bar as the first cause.
| name | Change | Running total |
|---|---|---|
| FY24 ARR | 4,200 | 4,200 |
| New business | 1,180 | 5,380 |
| Expansion | 640 | 6,020 |
| Downgrades | -310 | 5,710 |
| Churn | -820 | 4,890 |
| FY25 ARR | 0 | 4,890 |
subtotals
type total plants a bar on the baseline and sets the running figure, and it is used twice in the middle here — gross profit, then operating profit — so the cascade restates itself where a reader would otherwise be adding five floating lengths in their head. Neither subtotal carries a value, so neither can disagree with the steps above it. Turn the connectors off and the same bars become a row of lengths at unrelated heights, which is the argument for keeping them on and also the argument for taking them off deliberately: where the order of the steps is arbitrary, the connectors claim a sequence the data does not have.
| name | Change | Running total |
|---|---|---|
| Revenue | 8,400 | 8,400 |
| COGS | -3,100 | 5,300 |
| Gross profit | 0 | 5,300 |
| Sales | -1,450 | 3,850 |
| R&D | -1,900 | 1,950 |
| G&A | -720 | 1,230 |
| Operating profit | 0 | 1,230 |
against a plan
The closing bar says where the total landed; it does not say whether that was where the plan said it would. A reference line draws that one number across the cascade, above the bars rather than behind them — a target a bar covers is a target nobody can check — and it extends the axis to fit rather than being discarded, which is what makes it visible here, since the plan sits above every bar in the chart. weight firm is solid ink, for the one threshold a chart is actually about; the quiet default is a dashed hairline, for context that has to be present and clearly not data.
| name | Change | Running total |
|---|---|---|
| H1 actual | 5,200 | 5,200 |
| New business | 1,420 | 6,620 |
| Expansion | 780 | 7,400 |
| Discounts | -260 | 7,140 |
| Churn | -940 | 6,200 |
| H2 actual | 0 | 6,200 |
Types
export type WaterfallStepType = 'delta' | 'total'
export type WaterfallDirection = 'increase' | 'decrease' | 'total'Accessibility
- The connectors draw the steps as a sequence, and most breakdowns are not sequential — churn and expansion in the same month are simultaneous, and a reader takes the leftmost bar as the first cause. Where the order is arbitrary, say so in description.
- Intermediate bars are floating lengths read against no baseline, so a small step high up the cascade is hard to compare with a large one near zero. Total bars sit on the axis and are the only ones a reader can read absolutely.
- Direction is carried by the label’s sign and the bar’s texture as well as its position, so the reading survives greyscale and forced colours.
- A closing bar with no value is computed from the deltas, which keeps the arithmetic in the data rather than in the caller’s head.