Parallel coordinates
multi-criteria
Multi-criteria comparison: for option analysis.
- Format.d3
- Length95 lines
- Includesnone
The source
95 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.
// Parallel coordinates — one line per option, one vertical axis per
// criterion. The tool for "we scored six vendors on five dimensions"; crossing
// lines between two axes mean those criteria trade off against each other.
const dimensions = [
{ key: 'cost', label: 'Cost (£k)', invert: true },
{ key: 'latency', label: 'p95 (ms)', invert: true },
{ key: 'coverage', label: 'Feature fit', invert: false },
{ key: 'maturity', label: 'Maturity', invert: false },
{ key: 'lockIn', label: 'Lock-in risk', invert: true },
];
const options = [
{ name: 'Build in-house', cost: 820, latency: 180, coverage: 9, maturity: 4, lockIn: 1 },
{ name: 'Vendor A', cost: 340, latency: 120, coverage: 7, maturity: 9, lockIn: 8 },
{ name: 'Vendor B', cost: 260, latency: 260, coverage: 6, maturity: 7, lockIn: 6 },
{ name: 'Vendor C', cost: 610, latency: 90, coverage: 8, maturity: 8, lockIn: 7 },
{ name: 'Open source', cost: 150, latency: 240, coverage: 5, maturity: 6, lockIn: 2 },
{ name: 'Hybrid', cost: 480, latency: 140, coverage: 8, maturity: 6, lockIn: 4 },
];
const margin = { top: 54, right: 150, bottom: 40, left: 60 };
const x = d3.scalePoint()
.domain(dimensions.map(d => d.key))
.range([margin.left, width - margin.right]);
// "Invert" flips the axis so that up is always better — without it, a reader
// has to remember which axes are good-high and which are good-low.
const y = new Map(dimensions.map(dim => {
const extent = d3.extent(options, o => o[dim.key]);
const range = dim.invert
? [margin.top, height - margin.bottom]
: [height - margin.bottom, margin.top];
return [dim.key, d3.scaleLinear().domain(extent).nice().range(range)];
}));
const color = d3.scaleOrdinal(options.map(o => o.name), theme.palette);
const line = d3.line()
.x(([key]) => x(key))
.y(([key, value]) => y.get(key)(value))
.curve(d3.curveMonotoneX);
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('g')
.attr('fill', 'none')
.attr('stroke-width', 2)
.attr('stroke-opacity', 0.8)
.selectAll('path')
.data(options)
.join('path')
.attr('stroke', d => color(d.name))
.attr('d', d => line(dimensions.map(dim => [dim.key, d[dim.key]])));
svg.append('g')
.selectAll('g')
.data(dimensions)
.join('g')
.attr('transform', d => `translate(${x(d.key)},0)`)
.each(function (d) {
d3.select(this).call(d3.axisLeft(y.get(d.key)).ticks(5));
})
.call(g => g.selectAll('text').attr('fill', theme.muted))
.call(g => g.selectAll('line').attr('stroke', theme.muted))
.call(g => g.selectAll('.domain').attr('stroke', theme.muted))
.append('text')
.attr('y', margin.top - 22)
.attr('text-anchor', 'middle')
.attr('fill', theme.foreground)
.attr('font-weight', 600)
.text(d => d.label);
svg.append('text')
.attr('x', margin.left)
.attr('y', 22)
.attr('fill', theme.muted)
.text('every axis oriented so higher = better');
const legend = svg.append('g')
.attr('transform', `translate(${width - margin.right + 24},${margin.top})`);
legend.selectAll('g')
.data(options)
.join('g')
.attr('transform', (_, i) => `translate(0,${i * 18})`)
.call(g => g.append('rect')
.attr('width', 11).attr('height', 11).attr('rx', 2)
.attr('fill', d => color(d.name)))
.call(g => g.append('text')
.attr('x', 16).attr('y', 9)
.attr('fill', theme.foreground).text(d => d.name));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.
- 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.
- 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.