Waterfall (signed P&L)
type: waterfall renders each bar starting where the previous one ended. waterfallTotals marks subtotal/total bars that span from 0. Bars colour-code by sign (green / red); total bars get a neutral slate. Connector lines link bar tops so the cumulative trend reads at a glance.
A live, editable Svelte 5 data grid example. Open the interactive demo or read the documentation.
What this example shows
`type: 'waterfall'` renders a running-total bar chart - each bar starts where the previous one ended. Mark a bar as a total via `waterfallTotals: boolean[]` (parallel to `categories`) and that bar spans from 0 instead of stacking, so it reads as a subtotal / grand total. Bars colour-code by sign (green positive, red negative) and total bars get a neutral slate by default - override via `waterfallColors`. Thin connector lines link bar tops so the cumulative trend reads at a glance.
This demo: a quarterly P&L driven by the grid. Toggle a row's `isTotal` checkbox to mark it as a subtotal; the chart re-renders with the new running calculation.
Source code (158-chart-waterfall.svelte)
<!-- Documented in: docs/help/charts.md -->
<script lang="ts">
/**
* 158. Waterfall chart (signed P&L)
* ----------------------------------
* `type: 'waterfall'` renders a running-total bar chart - each bar
* starts where the previous one ended. Mark a bar as a total via
* `waterfallTotals: boolean[]` (parallel to `categories`) and that bar
* spans from 0 instead of stacking, so it reads as a subtotal /
* grand total. Bars colour-code by sign (green positive, red negative)
* and total bars get a neutral slate by default - override via
* `waterfallColors`. Thin connector lines link bar tops so the
* cumulative trend reads at a glance.
*
* This demo: a quarterly P&L driven by the grid. Toggle a row's
* `isTotal` checkbox to mark it as a subtotal; the chart re-renders
* with the new running calculation.
*/
import {
SvGrid,
SvGridChart,
tableFeatures,
rowSortingFeature,
columnFilteringFeature,
type ColumnDef,
type SvGridApi,
type ChartSpec,
} from '@svgrid/grid'
const features = tableFeatures({ rowSortingFeature, columnFilteringFeature })
type Row = { id: number; label: string; value: number; isTotal: boolean }
const seed: Row[] = [
{ id: 0, label: 'Revenue', value: 1_240_000, isTotal: false },
{ id: 1, label: 'Discounts', value: -85_000, isTotal: false },
{ id: 2, label: 'Refunds', value: -32_000, isTotal: false },
{ id: 3, label: 'Net revenue', value: 0, isTotal: true },
{ id: 4, label: 'COGS', value: -480_000, isTotal: false },
{ id: 5, label: 'Gross profit', value: 0, isTotal: true },
{ id: 6, label: 'Marketing', value: -180_000, isTotal: false },
{ id: 7, label: 'R&D', value: -210_000, isTotal: false },
{ id: 8, label: 'G&A', value: -95_000, isTotal: false },
{ id: 9, label: 'Operating income', value: 0, isTotal: true },
]
let rows = $state<Row[]>(seed)
const columns: ColumnDef<typeof features, Row>[] = [
{ field: 'label', header: 'Line item', width: 220 },
{ field: 'value', header: 'Amount', width: 150, align: 'right',
format: { type: 'currency', currency: 'USD', options: { maximumFractionDigits: 0 } } },
{ field: 'isTotal', header: 'Total?', width: 100 },
]
let api = $state<SvGridApi<typeof features, Row> | null>(null)
let displayed = $state<Row[]>(rows)
function sync() { displayed = (api?.getDisplayedRows() as Row[]) ?? rows }
const spec = $derived.by<ChartSpec>(() => ({
type: 'waterfall',
categories: displayed.map((r) => r.label),
series: [{
label: 'Amount',
values: displayed.map((r) => r.value),
}],
waterfallTotals: displayed.map((r) => r.isTotal),
width: 720,
height: 360,
yAxisTitle: 'USD',
}))
const compact = (v: number) => {
const a = Math.abs(v)
if (a >= 1e6) return (v / 1e6).toFixed(1) + 'M'
if (a >= 1e3) return (v / 1e3).toFixed(0) + 'k'
return String(Math.round(v))
}
function toggleTotal(id: number) {
rows = rows.map((r) => (r.id === id ? { ...r, isTotal: !r.isTotal } : r))
sync()
}
</script>
<section class="flex flex-col flex-1 min-h-0 gap-3">
<div class="shrink-0 rounded-lg border px-4 py-3" style="border-color: var(--sg-border); background: var(--sg-header-bg);">
<p class="text-sm font-semibold" style="color: var(--sg-fg);">
Waterfall chart for a quarterly P&L
</p>
<p class="mt-0.5 text-xs" style="color: var(--sg-muted);">
Each green/red bar starts where the previous one ended. The slate "total" bars span from 0,
showing the cumulative position. Click a row's <em>Total?</em> badge to flip its role - the
waterfall reflows in real time.
</p>
</div>
<div class="flex flex-1 min-h-0 gap-3">
<div class="flex-1 min-h-0">
<SvGrid responsive={true}
data={rows}
columns={columns}
features={features}
sortable
filterable
selectionMode="none"
enableRowSummaries={false}
rowHeight={32}
containerHeight="100%"
fitColumns={true}
onApiReady={(a) => { api = a; sync() }}
onCellClick={(e) => {
if (e.columnId === 'isTotal' && e.row) toggleTotal((e.row as Row).id)
}}
/>
</div>
<div class="shrink-0 rounded-lg border p-3" style="width: 780px; border-color: var(--sg-border); background: var(--sg-bg);">
<SvGridChart {spec} formatValue={compact} dataLabels />
</div>
</div>
</section>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.
- Integrated charts (no deps) - Chart the grid data with no external charting library. SvGridChart renders a ChartSpec; rowsToChartSpec aggregates the grid current (filtered/sorted) rows into one. Bar, line, area, pie - plus 100% stacked, top-N + Other, an average reference line, and double-click-to-isolate a series. Filter the grid and the chart re-aggregates live.
- Scatter / bubble chart - A scatter plot maps two numeric measures (x vs y); a bubble chart adds a third via dot radius. type: scatter with series points [{ x, y, r }]. Spend vs revenue, sized by deals, coloured by region, with an average-revenue reference line. Filter the grid and the cloud re-plots.
- Time-series chart (date axis) - xType: time spaces points by ACTUAL time - irregular date gaps render proportionally - and shows real date ticks. A referenceLines target/SLA line spans the plot; toggle 100% stacked to read each day as a share of its total. Line, stacked area, or stacked bar.