Directed graph
call direction
Directed edges with arrowheads. For when direction is the question, not just adjacency.
- Format.d3
- Length117 lines
- Includesnone
The source
117 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.
// Directed graph with arrowheads — when direction is the point.
// Two details make arrowheads actually work: a `<marker>` with
// `orient="auto"`, and shortening each line by the target's radius so the
// head lands on the circle's edge rather than under it.
const graph = {
nodes: [
{ id: 'web', group: 'edge' },
{ id: 'mobile-bff', group: 'edge' },
{ id: 'gateway', group: 'edge' },
{ id: 'orders', group: 'domain' },
{ id: 'catalogue', group: 'domain' },
{ id: 'payments', group: 'domain' },
{ id: 'identity', group: 'domain' },
{ id: 'search', group: 'domain' },
{ id: 'pricing', group: 'domain' },
{ id: 'postgres', group: 'platform' },
{ id: 'redis', group: 'platform' },
{ id: 'kafka', group: 'platform' },
{ id: 's3', group: 'platform' },
],
links: [
{ source: 'web', target: 'gateway' },
{ source: 'mobile-bff', target: 'gateway' },
{ source: 'gateway', target: 'orders' },
{ source: 'gateway', target: 'catalogue' },
{ source: 'gateway', target: 'identity' },
{ source: 'gateway', target: 'search' },
{ source: 'orders', target: 'payments' },
{ source: 'orders', target: 'pricing' },
{ source: 'orders', target: 'postgres' },
{ source: 'orders', target: 'kafka' },
{ source: 'catalogue', target: 'postgres' },
{ source: 'catalogue', target: 'redis' },
{ source: 'catalogue', target: 's3' },
{ source: 'payments', target: 'postgres' },
{ source: 'identity', target: 'redis' },
{ source: 'search', target: 'redis' },
{ source: 'search', target: 'kafka' },
{ source: 'pricing', target: 'postgres' },
],
};
const nodes = graph.nodes.map(d => ({ ...d }));
const links = graph.links.map(d => ({ ...d }));
const RADIUS = 8;
const groups = Array.from(new Set(nodes.map(d => d.group)));
const color = d3.scaleOrdinal(groups, theme.palette);
const simulation = d3.forceSimulation(nodes)
.force('link', d3.forceLink(links).id(d => d.id).distance(90))
.force('charge', d3.forceManyBody().strength(-380))
.force('centre', d3.forceCenter(width / 2, height / 2))
.force('collide', d3.forceCollide(28));
// Run the simulation to completion up front, then draw once. A live
// simulation would be serialised at an arbitrary tick and never look the
// same twice; seeding \`randomSource\` pins the initial jitter too.
simulation.randomSource(d3.randomLcg(42)).stop();
for (let i = 0; i < 400; i++) simulation.tick();
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);
svg.append('defs').append('marker')
.attr('id', 'arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 9)
.attr('markerWidth', 6)
.attr('markerHeight', 6)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', theme.muted);
svg.append('g')
.attr('stroke', theme.muted)
.attr('stroke-opacity', 0.6)
.attr('stroke-width', 1.4)
.attr('marker-end', 'url(#arrow)')
.selectAll('line')
.data(links)
.join('line')
.each(function (d) {
const dx = d.target.x - d.source.x;
const dy = d.target.y - d.source.y;
const len = Math.hypot(dx, dy) || 1;
const back = RADIUS + 4;
d3.select(this)
.attr('x1', d.source.x)
.attr('y1', d.source.y)
.attr('x2', d.target.x - (dx / len) * back)
.attr('y2', d.target.y - (dy / len) * back);
});
const node = svg.append('g')
.selectAll('g')
.data(nodes)
.join('g')
.attr('transform', d => `translate(${d.x},${d.y})`);
node.append('circle')
.attr('r', RADIUS)
.attr('fill', d => color(d.group))
.attr('stroke', theme.background)
.attr('stroke-width', 1.5);
node.append('text')
.attr('x', RADIUS + 4)
.attr('dy', '0.35em')
.attr('fill', theme.foreground)
.text(d => d.id);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.
- 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.
- 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.