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.
About this example
A custom chart inside the built-in panel of the Svelte 5 data grid. When group-by and split-by cannot express the chart, charting.buildSpec hands you the current rows and you return any ChartSpec; here a sankey flow diagram renders in the docked panel, the panel scopes it to the selection and re-runs it on every grid change, so filtering the flow table redraws the ribbons.
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)
Frequently asked questions
What does buildSpec receive?
The rows currently in scope, the displayed rows or the selected ones, plus the column definitions, and it returns a ChartSpec of any type the chart engine supports.
Do the panel's pickers still show?
The Group by and Value pickers are hidden when buildSpec is set, since the spec is yours; the type selector and export remain.
When is buildSpec re-run?
On every change to the displayed rows or the selection, so a filter, sort or edit rebuilds the chart without manual wiring.
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.
Source code (355-charting-custom-buildspec.svelte)
<!-- Documented in: docs/help/charts/from-the-grid.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>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.