Skip to content
Connect an agent
Other diagram kinds

Sankey diagrams

A sankey diagram draws flows between stages as ribbons as thick as the amount that moves. Use it to show where traffic, data, or cost goes: requests through a platform, a cloud bill by service and team, a pipeline that narrows as records drop out, or the outcomes of a request. It is for anyone, human or AI agent, who writes diagram source.

TL;DR

QuestionAnswer
What is it for?Where a total goes. A ribbon is as thick as the amount it carries, and a card is as tall as the amount that passes through it.
How do I start one?Write sankey "Title", then one line for each flow: "Edge" -> "API gateway" : 12000. A node exists once a flow names it, so it has no id.
How is an amount written?Plain digits right after the colon, above zero, with no sign, comma, or unit. Say what it counts once, on the header, with unit="req/s".
How do I give a node an icon or a tone?A node "API gateway" icon=lucide:server tone=blue line, before or after the flows that name it. Without one, a node takes the next tone of a fixed rotation.
What is drawn?Rounded cards joined by smooth ribbons in the tone of the node they leave, fading to the tone of the node they reach. The widest ribbons carry their amount in a pill, and a line under the picture gives the unit and the total.
What if the flow does not balance?A node whose flow in and out differ gets a dashed wedge for the difference, and lint warns with W200.
What if flows go round in a loop?The source is refused with E100, which names the loop. A sankey only goes forward.
What are the limits?60 nodes, 150 flows, and 12 columns. Past one, the source is refused with E140. More than 8 columns, 40 nodes, or 100 flows draws a warning.
Does Mermaid convert?Yes. sankey-beta and sankey become a Plotwork sankey. See From Mermaid.

Ask your agent

text
Use Plotwork to draw a sankey diagram of where our monthly cloud bill goes: from the bill to each service, then to the team that owns it, and then to the product it pays for, with the amounts in dollars.
text
Use Plotwork to draw a sankey diagram of what happens to 10,000 requests per second: served from the cache or from the origin, rejected, or failed, with failures in red and no amounts on the cards.

Example

Request traffic through the platform: 10 nodes and 13 flows in 4 columns, total 12,000 req/s. How 12,000 requests per second move from the edge through the API gateway and three services to the data stores, with blocked and failed requests branching off.

The nodes take their tones in turn in the order they first appear, and tone= changes one: the cache is aqua, and what goes wrong is red. The ribbon from Edge to Blocked skips two columns, so it has a lane under the gateway and the services and never passes behind a card. Every node here balances, so what goes in comes out and no wedge is drawn. The line under the picture says the unit, the total that enters, and how much there is of it.

The language

One statement to a line. Text is always in double quotes, options are key=value, and # starts a comment. Indentation means nothing to the parser.

StatementMeaningExample
sankey "Title" unit=<unit> values=<on|off> sort=<desc|written> align=<justify|left> desc="<text>"The header. Every option is optional, and so is the title.sankey "Traffic" unit="req/s"
"<From>" -> "<To>" : <amount> tone=<tone>One flow. tone= colors this ribbon only."Edge" -> "API gateway" : 12000
node "<Name>" icon=<set:name> tone=<tone>The icon and tone of a node, once.node "API gateway" icon=lucide:server tone=blue
desc "<text>"The plain-language description, as a statement. The same as desc= on the header.desc "How requests move."

A node's name is its identity, so writing the same name in two flows is one node. Write each pair of nodes once, with the total: a second line for the same pair is refused with E130. A node line for a name that no flow uses is refused, because a node with no flow has no size.

Plotwork
sankey "Orders" unit="orders"
"Visits" -> "Carts" : 3000"Visits" -> "Left" : 7000"Carts" -> "Paid" : 1400 tone=green"Carts" -> "Abandoned" : 1600 tone=amber

Header options

