Skip to content

Sankey

Flow volumes between nodes: energy from panels to loads, requests through services, budget through categories. Nodes stack into columns (sources left, sinks right — computed from the link graph), ribbons run between them with thickness ∝ value and a color gradient from source to target. Tapping a node spotlights its flows — everything else fades — and fires nodeAction with the node's in/out totals.

Type

"sankey"

Wide: [3, 4] and up. In a flow grid it auto-sizes to a ~3:2 landscape cell.

Relevant Fields

Inherits all shared fields. Key fields:

Field Type Default Description
label string — Header label (selection readout appears beside it)
sankeyConfig SankeyConfig — Full configuration object (all optional)
tint string "#667eea" First-node color / palette seed
formatValue string — Formatter for throughput readouts
action ActionDefinition — Fallback for nodeAction

SankeyConfig

Field Type Default Description
nodeWidth number 10 Node bar width in points
nodeSpacing number 10 Minimum vertical gap between nodes in a column
showLabels bool true Node names beside the bars
showValues bool false Throughput value alongside each label
linkOpacity number 0.32 Ribbon opacity (selection brightens involved ribbons)
colors string[] built-in palette Node color cycle (per-node color wins)
nodeAction ActionDefinition — Fired on node tap; {{value}} = {"name", "in", "out"}
scrub bool false Drag sweeps the spotlight from node to node with haptic ticks (the action still fires on tap)

Sync Payload Structure

Nodes plus source→target→value links — natural JSON or an encoded string. Endpoints may be node names or indices:

{
  "nodes": [
    { "name": "Solar", "color": "#FFD93D" },
    { "name": "Battery" },
    { "name": "House" },
    { "name": "Grid" }
  ],
  "links": [
    { "source": "Solar",   "target": "Battery", "value": 3.2 },
    { "source": "Solar",   "target": "House",   "value": 1.8 },
    { "source": "Battery", "target": "House",   "value": 2.1 },
    { "source": "Solar",   "target": "Grid",    "value": 0.9 }
  ]
}

Self-links, unknown endpoints, and zero/negative values are dropped. Cycles don't hang the layout, but a mostly-acyclic flow reads far better.

Examples

Live energy flow

{
  "type": "sankey",
  "id": "energy",
  "position": [0, 0],
  "span": [3, 4],
  "label": "Energy (kW)",
  "sankeyConfig": { "showValues": true },
  "sync": [{ "method": "meshsocket", "type": "listen", "event": "broadcast", "filter": { "msg_type": "power_flow" }, "valuePath": "sankey" }]
}

Tap-to-inspect service traffic

{
  "type": "sankey",
  "id": "traffic",
  "position": [0, 0],
  "span": [3, 4],
  "label": "Requests/min",
  "sankeyConfig": {
    "nodeAction": { "method": "meshsocket", "mode": "broadcast", "event": "broadcast", "payload": { "msg_type": "inspect_service", "node": "{{value}}" } }
  },
  "defaultValue": "{\"nodes\":[{\"name\":\"Edge\"},{\"name\":\"API\"},{\"name\":\"DB\"},{\"name\":\"Cache\"}],\"links\":[{\"source\":0,\"target\":1,\"value\":120},{\"source\":1,\"target\":2,\"value\":80},{\"source\":1,\"target\":3,\"value\":40}]}"
}

Behavior

  • Columns come from the longest path from the sources; pure sinks align to the last column; each column is vertically centered
  • Node height ∝ max(inflow, outflow); ribbons attach in far-end order so they fan instead of crossing at the bar
  • Tap a node to spotlight it (its ribbons brighten, unrelated ones fade to a whisper) and fire nodeAction; tap empty space to clear
  • scrub: true lets a drag sweep the spotlight fluidly across nodes with a tick per hand-off
  • Dataset pushes animate via the control's animation profile

Notes

  • ≤ 4 columns and ≤ ~10 nodes stay readable on a phone
  • The control's value is the dataset JSON — selection never overwrites it
  • For pairwise relationships without direction/stages, Chord is the circular sibling
  • Chord — circular inter-relationships
  • Graph — force-directed node-link topology
  • Pie Chart — single-stage part-of-whole
  • Sync — how flows arrive
  • Actions — how nodeAction fires