Skip to content

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.

Live example

Basic usage ​

ts
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:

ts
renderSankey(data: SankeyChartData, options?: SankeyChartOptions): { nodeCount: number; linkCount: number }

Data format ​

ts
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.
  • column is 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 column that goes to the same or an earlier column.

Options ​

OptionTypeDefaultDescription
nodeWidthnumber12Node bar width in CSS px (the bar height when vertical).
nodePaddingnumber10Gap 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.
iterationsnumber32Relaxation 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.
linkOpacitynumber0.5Ribbon opacity.
colorsColorInput[]theme paletteCategorical palette in fixed order.
levelsSankeyLevelOptions[]—Per-column node color and link opacity. See Colors and labels.
labelsbooleantrueNode labels beside the bars.
formatChartValueFormatlocale numberValue text of labels, tooltip and accessibility. See Number & Date Formats.
valueLabelstring'Value' (localized)Name of the values in the tooltip and data table.
titlestringChart title.
descriptionstringAccessible name of the canvas.
legendChartLegendOptions | booleanshown with 2+ groupsLists the node groups. Static: no toggle or emphasis.
paddingChartPaddingOptionsderivedPlot margins in CSS px.

Layout ​

The layout follows d3-sankey and runs in the engine (Rust):

  1. Columns: longest path from the sources, adjusted by align or an explicit column.
  2. Node height is proportional to its value, scaled so the fullest column fits.
  3. iterations rounds 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 ​

ts
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.

Live example

Colors and labels ​

  • Node color: by group when 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 ECharts levels) styles a column: the node color (a node's own color still wins) and the opacity of the links leaving that column. When several entries name the same column, the last one wins.
levels[] fieldTypeDefaultDescription
columnnumber—Column index (0 = sources), counted after unused columns are removed.
colorColorInputnode color rule aboveColor of the nodes in the column.
linkOpacitynumberchart linkOpacityOpacity 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):

ts
// 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.

See also ​

MIT License