OptionValuesDefaultMeaning
unit=a short word or symbol, at most 12 charactersnoneWhat the amounts count. $, €, £, and ¥ lead the figure ($1,240), % follows it (85%), and any other unit follows after a space (120 MB). A pill shows the unit only when it is a symbol, so a pill stays small.
values=on, offonoff hides the amount on each card and each ribbon. The line under the picture still says the total.
sort=desc, writtendescThe order of the first column before crossings are removed, and the tie-break after: largest first, or as written.
align=justify, leftjustifyWhere a node with nothing leaving it goes. justify puts every one in the last column, so the ends line up. left leaves it in the column after its source, which suits a funnel.
desc=quoted textnoneThe plain-language description.

Columns and order

A node sits one column to the right of the furthest node that flows into it. Within a column the order is chosen to cross as few ribbons as possible, and the same source always gives the same picture. A flow that skips a column gets a lane in each column it passes, so it runs between the cards.

With align=left a funnel reads from left to right, and each outcome sits beside the stage it leaves:

What happens to a request: 9 nodes and 8 flows in 4 columns, total 10,000 req/s. Where 10,000 requests per second end up: served from the cache or the origin, rejected, or failed.

With values=off the picture shows the shapes and the line under it keeps the total. The text description still lists every amount.

Cost by service, team, and product

When many ribbons cross, the order keeps related nodes together and the gradient shows where each ribbon goes. Amounts of 10,000 and more are drawn compactly on a card ($42K), and in full in the text description.

Where the cloud bill goes: 10 nodes and 16 flows in 4 columns, total $78,000. One month of cloud spend, from the bill to the kind of service, the team that owns it, and the product it pays for.

A pipeline that narrows

A sankey shows where records go as stages drop some of them. Each stage here passes on what it received less what it set aside:

Event pipeline: 10 nodes and 9 flows in 6 columns, total 1,000 MB/s. Events from three sources through ingest, validation, cleaning, and enrichment. Validation sets aside what is malformed, and the rest reaches the warehouse and the stream.

Flow that does not balance

What goes into a node should come out of it, with the rest drawn as a flow to a node of its own. When it does not, the card gets a dashed wedge as thick as the difference: on its right edge for flow lost, on its left edge for flow gained, with -60 or +80 beside it when there is room. Lint warns with W200, names the node, and says what to do. A difference of half a percent or less is rounding and draws nothing. A node with flow on one side only, a source or an end, never counts.

Plotwork
sankey "Ingest" unit="MB/s"
"Web" -> "Ingest" : 1000"Ingest" -> "Validate" : 940

Here Ingest takes in 1,000 and passes on 940, so it loses 60. Add a flow such as "Ingest" -> "Duplicates" : 60 to account for it.

Colour

Each node takes the next of blue, orange, aqua, violet, amber, green, and neutral in the order the nodes first appear. Red is left out of the rotation because it is what you choose for a failure, so a node is never red by accident. tone= on a node changes it, and tone= on a flow colors that ribbon alone. A ribbon takes the tone of the node it leaves and fades to the tone of the node it reaches. Nobody writes a color value: the theme owns every color in light and dark.

What a card and a ribbon show

A card holds its icon on a chip, its name, and its amount. It is as wide as its widest word and its amount need, and its name wraps to three lines before it is cut with an ellipsis and W203. It is as tall as the amount through it, or as its content needs when that is more, and a small card keeps its ribbons in its middle. An amount appears on a ribbon at least 10px thick, where there is a clear place for it, and never over a card or another amount. The text description lists every flow with its amount and its share of what leaves its source.

Limits and warnings

LimitValueWhat happens past it
Nodes60E140 before any layout, with the advice to fold the small ones into "Other".
Flows150E140.
Columns12E140.
An amountabove 0 and at most one trillionE136.
A unit12 charactersE136.
Columns before the warning8W200.
Nodes and flows before the warning40 and 100W200.

The picture is 880px wide for up to four columns, the width a Markdown column keeps text readable at. More columns need more room, so a picture of eight columns is a little over 1,100px and lint says W202 when it would shrink text too far. The layout is plain arithmetic, so a sankey of 60 nodes and 150 flows is laid out in a few milliseconds.

