Bullet chart
target vs actual
Target against actual, in one line. The chart a gauge wishes it were.
- 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.
// Bullet charts — Stephen Few's replacement for the dashboard gauge.
// Each row is: qualitative bands (poor/ok/good), a measure bar, and a target
// tick. Far more information per pixel than a dial, and directly comparable
// down the column.
const rows = [
{ label: 'Availability', sub: '% uptime', ranges: [99.0, 99.7, 100], measure: 99.82, target: 99.9 },
{ label: 'p95 latency', sub: 'ms (lower=better)', ranges: [600, 350, 150], measure: 280, target: 200 },
{ label: 'Change failure', sub: '% of deploys', ranges: [30, 15, 0], measure: 11, target: 8 },
{ label: 'Lead time', sub: 'hours', ranges: [72, 36, 4], measure: 26, target: 12 },
{ label: 'Test coverage', sub: '%', ranges: [50, 70, 95], measure: 78, target: 85 },
];
const margin = { top: 30, right: 30, bottom: 20, left: 150 };
const rowHeight = 52;
const barHeight = 20;
const boxHeight = margin.top + rows.length * rowHeight + margin.bottom;
const bandFill = theme.mode === 'dark'
? ['#3a3a3a', '#4c4c4c', '#5e5e5e']
: ['#e6e6e6', '#efefef', '#f7f7f7'];
const svg = d3.select(container).append('svg')
.attr('viewBox', [0, 0, width, boxHeight])
.attr('width', width)
.attr('height', boxHeight)
.attr('font-family', 'system-ui, sans-serif')
.attr('font-size', 11);
const g = svg.selectAll('g.row')
.data(rows)
.join('g')
.attr('class', 'row')
.attr('transform', (_, i) => `translate(0,${margin.top + i * rowHeight})`);
g.each(function (d) {
const row = d3.select(this);
// Ranges may descend (lower is better), so build the scale from the
// extent rather than assuming ranges[2] is the maximum.
const lo = Math.min(...d.ranges, d.measure, d.target);
const hi = Math.max(...d.ranges, d.measure, d.target);
const x = d3.scaleLinear().domain([lo, hi]).range([margin.left, width - margin.right]);
const sorted = [...d.ranges].sort(d3.ascending);
row.selectAll('rect.band')
.data([sorted[2], sorted[1], sorted[0]])
.join('rect')
.attr('class', 'band')
.attr('x', margin.left)
.attr('width', v => Math.max(0, x(v) - margin.left))
.attr('height', barHeight)
.attr('rx', 2)
.attr('fill', (_, i) => bandFill[i]);
row.append('rect')
.attr('x', margin.left)
.attr('y', barHeight / 2 - 4)
.attr('width', Math.max(0, x(d.measure) - margin.left))
.attr('height', 8)
.attr('rx', 1)
.attr('fill', theme.accent);
row.append('line')
.attr('x1', x(d.target)).attr('x2', x(d.target))
.attr('y1', -2).attr('y2', barHeight + 2)
.attr('stroke', theme.mode === 'dark' ? '#ffffff' : '#1a1a1a')
.attr('stroke-width', 2.5);
row.append('text')
.attr('x', margin.left - 12)
.attr('y', 8)
.attr('text-anchor', 'end')
.attr('fill', theme.foreground)
.attr('font-weight', 600)
.text(d.label);
row.append('text')
.attr('x', margin.left - 12)
.attr('y', 21)
.attr('text-anchor', 'end')
.attr('fill', theme.muted)
.attr('font-size', 10)
.text(d.sub);
row.append('text')
.attr('x', margin.left)
.attr('y', barHeight + 15)
.attr('fill', theme.muted)
.attr('font-size', 10)
.text(`actual ${d.measure} · target ${d.target}`);
});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.
- 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.
- 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.