⚠️ Experimental. Sankey is not covered by the 1.0 stability guarantee — its layout, rendering, and options may change without a major-version bump.
A Sankey diagram shows how a quantity flows between stages: nodes stand in columns, bands connect them, and the width of each band is the value it carries. It answers "where did it come from and where did it go" — budgets and their spending, money trails, funnels, energy balances.
The simplest case first: a city budget in two columns — where the money comes from on the left, where it goes on the right. Hover a band or a node to follow its flows; the tooltip gives each flow's share of its source.
Data
A Sankey takes the same { nodes, links } shape as the Graph. Each link is a flow from source to target with a positive value.
new RareCharts.Sankey('#chart', { title: 'City budget' }).setData({
nodes: [
{ id: 'tax', label: 'Income tax' },
{ id: 'health', label: 'Health' },
],
links: [
{ source: 'tax', target: 'health', value: 9.4 },
],
});
nodesis optional. Without it, nodes are taken from the links in order of appearance, with the id as the label. Passnodesto set labels, colors and order.- Aliases.
from/toare accepted forsource/target, andflowsforlinks. - Columns come from the longest path from the sources. Pin a node to a column with
column: nwhen the columns carry meaning of their own ("investors / vehicles / recipients"). - Repeated flows. Two links of the same pair and the same
typeare one flow: their values are summed, with a console warning. Different types between one pair stay separate bands. - Invalid input is dropped with a warning: a missing, zero, negative or non-numeric
value, or a link to a node not innodes. - Cycles are an error. A Sankey is acyclic: a link chain that returns to its start (A → B → A) throws, naming the cycle.
- In ≠ out. A node is as tall as the larger of its inflow and outflow.
Recommended size
Up to 4–5 columns and about 30 nodes. Past that, bands get too thin to read and labels collide — aggregate small flows into an "Other" node on the data side. There is no hard limit in code.
Money trail
Color bands by type and the legend lists the types. Hover a band to highlight it with its two nodes; click it to pin the tooltip, so a long label or links inside it can be read. A click elsewhere or Escape releases it.
The setup: linkColor: 'type' and a linkTypes map; anything else on a link — here a label — shows up in its tooltip.
new RareCharts.Sankey('#chart', {
linkColor: 'type',
linkTypes: {
equity: { color: '#00aaff', label: 'Equity' },
loan: { color: '#fa8c16', label: 'Loan' },
},
}).setData({
links: [
{ from: 'Pension fund', to: 'Holding A', value: 140, type: 'equity', label: 'Series B, 2021' },
],
});
Five columns
The recommended upper bound: an energy balance from primary sources to useful and lost energy, bands shaded from source to target color with linkColor: 'gradient'.
Mobile
With the default orientation: 'auto' the diagram turns 90° when its container is at most mobileBreakpoint (480 px) wide: columns become rows, flows run top to bottom, and the chart grows by rowHeight per row instead of squeezing columns into a narrow width. Force either layout with orientation: 'horizontal' or 'vertical'.
Options
| Option | Type | Default | Description |
|---|---|---|---|
height | number | 400 | Card height in px for the horizontal layout — title, legend and footer included |
orientation | string | 'auto' | 'horizontal', 'vertical', or 'auto' — vertical on narrow containers |
mobileBreakpoint | number | 480 | Container width (px) at or below which 'auto' turns vertical |
rowHeight | number | 140 | Height per column (row) in the vertical layout |
nodeWidth | number | 12 | Node thickness across the flow, px |
nodePadding | number | 12 | Gap between nodes of one column, px |
nodeAlign | string | 'justify' | 'justify' (end nodes to the last column), 'left', 'right', 'center' |
nodeSort | null | string | function | null | Order inside a column: null keeps input order, 'value' puts big nodes first, 'auto' follows the relaxed layout, or a comparator (a, b) => number |
iterations | number | 6 | Layout relaxation rounds — more straightens bands at the cost of time |
linkColor | string | 'source' | 'source', 'target', 'type', 'gradient', or any CSS color |
linkOpacity | number | 0.45 | Band opacity at rest |
linkTypes | object | — | { type: { color, label } } — band colors and legend for linkColor: 'type' |
labels | string | false | 'auto' | Node labels, right of each node (the last column gets a gutter sized to its longest label); long ones are cut with an ellipsis to the room before the next column, the full name stays in the tooltip. false leaves them to the tooltip |
minLabelSize | number | 10 | Nodes thinner than this (px) get no label, only a tooltip |
showValues | boolean | true | Append the node value to its label |
valueFormat | function | locale number | v => string for labels, tooltips and the table |
tooltipFormat | function | built-in | ({ node, incoming, outgoing }) => html |
linkTooltipFormat | function | built-in | ({ link, source, target }) => html |
tableFallback | boolean | true | A visually hidden table of the flows for screen readers |
animate / duration / ease | true / 600 / 'cubicOut' | Entry fade of the bands; off under prefers-reduced-motion |
Text slots title, subtitle, source and note, and the controls slot for your own inputs, work as on every chart — see Settings.
Accessibility
The SVG is labelled from title / subtitle. Every node and band carries an accessible name ("Income tax → Health: 9.4"); nodes are reachable with Tab, and Enter or Space pins a node's tooltip. A visually hidden table lists every flow, so the data stays readable without the picture.