Skip to content
Folio

Charts

SankeyChart

Where a quantity goes as it moves through stages.

Usage

When to reach for it

A funnel, a budget, an energy or traffic breakdown. The only chart here whose data is a graph rather than a table.

Ships from@misoto22/design/charts

TSX
import { SankeyChart } from '@misoto22/design/charts'

Notes

Where a quantity goes as it moves through stages — the shape for a funnel, a budget, an energy or traffic breakdown.

The only chart here whose data is a GRAPH rather than a table, so it takes { nodes, links } instead of rows, and the table view lists the flows rather than the nodes.

Anatomy

SankeyChart anatomy
ElementDescription
Figure framerequiredChartFigure’s <figure>, named by title, with the diagram inside one ChartContainer.
Node rectanglesrequiredThe root’s own node renderer. A node whose name is in config is painted from its gradient; one that is not falls back to currentColor, so it is drawn plainly rather than lost.
Node labels<SankeyChart.NodeLabel>, composed inside <SankeyChart.Node>, and entirely opt-in. Leave it out and NOTHING on the diagram is named — there is no legend here, so the names exist only in the tooltip and in the hidden table.
Flow bands<SankeyChart.Link>. gradient fades the source’s colour into the target’s and is the variant that actually reads as flow; solid gives up colour entirely and lets the node rectangles carry identity.
Hidden data tablerequiredThe sr-only table lists the LINKS — from, to, value — rather than the nodes, because a table of node totals loses every from-and-to the diagram exists to state.

Best practices

Do

  • Treat the nodes array as an addressing table. A link’s source and target are INDEXES into it, so inserting a node at the front silently re-points every link at a different pair — and the layout still renders, which is why this belongs in a test rather than in an eyeball.
  • Compose <SankeyChart.Node> with a <SankeyChart.NodeLabel> inside it. Names are opt-in and there is no legend to fall back on, so a sankey without labels is a set of anonymous grey bands.
  • Keep the flows conserved, or give the shortfall a node of its own with a name. Band width is the only arithmetic on screen, and a node that quietly loses eight percent simply reads as a smaller node.

Don’t

  • Do not reach for it when the quantity only narrows along one path. That is a FunnelChart; a sankey spends its whole layout budget on splits that are not there.
  • Do not put twenty nodes in one column. nodePadding is 10px and the layout distributes what is left, so past a dozen a node rectangle is a few pixels tall and a label centred on it has nowhere to sit.

Examples

default

The only chart here whose data is a GRAPH rather than a table: it takes nodes and links, and each link names its ends by index into that node array. Width is quantity and a node is drawn as tall as the flows through it, so a link the data leaves out quietly shrinks the node it should have fed — and a flow that returns to a stage it already left has no left-to-right reading at all. The hidden table lists the FLOWS rather than the nodes for the same reason: a table of node totals loses every from-to the diagram exists to state. NodeLabel with showValues prints the number beside each name, because a band's width is no easier to measure by eye than a wedge's angle.

Visits by source and outcome
Visits by source and outcome
fromToValue
SearchSigned up42
SearchBrowsed68
SocialBrowsed51
SocialLeft34
DirectSigned up27
DirectLeft18

The four link variants and the two label positions on one graph. gradient fades the source's colour into the target's and is the one that reads as movement; source and target attribute a whole band to one end, which is what to reach for when the question is where this came from rather than what became of it; solid gives up colour entirely and lets the node rectangles carry identity. Labels set to inside need a node wide enough to hold them, which is why nodeWidth jumps from 12 to 76 with the toggle — left at 12 the label runs outside the shape it is meant to sit in.

Visits by source and outcome
Visits by source and outcome
fromToValue
SearchSigned up42
SearchBrowsed68
SocialBrowsed51
SocialLeft34
DirectSigned up27
DirectLeft18

loading

The skeleton, and the one in this set that ignores its data: while isLoading is set the diagram is replaced by a fixed eight-node, eleven-link graph, so the shape reads as a sankey before anything has arrived and the data prop only has to satisfy the type. Nodes and links pulse out of phase rather than together, which is what keeps a skeleton from reading as one blinking block; under prefers-reduced-motion they hold at fixed opacities instead. Node and Link are slots the root reads, so composing them costs nothing here and is what makes the diagram come back as itself when the graph lands.

Visits by source and outcome
Loading

Types

TSX
export type SankeyLinkVariant = 'gradient' | 'solid' | 'source' | 'target'
export type SankeyLabelPosition = 'inside' | 'outside'

Accessibility

  • title is required. The hidden table lists the FLOWS rather than the nodes — a table of node totals would lose every “from → to” the diagram exists to show.
  • Four link variants: gradient reads as flow, source and target attribute a band to one end, solid gives up colour and lets the nodes carry identity.