Sankey and chord diagrams
Where things go: energy from five sources through two carriers to four uses as a three-column sankey whose ribbon width is the value, and trade between five regions as a chord diagram, the shape for flows that run both ways between the same arcs. Hover a ribbon for its value, a node for its total.
A live, editable Svelte 5 data grid example from the SvGrid gallery (Charts). See the SvGrid documentation for the full API.
About this example
Flow diagrams: a sankey takes sankeyNodes and sankeyLinks (source, target, value), places the nodes in columns by their depth and draws each link as a ribbon whose width is the value; a chord takes the same nodes and links and arranges them on a circle, the shape for flows that go both ways; specToSankey builds nodes and links from a split spec. Free, in @svgrid/grid.
Where things go: energy from its sources through the carriers to its uses, and trade between five regions.
- A sankey takes
sankeyNodesandsankeyLinks(source,target,value); nodes are placed in columns by their depth and the ribbon width is the value. Three columns here: source, carrier, use. Hover a ribbon for its value, a node for its total. - A chord takes the same nodes and links but arranges them on a circle, which is the shape for flows that go both ways: exports from A to B and from B to A are two ribbons between the same arcs.
specToSankeybuilds nodes and links from a split spec when the data comes from rows.
Free, in @svgrid/grid.
Imports, features and API used
Imports: @svgrid/grid, ../shared/ChartDataGrid.svelte, ../shared/ViewSwitch.svelte
Frequently asked questions
How are the sankey columns decided?
By depth: a node with no incoming links is in the first column, its targets in the next, and so on. Give a node a colour through sankeyNodes; the links take the colour of their source.
When is a chord better than a sankey?
When flows run both ways between the same pair, such as exports in each direction. A sankey wants a direction; a chord puts both ribbons between the same two arcs.
Can I build the links from rows?
Yes: rowsToChartSpec with a category and a series field, then specToSankey turns that split spec into nodes and links.
Related documentation
Source code (448-chart-flow.svelte)
<!-- Documented in: docs/help/charts/gallery.md -->
<script lang="ts">
/**
* 448. Sankey and chord diagrams
* ------------------------------
* Where things go: energy from its sources through the carriers to its
* uses, and trade between five regions.
*
* - A sankey takes `sankeyNodes` and `sankeyLinks` (`source`, `target`,
* `value`); nodes are placed in columns by their depth and the ribbon
* width is the value. Three columns here: source, carrier, use. Hover a
* ribbon for its value, a node for its total.
* - A chord takes the same nodes and links but arranges them on a circle,
* which is the shape for flows that go both ways: exports from A to B
* and from B to A are two ribbons between the same arcs.
* - `specToSankey` builds nodes and links from a split spec when the data
* comes from rows.
*
* Free, in @svgrid/grid.
*/
import { SvChart, type ChartSpec } from '@svgrid/grid'
import ChartDataGrid from '../shared/ChartDataGrid.svelte'
import ViewSwitch from '../shared/ViewSwitch.svelte'
const energy: ChartSpec = {
type: 'sankey',
categories: [],
series: [],
sankeyNodes: [
{ id: 'Solar', color: '#f59e0b' }, { id: 'Wind', color: '#0ea5e9' }, { id: 'Gas', color: '#94a3b8' }, { id: 'Nuclear', color: '#8b5cf6' }, { id: 'Hydro', color: '#2563eb' },
{ id: 'Electricity', color: '#16a34a' }, { id: 'Heat', color: '#ef4444' },
{ id: 'Homes' }, { id: 'Industry' }, { id: 'Transport' }, { id: 'Losses', color: '#cbd5e1' },
],
sankeyLinks: [
{ source: 'Solar', target: 'Electricity', value: 18 },
{ source: 'Wind', target: 'Electricity', value: 27 },
{ source: 'Gas', target: 'Electricity', value: 22 },
{ source: 'Gas', target: 'Heat', value: 31 },
{ source: 'Nuclear', target: 'Electricity', value: 19 },
{ source: 'Hydro', target: 'Electricity', value: 9 },
{ source: 'Electricity', target: 'Homes', value: 30 },
{ source: 'Electricity', target: 'Industry', value: 36 },
{ source: 'Electricity', target: 'Transport', value: 14 },
{ source: 'Electricity', target: 'Losses', value: 15 },
{ source: 'Heat', target: 'Homes', value: 19 },
{ source: 'Heat', target: 'Industry', value: 12 },
],
title: 'Energy flows',
subtitle: 'Sources to carriers to uses, terawatt-hours',
height: 380,
}
const regions = ['Europe', 'N. America', 'Asia', 'S. America', 'Africa']
const trade = [
[0, 62, 88, 21, 17],
[58, 0, 74, 33, 9],
[95, 81, 0, 26, 24],
[18, 29, 22, 0, 6],
[14, 8, 27, 5, 0],
]
const chord: ChartSpec = {
type: 'chord',
categories: [],
series: [],
sankeyNodes: regions.map((r) => ({ id: r })),
sankeyLinks: regions.flatMap((from, i) => regions.map((to, j) => ({ source: from, target: to, value: trade[i]![j]! })).filter((l) => l.value > 0)),
title: 'Trade between regions',
subtitle: 'Exports both ways, billions',
height: 380,
}
// 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']]} />
<span class="note">
A three-column sankey with the ribbon width as the value, and a chord diagram for flows that
run both ways between the same arcs. Hover a ribbon or a node.
</span>
</header>
<div class="row">
<div class="pane">
{#if view === 'chart'}
<SvChart spec={energy} legend={false} autosize />
{:else}
<ChartDataGrid spec={energy} />
{/if}
</div>
<div class="pane">
{#if view === 'chart'}
<SvChart spec={chord} legend={false} autosize />
{:else}
<ChartDataGrid spec={chord} />
{/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);
}
.row {
display: flex;
flex: none;
gap: 12px;
flex-wrap: wrap;
}
.pane {
flex: 1 1 420px;
border: 1px solid var(--sg-border, #e2e8f0);
border-radius: 10px;
background: var(--sg-bg, #fff);
padding: 8px 10px;
min-width: 0;
}
</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.