Skip to content

Sankey

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.

⚠️ Experimental. Sankey is not covered by the 1.0 stability guarantee — its layout, rendering, and options may change without a major-version bump.

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 },
  ],
});
  • nodes is optional. Without it, nodes are taken from the links in order of appearance, with the id as the label. Pass nodes to set labels, colors and order.
  • Aliases. from / to are accepted for source / target, and flows for links.
  • Columns come from the longest path from the sources. Pin a node to a column with column: n when the columns carry meaning of their own ("investors / vehicles / recipients").
  • Repeated flows. Two links of the same pair and the same type are 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 in nodes.
  • 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.

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
heightnumber400Card height in px for the horizontal layout — title, legend and footer included
orientationstring'auto''horizontal', 'vertical', or 'auto' — vertical on narrow containers
mobileBreakpointnumber480Container width (px) at or below which 'auto' turns vertical
rowHeightnumber140Height per column (row) in the vertical layout
nodeWidthnumber12Node thickness across the flow, px
nodePaddingnumber12Gap between nodes of one column, px
nodeAlignstring'justify''justify' (end nodes to the last column), 'left', 'right', 'center'
nodeSortnull | string | functionnullOrder inside a column: null keeps input order, 'value' puts big nodes first, 'auto' follows the relaxed layout, or a comparator (a, b) => number
iterationsnumber6Layout relaxation rounds — more straightens bands at the cost of time
linkColorstring'source''source', 'target', 'type', 'gradient', or any CSS color
linkOpacitynumber0.45Band opacity at rest
linkTypesobject—{ type: { color, label } } — band colors and legend for linkColor: 'type'
labelsstring | 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
minLabelSizenumber10Nodes thinner than this (px) get no label, only a tooltip
showValuesbooleantrueAppend the node value to its label
valueFormatfunctionlocale numberv => string for labels, tooltips and the table
tooltipFormatfunctionbuilt-in({ node, incoming, outgoing }) => html
linkTooltipFormatfunctionbuilt-in({ link, source, target }) => html
tableFallbackbooleantrueA visually hidden table of the flows for screen readers
animate / duration / easetrue / 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.