The D3 chart isn't showing
D3 is quiet when it fails. Selecting an element that isn't there, scaling
an undefined value, calling a loader the old way — none of these throw at
the point of the mistake. You get an empty <svg>, or no <svg> at all.
Open DevTools (F12) and check two places: the Console, and the
Elements panel to see whether an <svg> was created and what's inside it.
Match the symptom
| What you see | Cause |
|---|---|
Uncaught ReferenceError: d3 is not defined |
The library never loaded |
No <svg> in Elements, no error |
d3.select() matched nothing |
Error: <path> attribute d: Expected number, "MNaN,NaN…" |
A scale returned NaN |
<svg> exists, chart flipped, squashed or clipped |
Wrong domain, range or size |
Cannot read properties of undefined (reading 'pageX') |
d3.event — removed in v6 |
d3.nest is not a function / d3.mouse is not a function |
v5 code on v6+ |
Callback passed to d3.csv never runs as expected |
v4 callback style on v5+ |
Access to fetch at 'file:///…' … blocked by CORS policy |
Loading data from a local file |
| Line chart is a solid black shape | The path has a fill |
The library didn't load
d3 only exists once its <script> has run. Put it above your code and
pin the major version — an unversioned CDN URL serves whatever is latest:
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
If the Network tab shows the request failing, you are offline, behind a
proxy that blocks the CDN, or inside a viewer whose Content-Security-Policy
refuses it. A local d3.min.js works on your machine and 404s once only
the .html file is sent — the same trap as lost CSS.
With a bundler, import * as d3 from "d3" is the full namespace;
import d3 from "d3" gives undefined because D3 has no default export.
The selection was empty
const svg = d3.select("#chart").append("svg"); // nothing, if #chart doesn't exist yet
D3 doesn't throw on an empty selection — every chained call is a no-op.
The script ran in <head> or above the target div, or the id is wrong.
Move the script below the element or add defer. While debugging,
console.log(d3.select("#chart").empty()) settles it in one line.
NaN in the path
attribute d: Expected number, "MNaN,NaN…" (or attribute cx: Expected length, "NaN") means a scale got something it couldn't map:
CSV values are strings.
d3.csvreturns every field as text, sod3.max(data, d => d.value)compares lexically ("9" > "10") and arithmetic yields NaN on blanks. Convert them:const data = await d3.csv("sales.csv", d3.autoType); // or d => ({ ...d, value: +d.value })Dates weren't parsed. A time scale needs
Dateobjects:d3.timeParse("%Y-%m-%d")(d.date).The accessor names a field that doesn't exist.
d.Valuevsd.value, or a header with a trailing space or a byte order mark in front of the first name.The domain was never set, or was set before the data arrived.
The chart is there but wrong
- Upside down. SVG y grows downward. A y scale needs
.range([height, 0]), not[0, height]. - Tiny, 300×150. That is the default size of an
<svg>with nowidth/heightorviewBox. Set aviewBoxand let CSS size it:svg.attr("viewBox", [0, 0, width, height])withwidth: 100%— which also fixes charts that are tiny on a phone. - Axes or labels cut off. Nothing was left for margins. Translate the
plot group by
margin.left, margin.topand subtract the margins from the ranges. - A black blob instead of a line. Paths are filled by default — set
.attr("fill", "none").attr("stroke", "currentColor").
Code from an older D3
Most broken D3 is a tutorial from one major version run on another.
| Old code | Since | Now |
|---|---|---|
d3.csv(url, function (error, data) { … }) |
v5 | d3.csv(url).then(data => …) or await |
d3.event.pageX inside .on() |
v6 | .on("mousemove", (event, d) => …) |
d3.mouse(this) |
v6 | d3.pointer(event) |
d3.nest().key(…).entries(data) |
v6 | d3.group(data, d => d.key) / d3.rollup |
d3.scale.linear(), d3.svg.axis() |
v4 | d3.scaleLinear(), d3.axisBottom() |
The v5 one is the sneakiest: the old callback is still accepted — as a row-conversion function — so nothing errors and nothing draws.
Data from a local file
d3.csv and d3.json use fetch(). A page opened by double-click is
file://, its origin is null, and the browser refuses the request —
CORS error from origin 'null'. Run
python3 -m http.server in the folder while developing, and inline the
data for anything you send:
<script>
const data = d3.csvParse(
`month,value
Jan,120
Feb,98`,
d3.autoType,
);
</script>
Or skip D3 for this chart
If the chart only has to be read — a bar, line or pie in a report or a pull request — it may not need D3 at all. Paste the numbers into CSV to chart or JSON to chart and get a static SVG with no script, no CDN and nothing to fail.
Sharing a D3 chart that works
When it renders, send a link, not the file. Published to Comma, the page
runs in a sandboxed iframe (allow-scripts, no allow-same-origin), so
transitions, tooltips and brushing work and D3 loads from the CDN inside
the frame. Reviewers comment on the exact number or label they mean. One
caveat: a published report can't fetch() sibling files, so inline the
CSV or JSON before you publish.