Every mistake comes back as a result with a code, a line, a column, and a hint. Warnings never stop the drawing.

CodeWhen it firesFix
E100A flow with no amount or no colon, an arrow that is not ->, a name that is not in quotes, an amount with a comma, a node line for a name no flow uses, a sankey with no flows, or flows that go round in a loop.Read the hint, which names the line to write.
E101, E110, E121, E122An unterminated string, an unknown statement, an unknown tone, or an unknown icon.Read the hint, which names the nearest choice.
E130The same pair of nodes written twice, or a node described twice.Write it once, with the total.
E135An unknown option.Read the hint.
E136An unknown sort=, values=, or align=, a bad unit, or an amount that is not a number or is zero.Read the hint, which says what to write.
E140More than 60 nodes, 150 flows, or 12 columns.Fold the small ones into one "Other", or split the diagram by stage.
W200A node that loses or gains flow, ribbons thinner than 2px, more than 8 columns, or more than 40 nodes or 100 flows.Account for the difference, fold the small flows into "Other", or split the diagram.
W202The picture is wider than a Markdown column and its text would shrink.Merge stages, or shorten the names.
W203A name was cut with an ellipsis.Shorten the name.

From Mermaid

Mermaid sankey-beta (and sankey, which Mermaid is renaming it to) converts to a sankey. Each line is Source,Target,Value, a name with a comma is in double quotes, and a %% comment or a blank line is skipped. Ask your agent to convert it, or use the convert_mermaid tool. The migration guide covers the converter in full.

Mermaid
---title: Product budgetconfig:  sankey:    showValues: false---sankey-beta
%% source,target,valueBudget,Engineering,600Budget,Marketing,250Budget,Support,150Engineering,Platform,400Engineering,Application,200Marketing,Campaigns,200Marketing,Events,50Marketing,Events,0Support,"Help centre, docs",100Support,Tooling,50Support,Tooling,0

The converter reports the settings it dropped (M200) and the two flows of zero it dropped (M216):

Plotwork
sankey "Product budget"
"Budget" -> "Engineering" : 600"Budget" -> "Marketing" : 250"Budget" -> "Support" : 150"Engineering" -> "Platform" : 400"Engineering" -> "Application" : 200"Marketing" -> "Campaigns" : 200"Marketing" -> "Events" : 50"Support" -> "Help centre, docs" : 100"Support" -> "Tooling" : 50
MermaidPlotworkReport
Source,Target,Value"Source" -> "Target" : Valuenone
A source,target,value line, a %% comment, and a blank lineSkipped.none
A name in double quotes, with a comma or a doubled quoteRead as written.none
Front matter title: and accTitle:The title.none
accDescr:desc=none
Two lines for the same pair of nodesOne flow with the amounts added.M209
A flow of zero, and a flow with a node that has no nameDropped.M216
HTML in a nameRemoved.M205
Front matter config: such as showValues, nodeAlignment, and linkColor, and %%{init}%%Dropped. Put the unit on the header with unit=, and use align=left for left alignment.M200
A negative or non-numeric amount, a line that is not a flow, and a flow that closes a loopNothing is converted, or the line is skipped when the conversion is lenient.M110, or M218
More than 60 nodes or 150 flowsNothing is converted.M102
No titleConverted.M214

See also

DocWhy
Writing diagramsThe flow, sequence and schema languages, and the rules every kind shares
Examples to copyFinished diagrams with the prompt that gets an agent to write each one
Migrating from MermaidConvert existing Mermaid diagrams, and what is dropped
TreemapsWhere a total goes as nested areas, when the hierarchy matters more than the flow
Pie chartsThe shares of one whole, when there is no flow between stages
Language referenceEvery kind, node type, option and diagnostic
Diagram kindsThe kinds Plotwork draws and the header of each