Topic 5.1
What D3 Is (and Isn't)
In one line
D3 is a toolbox of small modules for turning data into pictures, not a chart library with a drawBarChart() button. Some modules only do maths and never touch the page; others change the page. Knowing which is which tells you what D3 can do for you and what you still write yourself.
Think of it like this
A well-stocked hardware shop versus a flat-pack wardrobe. A flat-pack box (a chart library) holds every part and gives you one picture of the finished wardrobe. A hardware shop (D3) sells saws, screws, hinges and a measuring tape. You can build any wardrobe, but you decide the shape, and you must know what each tool is for.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- module
- A small package of code that does one job. D3 is a bundle of about 30 of them.
- pure function
- A function that only turns its inputs into an output. It changes nothing on the page and gives the same output for the same input.
- DOM
- The tree of elements the browser builds from your HTML and SVG. Changing the DOM changes what you see.
- bundle
- Many modules packed into one import.
import * as d3 from "d3"gives you all of D3 at once. - default export
- The one thing a module hands over when you write
import name from "module". D3 has none: you import its parts by name or as* as d3. - undefined
- JavaScript's value for "nothing is here". Some D3 functions return it for empty data instead of throwing.
Step by step
01Call the modules that never touch the page
The first example runs scaleLinear, format, line, max, extent, mean and sum. There is no HTML in it at all, and nothing fails: these are pure functions.
scaleLinear().domain([0, 4380]).range([0, 300])(2190) maps the middle of the domain to the middle of the range, 150. d3.line() turns points into the text of an SVG path: M0,10L20,30L40,20. You saw that string by hand in Topic 3.4.
02Look at what they do with empty data
d3.max([]) and d3.mean([]) return undefined, d3.extent([]) returns [undefined, undefined] and d3.sum([]) returns 0. None of them throws.
That is friendly, but the undefined flows on into your next line, and later into an attribute as NaN. Topic 5.5 shows the console error it causes. Handle empty data where you get it.
03Then the modules that change the page
The second example appends an <svg> to the body, adds a <rect> and prints the HTML D3 built. select and append are from d3-selection: they need a document, which this editor provides.
04Recognise old code
d3.event is undefined in version 7. And d3.csv(url, function (error, rows) { ... }) runs the old callback once per row (treating it as a row converter) and returns an empty array: no error, no data, a blank chart.
The promise form, const rows = await d3.csv(url), is the only one in this course (Topic 4.2).
Try it yourself
- 1
Ask for the max of text numbers
In the first example add
console.log(d3.max(["10", "9"]));. It prints9: D3 compares text the way text sorts ("9" comes after "10"). Numbers read from a CSV are text until you convert them (Topic 4.2). Tryd3.max([10, 9])for the right answer. - 2
Use one module only
In your Vite project run
npm ls d3-arrayto see the one module inside D3 that holdsmaxandgroup. You may import from it directly, butimport * as d3 from "d3"is simpler and the bundler removes what you do not use.
Code & diagrams
`d3.version` is `undefined` in D3 7: check the installed version with `npm ls d3` in your project.
Console
(nothing was printed)Expected console output
scale of 2190: 150
format: 933,651
line: M0,10L20,30L40,20
max of [3, 9, 4]: 9
max of []: undefined
extent of []: [ undefined, undefined ]
mean of []: undefined
sum of []: 0
d3.version: undefined
typeof d3.default: undefinedA callback passed to `d3.csv` is read as a row converter, so nothing throws and the chart gets zero rows: the worst kind of failure.
Console
(nothing was printed)Expected console output
drew: <svg width="160" height="24"><rect width="120" height="20" fill="#2a9d8f"></rect></svg>
d3.event: undefined
old callback ran 25 times (once per row); rows returned: 0
promise form: 25 rows, first: QuayBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Import D3 as a default
Replace the first line of any example with import d3 from "d3";.
Break #2
Import a D3 package that is not in the import map
Replace the first line with import { select } from "d3-selection";.
Myth vs fact
Myth
D3 is a chart library.
Fact
D3 has no BarChart function. It gives you scales, shapes, selections and layouts. The chart is code you write with them, which is why the result can be anything.
Myth
D3 only works with SVG.
Fact
D3's maths and data modules work with anything. Phases 11 and 12 draw with Canvas using the same scales and shapes, and D3's modules also feed React, Chart.js and WebGL code.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
D3's packages are ES modules with no side effects, so a bundler can drop what you do not import (tree-shaking). With
import * as d3the whole bundle is not necessarily shipped: unused exports are removed in a production build. Check with a bundle analyser before you assume a size. - ▸
The split between pure and DOM modules is also a testing split. Scales, shapes and
d3-arraycalls can be unit tested in Node with no browser. Selection code needs a DOM (a browser test orjsdom). Put as much logic as possible in pure functions (Topic 16.7).
Remember this
- 1
The problem this solves: chart libraries draw the charts their authors imagined. The moment Tomás asks for something they did not imagine (a station dot that grows on hover, a map coloured by district, an outage day marked on a line), you fight the library. D3 gives you the parts to build any chart, and the parts work with the web's own tools: SVG, CSS and the DOM.
- 2
D3 is many small modules. The
d3object you import is the whole kit bundled together. Underneath are about 30 separate packages, such asd3-scale,d3-array,d3-shape,d3-format,d3-selectionandd3-axis. Each does one job. You can import the bundle (import * as d3 from "d3") or one package at a time. - 3
Some modules never touch the page.
d3-array(max,extent,group),d3-scale(Phase 6),d3-shape(line,arc, Phase 7),d3-formatandd3-time-formatare pure functions: numbers and strings in, numbers and strings out. They run in Node, in a test, or in a React component that never lets D3 near the DOM. The first example runs several of them with no page at all. - 4
Some modules change the page.
d3-selection(this phase) finds elements and changes them.d3-axisdraws axes into a selection.d3-transitionanimates changes (Phase 9).d3-drag,d3-zoomandd3-brushattach pointer handlers (Phase 8). These need a document. Remember the split, because it decides where D3 can run and how you test it. - 5
Why React teams still use D3. React wants to own the DOM, so it does not like another library changing elements behind its back. The usual answer is to use D3 only for its maths (scales, shapes, layouts) and let React render the elements (Phase 14). That works because the maths modules never touch the page.
- 6
Versions matter. This course uses D3 7.9.0 (
npm install d3writes"d3": "^7.9.0"intopackage.json). Tutorials from 2012–2019 teach version 3, 4 or 5. They break in version 7 in loud ways (an import error) and quiet ways (d3.eventis gone,d3.csvno longer takes a callback and returns an empty array). The second example shows both quiet failures, so you can recognise old code. Never copyd3.scale.linear()(version 3),d3.event(up to version 5) ord3.csv(url, callback)(up to version 4). - 7
Using it. In the Vite project you wrote in Topic 4.1,
import * as d3 from "d3"at the top of a file gives you the whole kit. In the editor on this page, the same line works, and the editor already has D3 7.9.0 installed. There is no default export, soimport d3 from "d3"fails (Break it below).
Explain it without notes
What is the difference between D3 and a chart library such as Chart.js?
Name two D3 modules that never touch the page and two that do.
Why does d3.csv(url, function (error, rows) {...}) return an empty array in D3 7 instead of throwing?
Practice
Write a function summary(values) that returns { min, max, mean } using D3 and returns null for an empty array, so the caller must handle the empty case. Test it with [3, 9, 4] and [].
Look at this old line: d3.select("#chart").on("click", function () { console.log(d3.event.pageX); });. Say what is wrong in D3 7. (The fix comes in Topic 5.6.)
Trade-offs
- ↔
D3 is the most flexible way to draw data and the slowest way to draw a standard bar chart. If a Chart.js option does what you need in five lines, use it (Topic 14.5 compares them).
Done when you can
I can explain why D3 is a toolbox, not a chart library.
I can name modules that never touch the page and modules that do.
I can import D3 correctly in Vite and in the editor on this page.
I can recognise version 3 to 5 code:
d3.event,d3.scale.,d3.csv(url, callback).