Waterfall, funnel and pareto
Three charts that explain how a total came to be: a waterfall that bridges revenue to net income with subtotals that restart from zero, connectors between the steps and named colours for up, down and total; a sign-up funnel with the conversion and drop-off per step in the tooltip and a trapezoid, pyramid or cone shape; and a pareto of return causes with its cumulative line and the 80% mark.
A live, editable Svelte 5 data grid example from the SvGrid gallery (Charts). See the SvGrid documentation for the full API.
About this example
Waterfall, funnel and pareto: waterfallTotals marks the bars that are subtotals and restart from zero, waterfallColors names the colours for up, down and total, and connectors join the steps; a funnel carries each step's count with the conversion from the top and the drop-off from the step before in the tooltip, drawn as a trapezoid, pyramid or cone through funnelShape; paretoSpec sorts the causes by value and runs a cumulative percentage line with the 80% mark. Free, in @svgrid/grid.
Three charts that explain how a total came to be:
- The waterfall bridges revenue to net income: each bar starts where the last one ended,
waterfallTotalsmarks the bars that are subtotals (they restart from zero), andwaterfallColorsnames the colours for up, down and total. Connectors join the steps. - The funnel is a sign-up flow. Each segment carries its count, the tooltip its conversion from the top and its drop-off from the step before;
funnelShapedraws it as a trapezoid, a pyramid or a cone. - The pareto sorts the causes of returns by count and runs a cumulative percentage line over them, with the 80% line marked, so the few causes behind most of the returns stand out.
Free, in @svgrid/grid.
Imports, features and API used
Imports: @svgrid/grid, ../shared/ChartDataGrid.svelte, ../shared/ViewSwitch.svelte
Frequently asked questions
How do I mark subtotals in a waterfall?
waterfallTotals is a boolean per category. A true entry draws that bar from zero to the running total instead of as a step: give it a value of 0 to show the sum reached so far, or a value of its own to set the sum, which is how the bridge opens on the revenue figure.
What does the funnel tooltip show?
The step's value, its conversion as a share of the first step, and its drop-off from the step before.
How do I make a pareto chart?
Pass a bar spec to paretoSpec. It sorts the categories by value, adds the cumulative percentage as a line on a right axis and marks the threshold, 80% by default.
Related documentation
Source code (449-chart-waterfall-funnel.svelte)
<!-- Documented in: docs/help/charts/gallery.md -->
<script lang="ts">
/**
* 449. Waterfall, funnel and pareto
* ---------------------------------
* Three charts that explain how a total came to be:
*
* - The waterfall bridges revenue to net income: each bar starts where the
* last one ended, `waterfallTotals` marks the bars that are subtotals
* (they restart from zero), and `waterfallColors` names the colours for
* up, down and total. Connectors join the steps.
* - The funnel is a sign-up flow. Each segment carries its count, the
* tooltip its conversion from the top and its drop-off from the step
* before; `funnelShape` draws it as a trapezoid, a pyramid or a cone.
* - The pareto sorts the causes of returns by count and runs a cumulative
* percentage line over them, with the 80% line marked, so the few
* causes behind most of the returns stand out.
*
* Free, in @svgrid/grid.
*/
import { SvChart, paretoSpec, type ChartSpec } from '@svgrid/grid'
import ChartDataGrid from '../shared/ChartDataGrid.svelte'
import ViewSwitch from '../shared/ViewSwitch.svelte'
let shape = $state<'trapezoid' | 'pyramid' | 'cone'>('trapezoid')
const waterfall: ChartSpec = {
type: 'waterfall',
categories: ['Revenue', 'Cost of sales', 'Gross profit', 'R&D', 'Sales & marketing', 'G&A', 'Operating income', 'Tax', 'Net income'],
series: [{ label: 'FY26', values: [4300, -1840, 0, -620, -780, -310, 0, -180, 0] }],
waterfallTotals: [true, false, true, false, false, false, true, false, true],
waterfallColors: { positive: '#16a34a', negative: '#dc2626', total: '#2563eb' },
valueFormat: 'currency',
title: 'From revenue to net income',
subtitle: 'USD thousands, FY26',
yAxis: { gridLines: true },
dataLabels: { show: true, placement: 'top' },
height: 340,
}
const funnel = $derived<ChartSpec>({
type: 'funnel',
categories: ['Visited', 'Signed up', 'Activated', 'Upgraded', 'Renewed'],
series: [{ label: 'Users', values: [48200, 12600, 7900, 2150, 1680] }],
funnelShape: shape,
title: 'Sign-up funnel',
subtitle: 'Last quarter; hover a step for the conversion',
valueFormat: 'compact',
height: 340,
})
const pareto: ChartSpec = {
...paretoSpec({
type: 'bar',
categories: ['Wrong size', 'Damaged', 'Changed mind', 'Late delivery', 'Not as described', 'Defective', 'Other'],
series: [{ label: 'Returns', values: [412, 268, 190, 96, 71, 44, 38] }],
}),
title: 'Why orders come back',
subtitle: 'Returns by cause, with the cumulative share',
yAxis: { title: 'Returns', gridLines: true },
height: 340,
}
// Chart | Grid: the same spec as the chart or as the rows behind it.
let view = $state<'chart' | 'grid'>('chart')
</script>
<section class="wrap">
<header class="chrome">
<ViewSwitch bind:value={view} options={[['chart', 'Chart'], ['grid', 'Grid']]} />
<label class="ctl">
Funnel shape
<select bind:value={shape}>
<option value="trapezoid">Trapezoid</option>
<option value="pyramid">Pyramid</option>
<option value="cone">Cone</option>
</select>
</label>
<span class="note">
A waterfall with subtotals and connectors, a funnel with conversion and drop-off in the tooltip,
and a pareto with its cumulative line and the 80% mark.
</span>
</header>
<div class="row">
<div class="pane pane-wide">
{#if view === 'chart'}
<SvChart spec={waterfall} legend={false} autosize />
{:else}
<ChartDataGrid spec={waterfall} />
{/if}
</div>
<div class="pane">
{#if view === 'chart'}
<SvChart spec={funnel} legend={false} autosize />
{:else}
<ChartDataGrid spec={funnel} />
{/if}
</div>
<div class="pane">
{#if view === 'chart'}
<SvChart spec={pareto} legend={false} autosize />
{:else}
<ChartDataGrid spec={pareto} />
{/if}
</div>
</div>
</section>
<style>
.wrap {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
gap: 10px;
overflow: auto;
}
.chrome {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
flex: none;
}
.note {
font-size: 12px;
color: var(--sg-muted, #64748b);
}
.ctl {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 12px;
color: var(--sg-fg, #0f172a);
white-space: nowrap;
}
.ctl select {
font: inherit;
border: 1px solid var(--sg-border, #e2e8f0);
border-radius: 6px;
background: var(--sg-bg, #fff);
color: inherit;
padding: 2px 6px;
}
.row {
display: flex;
flex: none;
gap: 12px;
flex-wrap: wrap;
}
.pane {
flex: 1 1 360px;
border: 1px solid var(--sg-border, #e2e8f0);
border-radius: 10px;
background: var(--sg-bg, #fff);
padding: 8px 10px;
min-width: 0;
}
.pane-wide {
flex: 1 1 100%;
}
</style>More Charts examples
- Chart a selection (context menu) - With integrated charting enabled, the right-click menu gains a Chart selected range item: select a block of cells, right-click, and the chart panel opens scoped to that range - the Excel chart-this gesture, built in. The item appears only when charting is on and is appended to the default context menu automatically.
- Chart view of the grid - The `chart` prop turns the same <SvGrid> into a chart, driven by the grid’s filtered + sorted rows (search + sort flow through). A view of the grid like board and scheduler, but the renderer is free: the grid lazy-loads a built-in view wrapping the standalone SvChart via rowsToChartSpec. Flip Table <-> Chart (bar / line / area) over one source of truth.
- Candlestick / OHLC - Candlestick and OHLC price marks with an ordinal date axis. ChartSeries.ohlc carries the four prices while values keeps the closes, so the CSV export, the screen-reader table and a 10-day moving average all work with no candle-specific code. Toggle the axis: a real time axis opens a gap over every weekend, an ordinal one spaces sessions evenly and still labels them by date. The strip under the plot is the chart brush: drag its window to pan, drag an edge to resize.
- Box plot + error bars - Distribution rather than average: box plots with the 1.5 IQR whisker rule and individual outliers, next to the same data as a bar chart of the means with error bars. two regions with nearly identical means sit side by side in the bars and look nothing alike in the boxes. boxStats() summarises a raw sample, rowsToBoxSpec() does it per group, and ChartSeries.errors annotates any existing mark without changing its type.
- Axes, titles and styling - The chart hands the decisions back: a numeric x axis that spreads payload sizes the way the numbers do, pinned domains with a fixed tick interval and a formatter per axis, grid lines on or off, a shaded budget band, a vertical reference line, per-point markers and colours, a stepped dashed series on the right axis, a title / subtitle / caption, a legend on any side, and a tooltip snippet in single-series mode. Below it, 50,000 monitoring readings decimated to one point per pixel with LTTB or min / max - toggle it off to see the cost.