Funnel chart (signup conversion)
type: funnel renders strictly-decreasing values as a stack of trapezoids. Each segment shows conversion vs. the top of the funnel inline; hover for the step drop-off. Click a segment to record a drill selection.
A live, editable Svelte 5 data grid example. Open the interactive demo or read the documentation.
What this example shows
`type: 'funnel'` renders a series of strictly-decreasing values as a stack of trapezoids - level N+1 is automatically narrower than level N proportional to its value. Each segment shows the absolute count plus the cumulative conversion vs. the top of the funnel; hover for the step drop-off. Click any segment to drill the grid to that stage.
Source code (160-chart-funnel.svelte)
<!-- Documented in: docs/help/charts.md -->
<script lang="ts">
/**
* 160. Funnel chart (signup conversion)
* --------------------------------------
* `type: 'funnel'` renders a series of strictly-decreasing values as a
* stack of trapezoids - level N+1 is automatically narrower than level
* N proportional to its value. Each segment shows the absolute count
* plus the cumulative conversion vs. the top of the funnel; hover for
* the step drop-off. Click any segment to drill the grid to that stage.
*/
import {
SvGrid,
SvGridChart,
tableFeatures,
rowSortingFeature,
columnFilteringFeature,
type ColumnDef,
type SvGridApi,
type ChartSpec,
type ChartSelection,
} from '@svgrid/grid'
const features = tableFeatures({ rowSortingFeature, columnFilteringFeature })
type Row = { id: number; stage: string; users: number; note: string }
const stages = [
{ stage: 'Visited', users: 12_400, note: 'All visits to /pricing' },
{ stage: 'Signed up', users: 3_180, note: 'Free trial start' },
{ stage: 'Verified email',users: 2_640, note: 'Clicked the activation link' },
{ stage: 'Onboarded', users: 1_790, note: 'Finished the 4-step setup' },
{ stage: 'Activated', users: 1_250, note: 'Created their first project' },
{ stage: 'Paid', users: 432, note: 'Upgraded to a paid plan' },
]
const rows: Row[] = stages.map((s, id) => ({ id, ...s }))
const columns: ColumnDef<typeof features, Row>[] = [
{ field: 'stage', header: 'Stage', width: 160 },
{ field: 'users', header: 'Users', width: 110, align: 'right',
format: { type: 'number', options: { maximumFractionDigits: 0 } } },
{ field: 'note', header: 'Definition', width: 280 },
]
let api = $state<SvGridApi<typeof features, Row> | null>(null)
let displayed = $state<Row[]>(rows)
let highlighted = $state<string | null>(null)
function sync() { displayed = (api?.getDisplayedRows() as Row[]) ?? rows }
const compact = (v: number) => (Math.abs(v) >= 1e3 ? (v / 1e3).toFixed(v % 1e3 ? 1 : 0) + 'k' : String(Math.round(v)))
const spec = $derived.by<ChartSpec>(() => ({
type: 'funnel',
categories: displayed.map((r) => r.stage),
series: [{
label: 'Users',
values: displayed.map((r) => r.users),
}],
width: 540,
height: 360,
palette: ['#2563eb', '#1d4ed8', '#1e40af', '#1e3a8a', '#312e81', '#3730a3'],
}))
function onSelect(sel: ChartSelection) {
highlighted = sel.category
}
</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);">
Funnel: signup conversion across 6 stages
</p>
<p class="mt-0.5 text-xs" style="color: var(--sg-muted);">
Each trapezoid's width is proportional to its value relative to the top. Conversion shown
inline; hover for the step drop-off vs. the previous stage.
{#if highlighted}<span style="color: var(--sg-accent); margin-left: 8px;">Last clicked: {highlighted}</span>{/if}
</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() }}
onFiltersChange={sync}
onSortingChange={sync}
/>
</div>
<div class="shrink-0 rounded-lg border p-3" style="width: 580px; border-color: var(--sg-border); background: var(--sg-bg);">
<SvGridChart {spec} formatValue={compact} {onSelect} />
</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.