Arc diagram
dependencies
Nodes on one axis, arcs above. Readable where a force graph is not, provided the ordering means something.
- Format.d3
- Length91 lines
- Includesnone
The source
91 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.
// Arc diagram — a dependency list you can actually read.
// Nodes sit on one axis; each dependency is an arc. Grouping by `layer`
// and sorting by it makes cross-layer edges (the interesting ones) obvious.
const nodes = [
{ id: 'web', layer: 'edge' },
{ id: 'mobile-bff', layer: 'edge' },
{ id: 'gateway', layer: 'edge' },
{ id: 'orders', layer: 'domain' },
{ id: 'catalogue', layer: 'domain' },
{ id: 'payments', layer: 'domain' },
{ id: 'identity', layer: 'domain' },
{ id: 'postgres', layer: 'platform' },
{ id: 'redis', layer: 'platform' },
{ id: 'kafka', layer: 'platform' },
];
const links = [
['web', 'gateway'], ['mobile-bff', 'gateway'],
['gateway', 'orders'], ['gateway', 'catalogue'], ['gateway', 'identity'],
['orders', 'payments'], ['orders', 'postgres'], ['orders', 'kafka'],
['catalogue', 'redis'], ['catalogue', 'postgres'],
['payments', 'postgres'], ['identity', 'redis'],
];
const layers = Array.from(new Set(nodes.map(d => d.layer)));
const color = d3.scaleOrdinal(layers, theme.palette);
const order = new Map(nodes.map((d, i) => [d.id, i]));
const margin = { top: 40, right: 24, bottom: 190, left: 24 };
const x = d3.scalePoint()
.domain(nodes.map(d => d.id))
.range([margin.left, width - margin.right])
.padding(0.5);
const baseline = height - margin.bottom;
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);
// Arc above the axis when the edge points forward, below when it points
// back — so a cycle is visible rather than hidden under a coincident path.
svg.append('g')
.attr('fill', 'none')
.attr('stroke-opacity', 0.5)
.attr('stroke-width', 1.5)
.selectAll('path')
.data(links)
.join('path')
.attr('stroke', ([s]) => color(nodes.find(n => n.id === s).layer))
.attr('d', ([s, t]) => {
const x1 = x(s);
const x2 = x(t);
const r = Math.abs(x2 - x1) / 2;
const sweep = order.get(t) > order.get(s) ? 1 : 0;
return `M${x1},${baseline} A${r},${r} 0 0 ${sweep} ${x2},${baseline}`;
});
const node = svg.append('g')
.selectAll('g')
.data(nodes)
.join('g')
.attr('transform', d => `translate(${x(d.id)},${baseline})`);
node.append('circle')
.attr('r', 5)
.attr('fill', d => color(d.layer));
node.append('text')
.attr('transform', 'rotate(45)')
.attr('x', 10)
.attr('dy', '0.35em')
.attr('fill', theme.foreground)
.text(d => d.id);
const legend = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top - 20})`);
legend.selectAll('g')
.data(layers)
.join('g')
.attr('transform', (_, i) => `translate(${i * 110},0)`)
.call(g => g.append('rect')
.attr('width', 10).attr('height', 10).attr('rx', 2)
.attr('fill', d => color(d)))
.call(g => g.append('text')
.attr('x', 15).attr('y', 9)
.attr('fill', theme.muted)
.text(d => d));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
- Sankey — request flowFlow with volume. The best chart here for "where does it all go".
- Chord — service interactionWho talks to whom, when the traffic is bidirectional.
- 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.