Sankey diagram
chart.sankey
Overview
Pass node labels and directed links with positive values. We assign nodes to layers by longest path, draw value-proportional ribbons, and refuse cyclic graphs or layouts too dense for the canvas. Defaults include canvas 900×500. Do not use for simple category comparisons (use chart.bar) or unweighted dependency graphs (use chart.dag). svg is a complete static, script-free chart. Mark highlight, the dotted guide, and the value panel are not in that SVG, and entry motion, focus, and updates stay inactive there. Pass the entire output.behavior.artifact unchanged to SquillifyChartRuntime.mount(container, artifact, options), then call the returned handle's replay() once to play the entry. A later value change uses update(nextArtifact, {intent}). Injecting svg directly leaves hover and motion off. Interaction is on by default for every host chart: after mount, pointer and keyboard focus open the value panel and the dotted guide. Set behavior.interaction.enabled to false to turn that off. motion.preset accepts only the family's entrance.
Sankey geometry — layering plus curved ribbons — is easy to get wrong and often produces markup that is hard to embed. We emit identical, allowlist-checked SVG every time.
Reliability
Identical input always produces the same layers, ribbons, and SVG. Declared failures: CHART_CAPACITY_EXCEEDED — trigger: too many nodes, links, or layers for the canvas at the chosen size and density; retryable: no; unchanged input: will not succeed; next: follow the recovery intent in the error details. CHART_OUTPUT_TOO_LARGE — trigger: the inline SVG byte budget would be exceeded at the requested size and data density; retryable: no; unchanged input: will not succeed; next: follow the recovery intent in the error details. CHART_THEME_UNKNOWN — trigger: theme or surface names a visual language we do not register for this chart family; retryable: no; unchanged input: will not succeed; next: follow the recovery intent in the error details. CHART_UNSAFE_OUTPUT — trigger: the finished SVG failed our final safety re-validation (this is on us, not your data); retryable: no; unchanged input: will not succeed; next: contact support with the request id — retrying the same input will not help. CHART_OUTPUT_TOO_COMPLEX — trigger: the render hit a structural complexity ceiling (elements, depth, text volume, or path commands) separate from the byte budget; retryable: no; unchanged input: will not succeed; next: contact support with the request id — retrying the same input will not help.
Context footprint
One call replaces bespoke sankey layout math and hand-built SVG. A successful call costs 4 execution credits.
When to use it
When you need to show weighted flows between named stages.
When the graph is cyclic, extremely dense, or the data is a simple bar comparison.
Scope
Non-goals
- Cyclic graphs — a sankey must be acyclic
- Unweighted dependency routing (use chart.dag)
- Simple category bar comparisons (use chart.bar)
Properties
Capabilities
Cost & billing
Flat rate per invocation
File handling
How we handle your data
What this means
This skill may receive sensitive content and may return submitted, transformed, extracted, or reformatted content.
Squillify guarantees
- Runs inside Squillify
- This skill runs inside Squillify.
- Payloads are not sent to an external service by this skill.
- Squillify does not permanently store submitted payloads.
- Telemetry records metadata only, not submitted content.
- Submitted payloads are not used for training.
- Submitted payloads are not reviewed by humans.
- This skill does not target sensitive data.
- The response may contain submitted or transformed content.
Your responsibility
Squillify does not determine whether submitted content is sensitive. Your application is responsible for deciding what data it is allowed to submit. Your application is responsible for securing the response.
Input schema
| Field | Type | Required | Description |
|---|---|---|---|
| nodes | array | Yes | 2..1000 closed node objects with unique stable id, bounded label, and optional shortLabel/group/meaning. |
| links | array | Yes | 1..5000 closed link objects with unique id, node-id endpoints, and a positive exact decimal-string value. Parallel links remain distinct. |
| orientation | string | No | leftToRight (default) or topToBottom. |
| balancePolicy | string | No | report (default) or requireConservation for exact internal-node balance. |
| nodeOrder | object | No | Closed ordering object: optimized (default) or fixed with topology-compatible layers. |
| nodeGroups | array | No | Optional array of at most 24 objects with id, label, and unique existing nodeIds; omit it for no groups and nodes are never merged. |
| nodeWidth | integer | No | Node thickness in design pixels, 8..40; default 16. |
| nodeGap | integer | No | Minimum same-layer node gap in design pixels, 2..32; default 6. |
| linkMinimumWidth | number | No | Visual ribbon floor in design pixels, 0.5..8; default 1. Exact values do not change. |
| labels | object | No | Optional closed object with node and link visibility policies; omit it for the default node-label policy. |
| legend | object | No | Optional closed object with visible and placement properties; omit it for automatic legend visibility. |
| width | integer | No | Canvas width in pixels, 100..1600 (optional; default 900). Outside that range the request is refused as invalid input rather than clamped. The ceiling is the size a chart is read at — a full-HD dashboard or a report page; beyond it the canvas stops being what limits how much fits, and type sized for the canvas is illegible once the export is displayed smaller. It applies per side, so a tall portrait canvas for a long horizontal ranking is allowed. |
| height | integer | No | Canvas height in pixels, 100..1600 (optional; default 500). Outside that range the request is refused as invalid input rather than clamped. The ceiling is the size a chart is read at — a full-HD dashboard or a report page; beyond it the canvas stops being what limits how much fits, and type sized for the canvas is illegible once the export is displayed smaller. It applies per side, so a tall portrait canvas for a long horizontal ranking is allowed. |
| theme | string | No | Visual language: basic, modern or squillify (optional; default squillify). An unregistered value is rejected with the registered names listed — we never substitute silently. |
| surface | string | No | Surface the theme renders on: dark or light (optional; default light). |
| density | string | No | How tightly the chrome is packed: compact, standard or spacious (optional; default standard). Density scales spacing only — it never lowers a legibility minimum. |
| visualProfile | string | No | What the SVG promises about the host font: portable or certified (optional; default portable). The default is layout-deterministic and tolerates substitution; certified refuses text the certified metrics cannot fully measure. |
| title | string | No | Heading drawn above the plot (optional; at most 1024 code points). Omit it and the chart renders untitled — we never invent a title from your data. One code point past that ceiling is refused as TOOL_INPUT_INVALID. |
| subtitle | string | No | Second line under the title, useful for the period covered or the unit (optional; at most 1024 code points). Omit it and no second line is drawn. |
| caption | string | No | Optional bounded editorial caption; omit it and no caption is emitted. |
| source | string | No | Optional bounded source attribution; omit it and no source line is emitted. |
| period | string | No | Optional bounded reporting-period label; omit it and no period line is emitted. |
| locale | string | No | Optional visible-number locale; omit it for the default locale and exact output remains unchanged. |
| numberFormat | object | No | Optional closed object with visible decimal and unit formatting properties; omit it for exact default display. |
| style | object | No | Optional bounded paint and typography styling; omit it to use the selected theme defaults. text.size is expressed in normalized design pixels at an 800-pixel reference width, so equivalent roles match at equal CSS width. It never changes data or accessibility. 'contract' selects the published semantics. 'tokens' sets design tokens; 'rules' applies typed properties to a role and optional series/category facets; 'palette' accepts a digest-checked palette; 'css' is a bounded post-layout stylesheet. The styleCapabilities section publishes this family's accepted roles, properties and copyable examples. For Bar, target one mark with role 'bar' plus seriesIndex and categoryIndex; categoryIndex alone targets that category across series. A minimal rule is {"target":{"role":"bar"},"set":{"paint.fill":"#2563eb"}}. Unsupported typed rules are refused; unsupported CSS is dropped and reported. Do not discover the grammar through trial calls: style validation failures are currently billable. Before calling, read this contract's styleSurface and styleCapabilities sections for the complete rule shape, accepted roles and properties, limits, and copyable examples. |
| behavior | object | No | Optional object for enter_flow, update_flow, focus_node, focus_link, and trace_flow host behavior; omit for static SVG. svg is a complete static, script-free chart. Mark highlight, the dotted guide, and the value panel are not in that SVG, and entry motion, focus, and updates stay inactive there. Pass the entire output.behavior.artifact unchanged to SquillifyChartRuntime.mount(container, artifact, options), then call the returned handle's replay() once to play the entry. A later value change uses update(nextArtifact, {intent}). Injecting svg directly leaves hover and motion off. Interaction is on by default for every host chart: after mount, pointer and keyboard focus open the value panel and the dotted guide. Set behavior.interaction.enabled to false to turn that off. motion.preset accepts only the family's entrance. |
| accessibility | object | No | Optional object with detail and maxItems properties; omit it for the default bounded graph reading order. |
| output | object | No | Output scoping. output.idNamespace ([a-z][a-z0-9-]{0,31}) is prefixed onto every generated element id so several charts can share one host document without colliding; omit it for a standalone chart. An invalid value is rejected, never corrected. |
Output schema
| Field | Type | Required | Description |
|---|---|---|---|
| svg | string | Yes | Allowlist-checked script-free SVG. svg is a complete static, script-free chart. Mark highlight, the dotted guide, and the value panel are not in that SVG, and entry motion, focus, and updates stay inactive there. Pass the entire output.behavior.artifact unchanged to SquillifyChartRuntime.mount(container, artifact, options), then call the returned handle's replay() once to play the entry. A later value change uses update(nextArtifact, {intent}). Injecting svg directly leaves hover and motion off. Interaction is on by default for every host chart: after mount, pointer and keyboard focus open the value panel and the dotted guide. Set behavior.interaction.enabled to false to turn that off. motion.preset accepts only the family's entrance. |
| width | integer | Yes | Rendered width in pixels. |
| height | integer | Yes | Rendered height in pixels. |
| viewBox | string | Yes | SVG viewBox string (for example "0 0 900 500"). |
| nodeResults | array | Yes | Exact inbound/outbound/imbalance, structural status, layer, and order per node. |
| linkResults | array | Yes | Every distinct link with exact canonical value, endpoints, and resolved order. |
| graphResult | object | Yes | Object with resolved node count, link count, layer count, orientation, and component properties. |
| balanceResult | object | Yes | Applied policy, imbalance count, and exact conservation status. |
| renderSummary | object | Yes | Object with bounded node, link, layer, ribbon, target, and imbalance counts or flags. |
| transformationSummary | object | Yes | Object proving no links were merged, values changed, nodes dropped, cycles repaired, or balances inferred. |
| accessibility | object | Yes | Object containing bounded accessible title, graph summary, detail level, and truncation metadata. |
| behavior | object | No | Optional object containing digest-pinned host manifest, module, data index, and target metadata. svg is a complete static, script-free chart. Mark highlight, the dotted guide, and the value panel are not in that SVG, and entry motion, focus, and updates stay inactive there. Pass the entire output.behavior.artifact unchanged to SquillifyChartRuntime.mount(container, artifact, options), then call the returned handle's replay() once to play the entry. A later value change uses update(nextArtifact, {intent}). Injecting svg directly leaves hover and motion off. Interaction is on by default for every host chart: after mount, pointer and keyboard focus open the value panel and the dotted guide. Set behavior.interaction.enabled to false to turn that off. motion.preset accepts only the family's entrance. |
| styleReport | object | No | Present only when a 'style' input was supplied. Reports tokensApplied, rulesApplied, typedRulesApplied, typedAssignmentsApplied, rulesDropped, declarationsDropped and, when anything was dropped, a bounded list of reasons — so a stylesheet that was modified never looks like one that was applied whole. |
| warnings | array | No | Present when the render succeeded with a known degradation. Each entry is an object with a stable code, a message and details. Every chart can emit FONT_SUBSTITUTION_POSSIBLE under visualProfile 'portable', FONT_GLYPH_UNMEASURED when a code point falls outside the certified metrics, and CHART_STYLE_PARTIALLY_APPLIED when part of your styling was dropped — styleReport then names which part and why. Codes are stable for agent routing. |
API contract
POST /v1/executecurl -sS -X POST "$BASE/v1/execute" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"skillKey":"chart.sankey","input":{"nodes":[{"id":"ads","label":"Ads"},{"id":"site","label":"Site"}],"links":[{"id":"ads-site","source":"ads","target":"site","value":"100"}],"title":"Acquisition flow"}}'import { Squillify } from "@squillify/sdk";
const sq = new Squillify({ apiKey: process.env.SQUILLIFY_API_KEY });
const { output } = await sq.execute("chart.sankey", {"title":"Acquisition flow","nodes":[{"label":"Traffic","id":"traffic"},{"label":"Signup","id":"signup"},{"label":"Paid","id":"paid"}],"links":[{"target":"signup","value":"620","id":"traffic-signup","source":"traffic"},{"target":"paid","value":"260","id":"signup-paid","source":"signup"}],"surface":"light"});Errors
| Code | HTTP | Retryable | Description |
|---|---|---|---|
| VALIDATION_ERROR | 400 | No | The request payload failed schema validation. |
| UNAUTHORIZED | 401 | No | Missing or invalid API key. |
| SKILL_NOT_ACCESSIBLE | 403 | No | The API key is not granted access to this skill. |
| SKILL_NOT_AVAILABLE_FOR_PLAN | 403 | No | This skill is not included in your current plan. |
| PLAN_ELIGIBILITY_UNAVAILABLE | 503 | Yes | Plan eligibility could not be decided. Retry after backoff. |
| SKILL_NOT_FOUND | 404 | No | The requested skill or version does not exist. |
| TENANT_RATE_LIMITED | 429 | Yes | Request rate limit exceeded. Retry after backoff. |
| QUOTA_EXCEEDED | 429 | Yes | Plan quota exhausted. Retryable once the quota period resets. |
| EXECUTION_TIMEOUT | 504 | Yes | Execution exceeded the allowed time budget. |
| CHART_CAPACITY_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| CHART_OUTPUT_TOO_LARGE | 429 | No | A size, quota, rate, or time limit was exceeded. |
| CHART_THEME_UNKNOWN | 400 | No | The request payload failed this skill's input validation. |
| CHART_UNSAFE_OUTPUT | 500 | No | The skill encountered an unexpected execution failure. |
| CHART_OUTPUT_TOO_COMPLEX | 500 | No | The skill encountered an unexpected execution failure. |
Other limits
| Limit | Value |
|---|---|
| Recommended inline bytes | 32 KiB |
Limitations
- Cyclic graphs — a sankey must be acyclic
- Unweighted dependency routing (use chart.dag)
- Simple category bar comparisons (use chart.bar)
- Cyclic graphs — we refuse those as a data-shape error
- Very dense graphs — we refuse overcrowding
- Category-vs-value bar charts (use chart.bar)
How to call
POST /v1/execute (requires authentication)
{
"skillKey": "chart.sankey",
"input": {
"title": "Acquisition flow",
"nodes": [
{
"label": "Traffic",
"id": "traffic"
},
{
"label": "Signup",
"id": "signup"
},
{
"label": "Paid",
"id": "paid"
}
],
"links": [
{
"target": "signup",
"value": "620",
"id": "traffic-signup",
"source": "traffic"
},
{
"target": "paid",
"value": "260",
"id": "signup-paid",
"source": "signup"
}
],
"surface": "light"
}
}Examples
Acquisition funnel with split and merge
{
"title": "Acquisition flow",
"nodes": [
{
"label": "Traffic",
"id": "traffic"
},
{
"label": "Signup",
"id": "signup"
},
{
"label": "Paid",
"id": "paid"
}
],
"links": [
{
"target": "signup",
"value": "620",
"id": "traffic-signup",
"source": "traffic"
},
{
"target": "paid",
"value": "260",
"id": "signup-paid",
"source": "signup"
}
],
"surface": "light"
}{
"nodeResults": [
{
"inbound": "260",
"targetKey": "t-sankey-node-e17ac9cc9c478f1f",
"id": "paid",
"label": "Paid",
"outbound": "0",
"imbalance": "-260",
"status": "sink",
"layer": 2,
"order": 0
},
{
"inbound": "620",
"targetKey": "t-sankey-node-7534f34e641688c2",
"id": "signup",
"label": "Signup",
"outbound": "260",
"imbalance": "-360",
"status": "internal",
"layer": 1,
"order": 0
},
{
"inbound": "0",
"targetKey": "t-sankey-node-4b6da3fc3c8b6e14",
"id": "traffic",
"label": "Traffic",
"outbound": "620",
"imbalance": "620",
"status": "source",
"layer": 0,
"order": 0
}
],
"balanceResult": {
"conserved": false,
"policy": "report",
"imbalancedNodeCount": 3
},
"accessibility": {
"title": "Acquisition flow",
"detail": "full",
"description": "Flow (sankey) chart. 3 nodes and 2 links across 3 layers. Flows: Signup to Paid 260, Traffic to Signup 620. All values are listed."
},
"graphResult": {
"layerCount": 3,
"linkMinimumWidth": 1,
"nodeCount": 3,
"linkCount": 2,
"nodeGap": 6,
"orientation": "leftToRight",
"nodeWidth": 16
},
"svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"900\" height=\"500\" viewBox=\"0 0 900 500\" role=\"img\" class=\"sq-chart sq-chart--sankey sq-family--flow sq-theme--squillify sq-surface--light sq-density--standard\" data-sq-style-contract=\"CHART_STYLE@1\" data-sq-chart=\"sankey\" data-sq-family=\"flow\" data-sq-theme=\"squillify\" data-sq-theme-version=\"1\" data-sq-surface=\"light\" data-sq-density=\"standard\" data-sq-a11y-detail=\"full\" aria-labelledby=\"sq-cd9b4bbd1f12-title-0 sq-cd9b4bbd1f12-desc-1\"><title id=\"sq-cd9b4bbd1f12-title-0\">Acquisition flow</title><desc id=\"sq-cd9b4bbd1f12-desc-1\">Flow (sankey) chart. 3 nodes and 2 links across 3 layers. Flows: Signup to Paid 260, Traffic to Signup 620. All values are listed.</desc><style>.sq-chart{--sq-chart-axis:#c9d1df;--sq-chart-font-family:'Squillify Mono', 'IBM Plex Mono', monospace;--sq-chart-grid:#d8dee9;--sq-chart-surface:#ffffff;--sq-chart-text-muted:#667085;--sq-chart-text-primary:#101828;--sq-series-1:#2563eb;--sq-series-2:#6d5ce7;--sq-series-3:#168a5b;--sq-series-4:#c45412;--sq-series-5:#d92d20;--sq-series-6:#667085;font-family:var(--sq-chart-font-family);font-weight:500;}.sq-chart .sq-background{fill:var(--sq-chart-surface);}.sq-chart .sq-title{fill:var(--sq-chart-text-primary);}.sq-chart .sq-node-label{fill:var(--sq-chart-text-primary);}.sq-chart .sq-flow-node[data-sq-palette-index=\"0\"]{fill:var(--sq-series-1);}.sq-chart .sq-flow-link[data-sq-palette-index=\"1\"]{fill:var(--sq-series-2);}.sq-chart .sq-flow-node[data-sq-palette-index=\"1\"]{fill:var(--sq-series-2);}.sq-chart .sq-flow-link[data-sq-palette-index=\"2\"]{fill:var(--sq-series-3);}.sq-chart .sq-flow-node[data-sq-palette-index=\"2\"]{fill:var(--sq-series-3);}</style><rect x=\"0\" y=\"0\" width=\"900\" height=\"500\" fill=\"#ffffff\" class=\"sq-background\" data-sq-role=\"background\"/><g class=\"sq-plot\" data-sq-role=\"plot\"><path d=\"M 467 34 C 653 34 653 165.806 839 165.806 L 839 356.194 C 653 356.194 653 224.387 467 224.387 Z\" fill=\"#6d5ce7\" fill-opacity=\"0.45\" class=\"sq-mark sq-flow-link\" data-sq-role=\"sankey-link\" data-sq-target-key=\"t-sankey-link-d8cac04bd13c2579\" data-sq-link-index=\"0\" data-sq-palette-index=\"1\"/><path d=\"M 79 34 C 265 34 265 34 451 34 L 451 488 C 265 488 265 488 79 488 Z\" fill=\"#168a5b\" fill-opacity=\"0.45\" class=\"sq-mark sq-flow-link\" data-sq-role=\"sankey-link\" data-sq-target-key=\"t-sankey-link-8c0bbaa2932cdb27\" data-sq-link-index=\"1\" data-sq-palette-index=\"2\"/><rect x=\"839\" y=\"165.806\" width=\"16\" height=\"190.387\" rx=\"1\" fill=\"#2563eb\" class=\"sq-mark sq-flow-node\" data-sq-role=\"sankey-node\" data-sq-target-key=\"t-sankey-node-e17ac9cc9c478f1f\" data-sq-node-index=\"0\" data-sq-palette-index=\"0\"/><rect x=\"451\" y=\"34\" width=\"16\" height=\"454\" rx=\"1\" fill=\"#6d5ce7\" class=\"sq-mark sq-flow-node\" data-sq-role=\"sankey-node\" data-sq-target-key=\"t-sankey-node-7534f34e641688c2\" data-sq-node-index=\"1\" data-sq-palette-index=\"1\"/><rect x=\"63\" y=\"34\" width=\"16\" height=\"454\" rx=\"1\" fill=\"#168a5b\" class=\"sq-mark sq-flow-node\" data-sq-role=\"sankey-node\" data-sq-target-key=\"t-sankey-node-4b6da3fc3c8b6e14\" data-sq-node-index=\"2\" data-sq-palette-index=\"2\"/><text x=\"859\" y=\"264.5\" font-size=\"10\" text-anchor=\"start\" textLength=\"24\" lengthAdjust=\"spacing\" fill=\"#101828\" class=\"sq-label sq-node-label\" data-sq-role=\"node-label\" data-sq-node-index=\"0\">Paid</text><text x=\"471\" y=\"264.5\" font-size=\"10\" text-anchor=\"start\" textLength=\"36\" lengthAdjust=\"spacing\" fill=\"#101828\" class=\"sq-label sq-node-label\" data-sq-role=\"node-label\" data-sq-node-index=\"1\">Signup</text><text x=\"59\" y=\"264.5\" font-size=\"10\" text-anchor=\"end\" textLength=\"42\" lengthAdjust=\"spacing\" fill=\"#101828\" class=\"sq-label sq-node-label\" data-sq-role=\"node-label\" data-sq-node-index=\"2\">Traffic</text><text x=\"450\" y=\"28\" font-size=\"16\" text-anchor=\"middle\" fill=\"#101828\" class=\"sq-title\" data-sq-role=\"title\">Acquisition flow</text></g></svg>",
"renderSummary": {
"skill": "chart.sankey",
"nodeCount": 3,
"linkCount": 2,
"layerCount": 3
},
"viewBox": "0 0 900 500",
"width": 900,
"transformationSummary": {
"applied": false,
"operations": [],
"affectedCategoryCount": 0,
"affectedPointCount": 0,
"affectedLabelCount": 0
},
"linkResults": [
{
"target": "paid",
"value": "260",
"floorApplied": false,
"resolvedOrder": 0,
"id": "signup-paid",
"source": "signup",
"projectedWidth": 190.38709677419357,
"targetKey": "t-sankey-link-d8cac04bd13c2579"
},
{
"target": "signup",
"value": "620",
"floorApplied": false,
"resolvedOrder": 1,
"id": "traffic-signup",
"source": "traffic",
"projectedWidth": 454,
"targetKey": "t-sankey-link-8c0bbaa2932cdb27"
}
],
"height": 500
}Two-stage energy transfer
{
"nodes": [
{
"label": "Source",
"id": "source"
},
{
"label": "Load",
"id": "load"
}
],
"links": [
{
"target": "load",
"value": "42.5",
"id": "transfer",
"source": "source"
}
]
}{
"nodeResults": [
{
"inbound": "42.5",
"targetKey": "t-sankey-node-e17ac9cc9c478f1f",
"id": "load",
"label": "Load",
"outbound": "0",
"imbalance": "-42.5",
"status": "sink",
"layer": 1,
"order": 0
},
{
"inbound": "0",
"targetKey": "t-sankey-node-7534f34e641688c2",
"id": "source",
"label": "Source",
"outbound": "42.5",
"imbalance": "42.5",
"status": "source",
"layer": 0,
"order": 0
}
],
"balanceResult": {
"conserved": false,
"policy": "report",
"imbalancedNodeCount": 2
},
"accessibility": {
"title": "Flow chart with 2 nodes",
"detail": "full",
"description": "Flow (sankey) chart. 2 nodes and 1 link across 2 layers. Flows: Source to Load 42.5. All values are listed."
},
"graphResult": {
"layerCount": 2,
"linkMinimumWidth": 1,
"nodeCount": 2,
"linkCount": 1,
"nodeGap": 6,
"orientation": "leftToRight",
"nodeWidth": 16
},
"svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"900\" height=\"500\" viewBox=\"0 0 900 500\" role=\"img\" class=\"sq-chart sq-chart--sankey sq-family--flow sq-theme--squillify sq-surface--light sq-density--standard\" data-sq-style-contract=\"CHART_STYLE@1\" data-sq-chart=\"sankey\" data-sq-family=\"flow\" data-sq-theme=\"squillify\" data-sq-theme-version=\"1\" data-sq-surface=\"light\" data-sq-density=\"standard\" data-sq-a11y-detail=\"full\" aria-labelledby=\"sq-32289f7d1845-title-0 sq-32289f7d1845-desc-1\"><title id=\"sq-32289f7d1845-title-0\">Flow chart with 2 nodes</title><desc id=\"sq-32289f7d1845-desc-1\">Flow (sankey) chart. 2 nodes and 1 link across 2 layers. Flows: Source to Load 42.5. All values are listed.</desc><style>.sq-chart{--sq-chart-axis:#c9d1df;--sq-chart-font-family:'Squillify Mono', 'IBM Plex Mono', monospace;--sq-chart-grid:#d8dee9;--sq-chart-surface:#ffffff;--sq-chart-text-muted:#667085;--sq-chart-text-primary:#101828;--sq-series-1:#2563eb;--sq-series-2:#6d5ce7;--sq-series-3:#168a5b;--sq-series-4:#c45412;--sq-series-5:#d92d20;--sq-series-6:#667085;font-family:var(--sq-chart-font-family);font-weight:500;}.sq-chart .sq-background{fill:var(--sq-chart-surface);}.sq-chart .sq-node-label{fill:var(--sq-chart-text-primary);}.sq-chart .sq-flow-node[data-sq-palette-index=\"0\"]{fill:var(--sq-series-1);}.sq-chart .sq-flow-link[data-sq-palette-index=\"1\"]{fill:var(--sq-series-2);}.sq-chart .sq-flow-node[data-sq-palette-index=\"1\"]{fill:var(--sq-series-2);}</style><rect x=\"0\" y=\"0\" width=\"900\" height=\"500\" fill=\"#ffffff\" class=\"sq-background\" data-sq-role=\"background\"/><g class=\"sq-plot\" data-sq-role=\"plot\"><path d=\"M 73 12 C 456 12 456 12 839 12 L 839 488 C 456 488 456 488 73 488 Z\" fill=\"#6d5ce7\" fill-opacity=\"0.45\" class=\"sq-mark sq-flow-link\" data-sq-role=\"sankey-link\" data-sq-target-key=\"t-sankey-link-d8cac04bd13c2579\" data-sq-link-index=\"0\" data-sq-palette-index=\"1\"/><rect x=\"839\" y=\"12\" width=\"16\" height=\"476\" rx=\"1\" fill=\"#2563eb\" class=\"sq-mark sq-flow-node\" data-sq-role=\"sankey-node\" data-sq-target-key=\"t-sankey-node-e17ac9cc9c478f1f\" data-sq-node-index=\"0\" data-sq-palette-index=\"0\"/><rect x=\"57\" y=\"12\" width=\"16\" height=\"476\" rx=\"1\" fill=\"#6d5ce7\" class=\"sq-mark sq-flow-node\" data-sq-role=\"sankey-node\" data-sq-target-key=\"t-sankey-node-7534f34e641688c2\" data-sq-node-index=\"1\" data-sq-palette-index=\"1\"/><text x=\"859\" y=\"253.5\" font-size=\"10\" text-anchor=\"start\" textLength=\"24\" lengthAdjust=\"spacing\" fill=\"#101828\" class=\"sq-label sq-node-label\" data-sq-role=\"node-label\" data-sq-node-index=\"0\">Load</text><text x=\"53\" y=\"253.5\" font-size=\"10\" text-anchor=\"end\" textLength=\"36\" lengthAdjust=\"spacing\" fill=\"#101828\" class=\"sq-label sq-node-label\" data-sq-role=\"node-label\" data-sq-node-index=\"1\">Source</text></g></svg>",
"renderSummary": {
"skill": "chart.sankey",
"nodeCount": 2,
"linkCount": 1,
"layerCount": 2
},
"viewBox": "0 0 900 500",
"width": 900,
"transformationSummary": {
"applied": false,
"operations": [],
"affectedCategoryCount": 0,
"affectedPointCount": 0,
"affectedLabelCount": 0
},
"linkResults": [
{
"target": "load",
"value": "42.5",
"floorApplied": false,
"resolvedOrder": 0,
"id": "transfer",
"source": "source",
"projectedWidth": 475.99999999999994,
"targetKey": "t-sankey-link-d8cac04bd13c2579"
}
],
"height": 500
}Recommended use cases
- Visualise how a quantity splits and merges across stages
- Show weighted flows in a funnel, budget, or energy diagram
- Embed a static flow diagram in a report
Avoid for
- Cyclic graphs — we refuse those as a data-shape error
- Very dense graphs — we refuse overcrowding
- Category-vs-value bar charts (use chart.bar)
Related skills
Composable with
Safety & privacy
- Runs without calling any external service.
- Your payloads are never stored permanently.
- Telemetry captures metadata only — never your payload contents.
- We never train models on your payloads.
- No human reviews your payloads.