Built-in charting: custom buildSpec
When group-by / split-by can't express the chart, charting.buildSpec hands you the current rows and you return any ChartSpec - here a custom sankey rendered right in the built-in Chart panel. Filter the flow table and the ribbons redraw.
A live, editable Svelte 5 data grid example from the SvGrid gallery (Charts). See the SvGrid documentation for the full API.
What this example shows
Shapes that aren't a group-by aggregation - here a sankey flow diagram - still live in the built-in docked panel. Return any `ChartSpec` from the current rows via `charting.buildSpec`; the panel scopes it to the selection and re-runs it on every grid change, so the custom chart stays live. Filter the flow table and the ribbons re-draw.
Imports, features and API used
Imports: @svgrid/grid
Table features registered: rowSortingFeature, columnFilteringFeature
Columns: from (From), to (To), users (Users)
Source code (355-charting-custom-buildspec.svelte)
<!-- Documented in: docs/help/charts.md -->
<script lang="ts">
/**
* 355. Built-in charting: custom chart via `buildSpec`
* ---------------------------------------------------
* Shapes that aren't a group-by aggregation - here a sankey flow diagram -
* still live in the built-in docked panel. Return any `ChartSpec` from the
* current rows via `charting.buildSpec`; the panel scopes it to the selection
* and re-runs it on every grid change, so the custom chart stays live. Filter
* the flow table and the ribbons re-draw.
*/
import {
SvGrid,
tableFeatures,
rowSortingFeature,
columnFilteringFeature,
type GridColumns,
type ChartSpec,
} from '@svgrid/grid'
const features = tableFeatures({ rowSortingFeature, columnFilteringFeature })
type Flow = { id: number; from: string; to: string; users: number }
// A small acyclic user-journey funnel.
const rows: Flow[] = [
{ id: 1, from: 'Landing', to: 'Browse', users: 5200 },
{ id: 2, from: 'Landing', to: 'Sign up', users: 1800 },
{ id: 3, from: 'Browse', to: 'Sign up', users: 2400 },
{ id: 4, from: 'Browse', to: 'Exit', users: 2800 },
{ id: 5, from: 'Sign up', to: 'Activated', users: 3100 },
{ id: 6, from: 'Sign up', to: 'Exit', users: 1100 },
{ id: 7, from: 'Activated', to: 'Subscribed', users: 1500 },
{ id: 8, from: 'Activated', to: 'Exit', users: 1600 },
]
const columns: GridColumns<Flow> = [
{ field: 'from', header: 'From', width: 150 },
{ field: 'to', header: 'To', width: 150 },
{ field: 'users', header: 'Users', width: 130, align: 'right', cellDataType: 'number', format: { type: 'number' } },
]
// Build a sankey spec from whatever rows the grid currently shows.
function buildFlow(data: Flow[]): ChartSpec {
const ids = new Set<string>()
for (const r of data) { ids.add(r.from); ids.add(r.to) }
return {
type: 'sankey',
categories: [],
series: [],
sankeyNodes: [...ids].map((id) => ({ id, label: id })),
sankeyLinks: data.map((r) => ({ source: r.from, target: r.to, value: r.users })),
}
}
</script>
<div class="demo-page" style="height: 620px; display: flex; flex-direction: column;">
<p class="demo-hint" style="margin: 0 0 8px;">
A custom <strong>sankey</strong> - impossible via group-by - rendered in the built-in chart panel through
<code>charting.buildSpec</code>. Filter the flow table and the ribbons re-draw live.
</p>
<div style="flex: 1; min-height: 0;">
<SvGrid
columnResize
data={rows}
{columns}
{features}
sortable
filterable
filterMode="row"
containerHeight="100%"
charting={{ defaultOpen: true, position: 'right', width: 460, buildSpec: buildFlow }}
/>
</div>
</div>Related documentation
Related articles
- Progress and Percentage Bar Cells in SvGrid - Build in-cell progress bars in your Svelte 5 data grid - with color thresholds, accessible markup, and sorting that still works.
- How We Built Excel-Style Filters - How SvGrid implements per-column filter menus, type-aware operators, and a shared filter model that works identically for in-memory and server-side data.
- A Custom Column Header Menu in SvGrid - Build a per-column header menu for sort, hide, pin, and custom actions using header snippets and your own dropdown component.
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.
- 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.