Sankey
request flow
Flow with volume. The best chart here for "where does it all go".
- Format.d3
- Length82 lines
- Includesnone
The source
82 lines of D3 JavaScript, and uses only the bundled D3 build. Copy it, or open the template inside Gnomon and render it as it is.
// Sankey — where traffic actually goes.
// Node ids are strings, so `nodeId` must be set; d3-sankey defaults to
// indices and would silently mis-wire the links.
const data = {
nodes: [
{ id: 'Browser' }, { id: 'Mobile app' }, { id: 'Partner API' },
{ id: 'CDN' }, { id: 'API gateway' },
{ id: 'Orders' }, { id: 'Catalogue' }, { id: 'Payments' },
{ id: 'Postgres' }, { id: 'Redis' }, { id: 'Stripe' },
],
links: [
{ source: 'Browser', target: 'CDN', value: 620 },
{ source: 'Mobile app', target: 'API gateway', value: 410 },
{ source: 'Partner API', target: 'API gateway', value: 90 },
{ source: 'CDN', target: 'API gateway', value: 380 },
{ source: 'API gateway', target: 'Orders', value: 340 },
{ source: 'API gateway', target: 'Catalogue', value: 420 },
{ source: 'API gateway', target: 'Payments', value: 120 },
{ source: 'Orders', target: 'Postgres', value: 300 },
{ source: 'Orders', target: 'Redis', value: 40 },
{ source: 'Catalogue', target: 'Redis', value: 350 },
{ source: 'Catalogue', target: 'Postgres', value: 70 },
{ source: 'Payments', target: 'Stripe', value: 120 },
],
};
const margin = { top: 20, right: 150, bottom: 20, left: 90 };
const color = d3.scaleOrdinal(theme.palette);
const svg = d3.select(container).append('svg')
.attr('viewBox', [0, 0, width, height])
.attr('width', width)
.attr('height', height)
.attr('font-family', 'system-ui, sans-serif')
.attr('font-size', 11);
const layout = d3.sankey()
.nodeId(d => d.id)
.nodeWidth(14)
.nodePadding(14)
.nodeAlign(d3.sankeyJustify)
.extent([[margin.left, margin.top], [width - margin.right, height - margin.bottom]]);
// Copy the arrays: d3-sankey mutates what it is handed, and re-running against
// already-laid-out objects produces nonsense.
const { nodes, links } = layout({
nodes: data.nodes.map(d => ({ ...d })),
links: data.links.map(d => ({ ...d })),
});
svg.append('g')
.attr('fill', 'none')
.selectAll('path')
.data(links)
.join('path')
.attr('d', d3.sankeyLinkHorizontal())
.attr('stroke', d => color(d.source.id))
.attr('stroke-opacity', 0.35)
.attr('stroke-width', d => Math.max(1, d.width));
svg.append('g')
.selectAll('rect')
.data(nodes)
.join('rect')
.attr('x', d => d.x0)
.attr('y', d => d.y0)
.attr('width', d => d.x1 - d.x0)
.attr('height', d => Math.max(1, d.y1 - d.y0))
.attr('fill', d => color(d.id))
.attr('rx', 2);
svg.append('g')
.attr('fill', theme.foreground)
.selectAll('text')
.data(nodes)
.join('text')
.attr('x', d => d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6)
.attr('y', d => (d.y0 + d.y1) / 2)
.attr('dy', '0.35em')
.attr('text-anchor', d => d.x0 < width / 2 ? 'start' : 'end')
.text(d => `${d.id} · ${d.value}`);Render this offline
This template ships in Gnomon and renders on your machine, with no account and nothing sent to a server. The browser editor is free and needs no install.
Others in D3 visualisations
- Chord — service interactionWho talks to whom, when the traffic is bidirectional.
- Arc diagram — dependenciesNodes on one axis, arcs above. Readable where a force graph is not, provided the ordering means something.
- Adjacency matrix — couplingAdjacency matrix. Unfashionable, and better than a force graph for dense dependencies.
- Edge bundling — module importsHierarchical edge bundling. For import graphs big enough that straight edges become a hairball.
- Sunburst — nested spendNested hierarchy, radially. Prettier than an icicle, harder to compare.
- Icicle — nested spend (linear)A sunburst unrolled flat. Harder to love, much easier to compare siblings.
- Circle packing — nested sizeCircle packing. Nested size when the nesting matters more than reading exact areas.
- Treemap — portfolio costNested size. Good for cost, storage, lines of code.
- Tidy tree — structureA tidy tree. The default for anything with one parent per node.
- Radial tree — structure (radial)The same tree bent into a circle. Fits more depth on a slide, costs you easy comparison.
- Dendrogram — clusteringClustering, with join height carrying the distance. Not merely a tree with curves.
- Indented tree — file/spec outlineA file or spec outline. The chart that looks like the thing it describes.
- Force graph — service mapService map. Use for clusters, not for reading individual edges.
- Force graph — disjoint clustersForce graph that keeps unconnected clusters apart rather than flinging them off screen.
- Force graph — radial tiersForce layout pinned to rings, so tier is a position instead of a colour legend.
- Directed graph — call directionDirected edges with arrowheads. For when direction is the question, not just adjacency.
- Force tree — blast radiusBlast radius from one node: what breaks if this goes.
- Calendar heatmap — daily activityDaily activity over a year: deploys, incidents, commits.
- Streamgraph — shifting mixShifting composition over time. Good for the mix, poor for reading any single value.
- Gantt — delivery roadmapDelivery roadmap, rendered from data.
- Radar — capability scoringCapability scoring across axes. Fine for one subject, misleading with four overlaid.
- Bullet chart — target vs actualTarget against actual, in one line. The chart a gauge wishes it were.
- Beeswarm — distribution by groupEvery point, grouped, without the overplotting a strip plot suffers.
- Horizon chart — many series, little spaceMany series in little space. Takes a moment to learn, then very dense.
- Slope chart — before and afterBefore and after, two points, one line each. Devastatingly clear.
- Parallel coordinates — multi-criteriaMulti-criteria comparison: for option analysis.
- Box plot — latency distributionLatency distribution. The chart that shows the tail a mean hides.
- Grouped bar — category comparisonCategory comparison. Unglamorous, and usually the right answer.
- Multi-line — metrics over timeMetrics over time. Keep it under about five series, or switch to horizon.