Sankey
renderSankey draws flows between nodes as ribbons whose width is proportional to their value, with nodes arranged in columns. Use it for budgets, energy balances, user journeys and other directed, acyclic flows.
Basic usage
import { Charturbo } from 'charturbo';
const canvas = document.querySelector('canvas')!;
const sdk = await Charturbo.create(canvas);
const link = (source: string, target: string, value: number) => ({ source, target, value });
sdk.renderSankey(
{
nodes: ['Coal', 'Gas', 'Solar', 'Electricity', 'Heat', 'Homes', 'Industry'].map((id) => ({ id })),
links: [
link('Coal', 'Electricity', 30), link('Coal', 'Heat', 12),
link('Gas', 'Electricity', 22), link('Gas', 'Heat', 18),
link('Solar', 'Electricity', 14),
link('Electricity', 'Homes', 36), link('Electricity', 'Industry', 30),
link('Heat', 'Homes', 18), link('Heat', 'Industry', 12),
],
},
{ title: 'Energy (TWh)', format: ',.0f' }
);
sdk.enableNodeInteractions(canvas, { autoRender: true });
sdk.render();Signature:
renderSankey(data: SankeyChartData, options?: SankeyChartOptions): { nodeCount: number; linkCount: number }Data format
interface SankeyChartData {
nodes: SankeyNodeInput[];
links: SankeyLinkInput[];
}
interface SankeyNodeInput {
id: string | number;
name?: string; // display name (default: the id)
color?: ColorInput;
group?: string; // color group (palette in first-appearance order; the legend lists the groups)
column?: number; // minimum column (0 = left)
}
interface SankeyLinkInput {
source: string | number; // node id
target: string | number; // node id
value: number; // finite and >= 0
}- A node's value is the larger of its inflow and outflow.
columnis a minimum: a node is always placed to the right of all its sources, and only used columns are kept.- These inputs throw:
- a duplicate node id, or a link to an unknown id (
Sankey link 3 has an unknown source "X"); - a negative or non-finite link value;
- a link from a node to itself, or links that form a cycle (
Sankey links form a cycle: "A" -> "B" -> "A"); - a link between two nodes with explicit
columnthat goes to the same or an earlier column.
- a duplicate node id, or a link to an unknown id (
Options
| Option | Type | Default | Description |
|---|---|---|---|
nodeWidth | number | 12 | Node bar width in CSS px (the bar height when vertical). |
nodePadding | number | 10 | Gap between nodes of a column, in CSS px. |
orient | 'horizontal' | 'vertical' | 'horizontal' | Flow direction: columns left to right, or rows top to bottom. See Vertical layout. |
iterations | number | 32 | Relaxation rounds of the layout. |
align | 'justify' | 'left' | 'right' | 'center' | 'justify' | Column assignment of nodes without column. 'justify' puts sinks in the last column. |
linkColor | 'source' | 'target' | 'neutral' | 'gradient' | 'source' | Ribbon color. 'gradient' blends the source color into the target color. |
linkOpacity | number | 0.5 | Ribbon opacity. |
colors | ColorInput[] | theme palette | Categorical palette in fixed order. |
levels | SankeyLevelOptions[] | — | Per-column node color and link opacity. See Colors and labels. |
labels | boolean | true | Node labels beside the bars. |
format | ChartValueFormat | locale number | Value text of labels, tooltip and accessibility. See Number & Date Formats. |
valueLabel | string | 'Value' (localized) | Name of the values in the tooltip and data table. |
title | string | Chart title. | |
description | string | Accessible name of the canvas. | |
legend | ChartLegendOptions | boolean | shown with 2+ groups | Lists the node groups. Static: no toggle or emphasis. |
padding | ChartPaddingOptions | derived | Plot margins in CSS px. |
Layout
The layout follows d3-sankey and runs in the engine (Rust):
- Columns: longest path from the sources, adjusted by
alignor an explicitcolumn. - Node height is proportional to its value, scaled so the fullest column fits.
iterationsrounds of relaxation move nodes toward the weighted position of their neighbors (right to left, then left to right), followed by collision resolution per column. After the last round, nodes in the same column never overlap.
Links are cubic Bézier ribbons with horizontal tangents at both ends, drawn translucent and thickest first, so thin flows stay visible on top.
Vertical layout
sdk.renderSankey(data, {
title: 'Energy (TWh)',
orient: 'vertical',
linkColor: 'gradient',
levels: [
{ column: 0, linkOpacity: 0.65 },
{ column: 2, color: '#64748b' },
],
});orient: 'vertical'computes the same layout in flow coordinates (along and across the flow) and swaps x and y on screen. Columns become rows stacked from top to bottom.- Hit testing, keyboard positions and exported node rectangles use the same transform.
- Labels are centered on the bar: below nodes in the top half, above the others. In a row, a label is dropped when its center is closer to another than the widest label (estimated by the SDK); the larger node keeps its label.
Colors and labels
- Node color: by
groupwhen groups are given; else one color per node when the nodes fit the palette; else by column (nodes beyond the palette are gray). - Labels show the name and value: to the right of nodes in the left half, to the left of the others. When two labels in a column are closer than a line, the larger node keeps its label.
linkColor: 'gradient': each ribbon's color changes linearly from the source node color to the target node color along the ribbon.levels(like EChartslevels) styles a column: the node color (a node's owncolorstill wins) and the opacity of the links leaving that column. When several entries name the same column, the last one wins.
levels[] field | Type | Default | Description |
|---|---|---|---|
column | number | — | Column index (0 = sources), counted after unused columns are removed. |
color | ColorInput | node color rule above | Color of the nodes in the column. |
linkOpacity | number | chart linkOpacity | Opacity of the links leaving the column, 0–1. |
Interactions
Call enableNodeInteractions(canvas) to turn them on. Hover finds a node first, then a ribbon (top-most first). Only the flows connected to the hovered node or link are highlighted. There is no drill-down. Brush, zoom and cursor sync are disabled.
Tooltip context (see Tooltips & Hover):
// ctx.chartType === 'sankey'
// ctx.flow = { kind: 'link', source, target, value, percentOfSource }
// | { kind: 'node', source, value, inflow, outflow } // node: its name in `source`Data point ids: node index for nodes, node count + link index for links.
Notes
- One relaxation round costs O(N + L log L), so hub nodes stay cheap.
- Accessibility: summary (node, column and link counts, largest flow), a data table (source, target, value, % of source per link), left / right arrows move between links. See Accessibility.