Skip to content

Usage

Sankey data points define directed flows between named nodes.

{
from: 'Coal',
to: 'Electricity',
flow: 42
}
OptionDescription
alphaOpacity applied to colorFrom and colorTo when rendering a flow. Not scriptable. Defaults to 0.5.
colorFromScriptable color for the source node side.
colorModeFlow coloring mode: gradient, from, or to.
colorToScriptable color for the target node side.
columnOptional column assignment by node key.
flowColorScriptable flow color. When set, it overrides colorMode for flows without changing node colors.
flowLabelsOptional flow-value labels with scriptable styling and placement. See Flow Labels.
hoverColorFromScriptable hover color for the source node side. Defaults to colorFrom saturated and darkened.
hoverColorToScriptable hover color for the target node side. Defaults to colorTo saturated and darkened.
labelsOptional display labels by node key.
nodeLabelsNode label styling and placement. Positions are auto, left, top, right, bottom, or center. See Node Labels.
nodeMinSizeMinimum drawn node bar size, in CSS pixels. Resolved per node; see Node Min Size. Defaults to 0 (no minimum).
nodePaddingRequested vertical gap between nodes within a column, in CSS pixels — measured before the chart is scaled to fit, so the rendered gap is approximate. Resolved per node; see Node Padding.
nodePaddingModeauto (default) or even. See Node Padding Mode for the trade-off.
nodeWidthNode rectangle width in pixels.
orientationFlow direction: horizontal (left to right, the default) or vertical (top to bottom).
priorityOptional ordering priority by node key.
sizeNode size strategy: min or max.

hoverColorFrom and hoverColorTo are never simply unset: leaving them out doesn’t turn off the hover effect, it falls back to a computed color — colorFrom/colorTo run through Chart.js’s own getHoverColor helper (saturated by 0.5, darkened by 0.1). To get no color change on hover, set hoverColorFrom/hoverColorTo explicitly to the same value as colorFrom/colorTo.

Use nodeLabels to place and style labels globally or by node. Position names follow Chart.js layout positions, with auto preserving Sankey’s automatic left/right placement.

nodeLabels: {
position: {
Coal: 'right',
Electricity: 'center',
Energy: 'left'
},
color: (node) => node.key === 'Electricity' ? 'white' : 'black',
backgroundColor: {
Electricity: 'blue'
},
borderRadius: 3,
padding: 4,
font: {
size: 11,
weight: 'normal'
}
}

nodePadding sets the vertical gap between nodes stacked in the same column, in CSS pixels — but it’s a request, not an exact distance: the value is measured before the chart is scaled to fit its drawing area, so the gap that actually renders is approximate rather than exact. In auto mode it typically renders a little smaller than requested, narrowing further the more gaps a column has (a nodePadding: 10 renders as roughly 9px in a 400px-tall chart with five nodes in the column). In even mode a column packs more tightly instead, so the rendered gap can land at or close to the requested value — see Node Padding Mode for why. Neither mode promises a direction in general. The ratio between different nodePadding values you set is preserved either way. Like nodeLabels, it resolves per node, in three forms:

  • A plain number applies the same gap everywhere: nodePadding: 20.
  • A { before, after } object sets an asymmetric gap: more space above the node than below it, or vice versa. before is the gap above the node and after is the gap below it, regardless of orientation (in a vertical chart, “above”/“below” become “left”/“right” visually, but before/after keep the same meaning). Either side left out falls back to 10.
  • A function receiving the node and returning either form, for full control: nodePadding: (node) => node.key === 'Coal' ? { after: 28 } : 4.

A top-level object is always a Record of node keys (like nodeLabels), never a { before, after } gap directly — that shape would be ambiguous with a keyed Record. Write a function for a global asymmetric gap instead:

// per-node gaps, keyed by node
nodePadding: {
Coal: { after: 28 },
Solar: 4
}
// a global asymmetric gap: more space below every node than above it
nodePadding: () => ({ before: 4, after: 8 })

Gaps collapse like CSS margins instead of summing: the space between two adjacent nodes in a column is max(prevNode.after, nextNode.before), not their sum. A plain nodePadding: 10 is therefore equivalent to every node using { before: 10, after: 10 } — max(10, 10) is still 10.

By default (nodePaddingMode: 'auto'), a node’s vertical position also has to clear every node stacked above it in the columns feeding into it, so it stays aligned with the flows arriving from the left. That alignment is the point, but it has a side effect: two nodes in the same column can end up with visibly different gaps above them, depending on how crowded the columns to their left are. Setting nodePaddingMode: 'even' instead stacks a column’s nodes back to back, top node’s position unchanged, each following node placed at exactly max(prevNode.after, nextNode.before) below the previous one — regular gaps, but the column’s nodes are no longer aligned with their incoming flows, so those flows bend more to reach them. Note also that size: 'min' can make a node’s drawn bar smaller than the flows it carries, so flows can still overlap visually in even mode exactly as they can today in auto mode.

See the Node Padding sample for a single wide gap separating groups within a column, and for auto vs. even side by side.

nodeMinSize gives a node’s drawn bar a minimum size in CSS pixels, so a node carrying a tiny flow next to a huge one doesn’t disappear into a sub-pixel line. It resolves per node, in the same three forms as nodePadding: a plain number (nodeMinSize: 6), a Record keyed by node key, or a function receiving the node. Defaults to 0, meaning no minimum.

Only the drawn bar is stretched — the underlying data isn’t. When a node’s natural size (in flow units, converted to pixels) is smaller than nodeMinSize, its bar grows to nodeMinSize symmetrically around the node’s real position: half the growth extends above/left, half below/right. The node’s label follows the same stretched rectangle. The flows attached to the node keep their exact proportional height and still connect at the same points they always did — stretching the bar doesn’t move or resize any flow, so a bar that grew to be visible can still have flows that are their own hairline thickness inside it.

Space for the stretch is reserved during layout, so a stretched bar does not overlap a neighboring node — the gap between two stretched bars in the same column still respects the requested nodePadding, in both auto and even mode.

nodeMinSize makes the node visible, not the flow. A flow of 0.37 arriving at a 6px node bar is still a hairline inside that bar — if the flow itself needs to be visible, the data needs a minimum applied before charting (e.g. clamped upstream), and that’s usually worth calling out in a label rather than silently distorting the chart.

See the Node Min Size sample for a huge node next to several tiny ones, with and without a minimum.

Flow labels use the same visual options and display the numeric flow value. Since flows are Chart.js elements, these options use the standard scriptable context.

flowLabels: {
display: (context) => context.raw.flow >= 10,
position: 'center',
color: 'black',
backgroundColor: 'white',
borderRadius: 3,
padding: 4,
font: {
size: 11,
weight: 'normal'
}
}