How a starting value moves through increases and decreases to a total
Tags: chart waterfall bridge variance finance reporting
The WaterfallChart shows how a starting value moves through a series of increases and decreases to a total, with each step floating from the running total before it. Use it for margin bridges (sale price to gross margin), budget variance, or backlog movement (opening backlog, sales, cancellations, closings, closing backlog).
Svelte only (
@classic-homes/charts-svelte); React support is pending.
Import
<script>
import { WaterfallChart } from '@classic-homes/charts-svelte';
</script>Basic Usage
<script>
import { WaterfallChart } from '@classic-homes/charts-svelte';
const data = [
{ name: 'Base price', value: 412000, isTotal: 'value' },
{ name: 'Options', value: 38500 },
{ name: 'Lot premium', value: 15000 },
{ name: 'Incentives', value: -12000 },
{ name: 'Net revenue', isTotal: true, value: 0 },
{ name: 'Direct costs', value: -318000 },
{ name: 'Gross margin', isTotal: true, value: 0 },
];
</script>
<WaterfallChart title="Average Margin Bridge" {data} valueFormat="currency" />Props
Chart-Specific Props
| Prop | Type | Default | Description |
|---|---|---|---|
data |
WaterfallStep[] |
required | Steps in order |
showLabels |
boolean |
true |
Show each step's signed change (or total) on its bar |
labelFormatter |
string | ((params: LabelFormatterParams) => string) |
- | Label function; receives the step's change (or total) as value. String templates are not applied |
increaseColor |
string |
- | Colour for increases (defaults to the success colour) |
decreaseColor |
string |
- | Colour for decreases (defaults to the destructive colour) |
totalColor |
string |
- | Colour for totals (defaults to the first series colour) |
xAxisConfig |
AxisConfig |
- | Step axis customisation |
yAxisConfig |
AxisConfig |
- | Value axis customisation |
referenceLines |
ReferenceLine[] |
- | Lines marking targets or thresholds |
referenceBands |
ReferenceBand[] |
- | Shaded value ranges |
onClick |
(params: DataPointEventParams) => void |
- | Callback when a step is clicked; value is the step's change (or total) |
Inherited Base Props
| Prop | Type | Default | Description |
|---|---|---|---|
title |
string |
required | Chart title for accessibility (aria-label) |
description |
string |
- | Description for screen readers (generated from the data when omitted) |
height |
number | string |
400 |
Chart height in pixels or CSS value |
minHeight |
number |
- | Minimum height in pixels; also bounds an 'auto' height |
maxHeight |
number |
- | Maximum height in pixels; also bounds an 'auto' height |
loading |
boolean |
false |
Show loading skeleton (an overlay when data is already shown) |
error |
string | null |
- | Error message to display |
onRetry |
() => void |
- | Show a Retry button on the error state, calling this |
emptyMessage |
string |
- | Message shown when data is empty |
theme |
'light' | 'dark' | 'auto' |
'auto' |
Theme preference |
animation |
boolean |
true |
Enable/disable animations (respects prefers-reduced-motion) |
showTooltip |
boolean |
true |
Show tooltip on hover |
showGrid |
boolean |
true |
Show grid lines |
valueFormat |
ValueFormat |
- | Preset ('number', 'integer', 'compact', 'currency', 'currencyCents', 'percent', 'percentValue', 'days'), Intl.NumberFormatOptions or a function; drives the value axis, tooltip, labels and data table |
locale |
string |
'en-US' |
Locale for valueFormat |
tooltipFormatter |
string | ((items: TooltipFormatterParams[]) => string) |
- | Custom tooltip template or HTML function |
tooltipValueFormatter |
(value: number) => string |
- | Format values in the default tooltip (overrides valueFormat there) |
option |
EChartsOption |
- | Raw ECharts option deep-merged over the generated one |
onReady |
(chart: EChartsType) => void |
- | Called with the ECharts instance when it is created |
events |
ChartEventHandlers |
- | Raw ECharts event handlers keyed by event name ('datazoom', 'legendselectchanged', ...) |
showDataTable |
boolean | 'sr-only' |
'sr-only' |
Data table for screen readers only, a visible "Show data" toggle (true) or none (false) |
exportable |
boolean | ChartExportConfig |
- | Show PNG and CSV download buttons, or { image, csv, filename } |
patterns |
boolean |
false |
Fill series with decal patterns as well as colour |
class |
string |
- | Custom CSS class |
showLegend |
boolean |
- | Show chart legend |
Data Structure
interface WaterfallStep {
name: string; // Step label
value: number; // Change contributed by this step (ignored for isTotal: true)
isTotal?: boolean | 'value'; // true: bar from zero to the running total
// 'value': bar from zero to `value`, resetting the running total
color?: string; // Override the bar colour
label?: string; // Label text shown instead of the formatted value
}Examples
Backlog Movement
Start with isTotal: 'value' to set the opening balance, and end with isTotal: true to show where the steps land.
<WaterfallChart
title="Backlog Movement, Q3"
data={[
{ name: 'Opening backlog', value: 214, isTotal: 'value' },
{ name: 'Sales', value: 96 },
{ name: 'Cancellations', value: -14 },
{ name: 'Closings', value: -88 },
{ name: 'Closing backlog', value: 0, isTotal: true },
]}
valueFormat="integer"
/>Custom Colours and Labels
<WaterfallChart
title="Budget Variance"
{data}
increaseColor="#15803d"
decreaseColor="#b91c1c"
totalColor="#334155"
valueFormat="compact"
/>Layout Without the Chart
waterfallLayout returns where each bar sits (start, end, change and kind), for tables or custom rendering.
import { waterfallLayout } from '@classic-homes/charts-svelte';
waterfallLayout([
{ name: 'Start', value: 100, isTotal: 'value' },
{ name: 'Change', value: -30 },
{ name: 'End', value: 0, isTotal: true },
]);
// [{ start: 0, end: 100, kind: 'total', ... }, { start: 100, end: 70, kind: 'decrease', ... }, ...]Accessibility
- The
titleprop is used asaria-label; a description counting increases and decreases and stating the final total is generated whendescriptionis omitted - Steps, changes and running totals are rendered as a screen-reader table (
showDataTable) - Labels carry a sign (+/−), so direction does not rely on colour alone
- Animations respect
prefers-reduced-motion
Related Charts
- BarChart - Categorical comparison
- FunnelChart - Stage-by-stage drop-off
- SankeyChart - Flows between stages