Command Palette

Search for a command to run...

Hectal
PHASE 5Intermediate ~19 min· topic 1 of 7

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. 1

    Ask for the max of text numbers

    In the first example add console.log(d3.max(["10", "9"]));. It prints 9: 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). Try d3.max([10, 9]) for the right answer.

  2. 2

    Use one module only

    In your Vite project run npm ls d3-array to see the one module inside D3 that holds max and group. You may import from it directly, but import * as d3 from "d3" is simpler and the bundler removes what you do not use.

Code & diagrams

Modules that never touch the page New tab

`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: undefined
Modules that change the page, and old code that fails quietly New tab

A 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: Quay

Break 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";.

terminal
$ Console
── what you'll see ──
Uncaught SyntaxError: The requested module 'd3' does not provide an export named 'default'

Break #2

Import a D3 package that is not in the import map

Replace the first line with import { select } from "d3-selection";.

terminal
$ Console (in this editor)
── what you'll see ──
Uncaught TypeError: Failed to resolve module specifier "d3-selection". Relative references must start with either "/", "./", or "../".

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 d3 the 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-array calls can be unit tested in Node with no browser. Selection code needs a DOM (a browser test or jsdom). Put as much logic as possible in pure functions (Topic 16.7).

Remember this

  1. 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. 2

    D3 is many small modules. The d3 object you import is the whole kit bundled together. Underneath are about 30 separate packages, such as d3-scale, d3-array, d3-shape, d3-format, d3-selection and d3-axis. Each does one job. You can import the bundle (import * as d3 from "d3") or one package at a time.

  3. 3

    Some modules never touch the page. d3-array (max, extent, group), d3-scale (Phase 6), d3-shape (line, arc, Phase 7), d3-format and d3-time-format are 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. 4

    Some modules change the page. d3-selection (this phase) finds elements and changes them. d3-axis draws axes into a selection. d3-transition animates changes (Phase 9). d3-drag, d3-zoom and d3-brush attach pointer handlers (Phase 8). These need a document. Remember the split, because it decides where D3 can run and how you test it.

  5. 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. 6

    Versions matter. This course uses D3 7.9.0 (npm install d3 writes "d3": "^7.9.0" into package.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.event is gone, d3.csv no longer takes a callback and returns an empty array). The second example shows both quiet failures, so you can recognise old code. Never copy d3.scale.linear() (version 3), d3.event (up to version 5) or d3.csv(url, callback) (up to version 4).

  7. 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, so import d3 from "d3" fails (Break it below).

Explain it without notes

01

What is the difference between D3 and a chart library such as Chart.js?

02

Name two D3 modules that never touch the page and two that do.

03

Why does d3.csv(url, function (error, rows) {...}) return an empty array in D3 7 instead of throwing?

Practice

01

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 [].

02

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).