Adjacency matrix
coupling
Adjacency matrix. Unfashionable, and better than a force graph for dense dependencies.
- Format.d3
- Length87 lines
- Includesnone
The source
87 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.
// Adjacency matrix — the honest view of a dense graph.
// Past ~20 nodes a node-link diagram is a hairball; a matrix stays readable
// and makes clusters show up as blocks along the diagonal.
const names = [
'web', 'mobile-bff', 'gateway', 'orders', 'catalogue',
'payments', 'identity', 'search', 'postgres', 'redis',
];
// weight[i][j] = calls/min from names[i] to names[j].
const weights = [
[ 0, 0, 90, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 70, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 62, 84, 0, 31, 45, 0, 0],
[ 0, 0, 0, 0, 8, 40, 6, 0, 55, 12],
[ 0, 0, 0, 4, 0, 0, 0, 22, 14, 61],
[ 0, 0, 0, 3, 0, 0, 5, 0, 18, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 9, 27],
[ 0, 0, 0, 0, 11, 0, 0, 0, 7, 33],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
];
const margin = { top: 110, right: 20, bottom: 20, left: 110 };
const side = Math.min(width - margin.left - margin.right,
height - margin.top - margin.bottom);
const scale = d3.scaleBand().domain(names).range([0, side]).padding(0.06);
const maxWeight = d3.max(weights.flat());
const colour = d3.scaleSequential([0, maxWeight], d3.interpolateBlues);
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 g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`);
const cells = [];
for (let i = 0; i < names.length; i++) {
for (let j = 0; j < names.length; j++) {
cells.push({ i, j, value: weights[i][j] });
}
}
g.selectAll('rect')
.data(cells)
.join('rect')
.attr('x', d => scale(names[d.j]))
.attr('y', d => scale(names[d.i]))
.attr('width', scale.bandwidth())
.attr('height', scale.bandwidth())
.attr('rx', 2)
.attr('fill', d => d.value === 0
? (theme.mode === 'dark' ? '#2a2a2a' : '#f2f2f2')
: colour(d.value))
// The diagonal is structurally meaningless (nothing calls itself here);
// outline it so the eye uses it as a guide instead of reading it as data.
.attr('stroke', d => d.i === d.j ? theme.muted : 'none')
.attr('stroke-dasharray', '2,2');
g.append('g')
.selectAll('text')
.data(names)
.join('text')
.attr('x', -8)
.attr('y', d => scale(d) + scale.bandwidth() / 2)
.attr('dy', '0.35em')
.attr('text-anchor', 'end')
.attr('fill', theme.foreground)
.text(d => d);
g.append('g')
.selectAll('text')
.data(names)
.join('text')
.attr('transform', d => `translate(${scale(d) + scale.bandwidth() / 2},-8) rotate(-45)`)
.attr('fill', theme.foreground)
.text(d => d);
svg.append('text')
.attr('x', margin.left)
.attr('y', 24)
.attr('fill', theme.muted)
.text('rows call columns · darker = more traffic');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.
- Arc diagram — dependenciesNodes on one axis, arcs above. Readable where a force graph is not, provided the ordering means something.
- 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.