Skip to content
Historical Data Visualisation

D3 (Data-Driven Documents) is a JavaScript library that binds your data directly to SVG shapes in the browser, so a historian can build fully custom, interactive visualisations that tools like Datawrapper cannot. The trade-off is that you write code. If you only need a standard chart, start with Observable Plot (D3's friendlier sibling); reach for raw D3 when you need a bespoke layout or interaction. This guide walks through the core ideas with a small worked example.

What is D3, in plain language?

Think of D3 as glue between two worlds: your data (an array of records, say one per Tudor parish) and the document (SVG circles, lines, and text on a web page). You describe a rule — "one circle per parish, x-position from its founding year" — and D3 creates, updates, and removes elements to keep the page matching the data. That data-to-pixels mapping is the whole idea.

Do you need JavaScript first?

You need the basics: variables, arrays, functions, and arrow syntax (d => d.year), plus a little HTML and SVG. You do not need to be an expert. The classic beginner trap is trying to learn JavaScript and D3 simultaneously from zero — get a week of plain JavaScript under your belt first, and D3 will feel like a focused toolkit rather than a second language.

Should you use Observable Plot or raw D3?

For most historical charts, start higher up the ladder:

NeedUseEffort
Standard line/bar/scatterObservable PlotVery low
Custom layout, novel encodingRaw D3High
Bespoke interaction, animationRaw D3High
Quick exploratory chartObservable PlotVery low

A timeline of monarchs in Plot is a handful of lines; the same in raw D3 is fifty. Save raw D3 for when its control is genuinely needed.

How do scales work?

A scale is a function that maps your data's range to screen space, so you never hand-compute pixels. For a timeline of founding dates:

js
import * as d3 from "d3";

const x = d3.scaleTime()
  .domain([new Date(1500, 0, 1), new Date(1900, 0, 1)])  // your years
  .range([40, 760]);                                       // pixels

x(new Date(1600, 0, 1));  // → a pixel position inside the axis

scaleTime understands dates and leap years; scaleLinear handles counts; scaleOrdinal maps categories to colours. Choosing the right scale is most of the work.

A small worked example: plotting parishes by date

Here is a minimal data join — D3's central mechanism — placing one circle per record:

js
const data = [
  { parish: "St Mary", founded: 1540, records: 120 },
  { parish: "All Saints", founded: 1612, records: 88 },
  { parish: "St Giles", founded: 1701, records: 54 },
];

const svg = d3.select("body").append("svg")
  .attr("width", 800).attr("height", 200);

svg.selectAll("circle")
  .data(data)
  .join("circle")                          // enter + update + exit, handled
  .attr("cx", d => x(new Date(d.founded, 0, 1)))
  .attr("cy", 100)
  .attr("r", d => Math.sqrt(d.records));    // area ~ record count

.join("circle") is the modern, beginner-friendly form of the data join: it creates a circle for each record and keeps them in sync if the data changes. Radius scaled by the square root of the count keeps area proportional — a small but important honesty point.

What should you learn next?

Once the join clicks, the productive path is: add an axis with d3.axisBottom(x), add tooltips on mouseover, then explore a layout helper (d3.forceSimulation for networks, d3.geoPath for maps). Resist building everything from primitives — lean on Plot for ordinary charts and reserve your D3 effort for the genuinely custom historical visual that justified learning it.

Key Takeaways

  • D3 binds data to SVG, enabling custom interactive visualisations at the cost of code.
  • Learn basic JavaScript first; learning both from zero at once is the usual stall point.
  • Prefer Observable Plot for standard charts; drop to raw D3 only when necessary.
  • Scales map data domains to pixel ranges — pick scaleTime, scaleLinear, or scaleOrdinal deliberately.
  • The data join via .join() is the core concept; start there.
  • Scale circle radius by the square root of a count so area stays proportional.

Frequently Asked Questions

What is D3 and why would a historian use it?

D3 (Data-Driven Documents) is a JavaScript library that binds data to SVG elements in the browser, letting historians build bespoke, fully interactive visualisations that off-the-shelf tools cannot produce, at the cost of writing code.

Do I need to know JavaScript before learning D3?

You need comfort with basic JavaScript (variables, arrays, functions, arrow syntax) and a little HTML and CSS; D3 itself is learnable alongside, but trying to learn both from absolute zero at once is the usual reason beginners stall.

Should I use raw D3 or a simpler wrapper like Observable Plot?

Start with Observable Plot for standard charts because it produces good results in a few lines; drop to raw D3 only when you need a custom layout, bespoke interaction, or an encoding that Plot cannot express.

How do scales work in D3?

A scale is a function mapping your data domain (for example years 1500 to 1900) to a visual range (for example 0 to 800 pixels); D3 provides linear, time, ordinal, and other scales so you rarely compute pixel positions by hand.

What is the single hardest concept in D3 for beginners?

The data join — how D3 matches array elements to DOM elements through enter, update, and exit selections; modern D3 simplifies this with the join() method, which is where beginners should start.