From d3-consistency
Write, review, refactor, or debug D3.js visualization code (selections, scales, axes, data joins, transitions, d3.csv/json loading, SVG charts) using one canonical, modern v7 idiom set. Use this skill whenever code builds or fixes a D3 chart, binds data to DOM elements, migrates old D3 examples, or when the user hits "d3.event is undefined", "d3.scale.linear is not a function", "d3.nest is not a function", enter/exit elements duplicating on update, or callbacks that never fire on data loads. Trigger it even when the user just says "make a bar chart in JavaScript/SVG" or pastes an old bl.ocks example — without saying the word "D3."
How this skill is triggered — by the user, by Claude, or both
Slash command
/d3-consistency:d3-consistencyThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
D3 is the most era-mixed library in training data: a decade of tutorials spans v3
D3 is the most era-mixed library in training data: a decade of tutorials spans v3
(d3.scale.linear(), callback loaders), v4/v5 (namespace flattened, promises arrive),
and v6/v7 (d3.event removed, selection.join, d3.group). Generated code routinely
welds incompatible eras together, producing undefined is not a function — or worse,
update patterns that silently duplicate elements. This skill pins the v7 idiom set.
| Always | Never | Why |
|---|---|---|
d3.scaleLinear(), d3.scaleBand(), d3.axisBottom(x) | d3.scale.linear(), d3.svg.axis() | The nested namespaces are v3; flattened names since v4. |
.on("click", (event, d) => ...) | d3.event, .on("click", function(d) {...}) with d first | d3.event was removed in v6; the event is now the first handler argument, datum second. |
selection.join("rect") (or join(enter => ..., update => ..., exit => ...)) | hand-rolled .enter().append() + .exit().remove() + merge bookkeeping | join handles enter/update/exit correctly in one place; manual patterns routinely forget exit or merge, duplicating elements on re-render. |
const data = await d3.csv(url, d3.autoType) | d3.csv(url, (err, data) => ...) callbacks | Loaders return promises since v5; the callback form silently never fires. |
d3.group(data, d => d.key) / d3.rollup(data, v => v.length, d => d.key) | d3.nest().key(...) | d3.nest was removed; group/rollup return real Maps. |
key function on data joins: .data(items, d => d.id) | index-based joins for dynamic data | Without keys, updates re-bind by position — bars swap identities silently. |
scales: domain from data (d3.extent, [0, d3.max(...)]), range from layout | magic-number pixel math in attribute callbacks | The scale is the data-to-pixel contract; inline math drifts out of sync. |
margin convention: one translated <g>, inner width/height | positioning axes with ad-hoc offsets | The convention keeps axes, scales, and clipping consistent. |
d3.schemeTableau10 / d3.interpolateViridis via scaleOrdinal/scaleSequential | hard-coded color arrays | Built-in schemes handle perceptual issues and category counts. |
arrow functions normally; function only when this (the node) is needed | mixing them randomly | In D3, this in a function callback is the DOM node — a feature; arrows deliberately don't get it. |
House style — a re-renderable bar chart core:
const margin = { top: 20, right: 20, bottom: 30, left: 40 };
const width = 640 - margin.left - margin.right;
const height = 360 - margin.top - margin.bottom;
const svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
const x = d3.scaleBand().range([0, width]).padding(0.1);
const y = d3.scaleLinear().range([height, 0]);
const xAxis = svg.append("g").attr("transform", `translate(0,${height})`);
const yAxis = svg.append("g");
function render(data) {
x.domain(data.map(d => d.name));
y.domain([0, d3.max(data, d => d.value)]).nice();
svg.selectAll("rect.bar")
.data(data, d => d.name)
.join("rect")
.attr("class", "bar")
.attr("x", d => x(d.name))
.attr("width", x.bandwidth())
.transition().duration(300)
.attr("y", d => y(d.value))
.attr("height", d => height - y(d.value));
xAxis.call(d3.axisBottom(x));
yAxis.transition().duration(300).call(d3.axisLeft(y));
}
svg.append("rect")-style code inside a render function
duplicates elements every call. Structure: static scaffolding once, data-driven
elements via join in render.y range inversion forgotten: SVG y grows downward; range([height, 0]) for the
y scale, height - y(v) for bar heights. Getting one without the other flips or
inflates bars plausibly.scaleBand with non-string domains: band scales key by value identity
(stringified); numeric categories work but mixed types silently miss → undefined x.d3.csv values are strings without a row converter — "5" + 1 = "51" charts.
Pass d3.autoType or convert explicitly."end" events that may
never fire on interrupted transitions..data() vs .datum(): data computes a join (per-element); datum binds one
value with no join semantics (used for single-path lines). Swapping them breaks
enter/exit invisibly.d3.timeParse, new Date("2024-01-05") UTC-vs-local
mismatch shifts daily data by one day depending on the viewer's timezone — parse
explicitly (d3.utcParse) and use scaleUtc for date axes unless local time is the
point.Target D3 v7 (v6 idioms are identical except minor internals). Era tells when reading
old code: d3.scale.*/d3.svg.*/d3.layout.* = v3; callback loaders = ≤v4;
d3.event = ≤v5; d3.nest = ≤v5. ESM-first: import * as d3 from "d3" (or cherry-pick
d3-scale, d3-selection modules); the UMD bundle still ships for script tags.
<g> per chart region, scales
declared with ranges up front.autoType; shape data with d3.group/rollup/array
helpers before any DOM work.extent/max, .nice() for axes)..data().join() — idempotent renders
that can be called again on update.d3.event, append-in-render, unkeyed joins
on dynamic data, string arithmetic from CSV columns, y-axis direction bugs.For the era-migration table, join/transition semantics, scale catalog, and shape
(line/area/arc) recipes, read references/d3-patterns.md.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub guidogl/d3-consistency --plugin d3-consistency