NexusCS

D3.js

JavaScript libraries
Quick reference for D3.js v7+ — JavaScript library for data-driven DOM manipulation and data visualization.
data-visualization
svg
charts
graphs

Getting started

Installation

// CDN ES Module (Recommended)
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
// npm
npm install d3
import * as d3 from "d3";
// Selective imports (tree-shakeable)
import { select, selectAll } from "d3-selection";
import { scaleLinear } from "d3-scale";
// UMD (legacy)
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>

Quick Example

// Create bar chart
const data = [4, 8, 15, 16, 23, 42];

d3.select("body")
  .selectAll("div")
  .data(data)
  .join("div")
  .style("width", (d) => `${d * 10}px`)
  .text((d) => d);

Basic Concepts

Concept Description
Selections Query and manipulate DOM
Data joins Bind data to elements
Scales Map data to visual values
Shapes Generate SVG paths
Axes Render reference marks
Transitions Animate changes

Selections

Selecting Elements

d3.select("div");
// First matching element
d3.selectAll("div");
// All matching elements
d3.select("#id");
// By ID
d3.selectAll(".class");
// By class name
selection.select("span");
// Descendant (propagates data)
selection.selectAll("span");
// All descendants (no data propagation)

Modifying Elements

selection.attr("class", "active");
// Set attribute
selection.attr("width", (d) => d.value);
// With data accessor
selection.style("color", "red");
// Set CSS style
selection.property("checked", true);
// Set DOM property
selection.text("Hello");
// Set text content
selection.html("<b>Bold</b>");
// Set HTML content
selection.classed("active", true);
// Add/remove class

Creating Elements

selection.append("div");
// Append as last child
selection.insert("div", ":first-child");
// Insert before selector
selection.remove();
// Remove from DOM
const svg = d3.create("svg").attr("width", 640).attr("height", 400);
document.body.append(svg.node());
// Create detached element

Data Joins

Modern Join Pattern

svg
  .selectAll("circle")
  .data(data)
  .join("circle")
  .attr("cx", (d) => x(d.date))
  .attr("cy", (d) => y(d.value))
  .attr("r", 5);
// Recommended v7+ pattern

Join with Callbacks

svg
  .selectAll("circle")
  .data(data)
  .join(
    (enter) => enter.append("circle").attr("fill", "green"),
    (update) => update.attr("fill", "blue"),
    (exit) => exit.remove(),
  );
// Separate enter/update/exit logic

Key Function

selection.data(data, (d) => d.id);
// Bind by unique key

Legacy Enter/Exit

const circles = svg.selectAll("circle").data(data);

circles.enter().append("circle").attr("r", 5);
// Handle new data

circles.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
// Update existing

circles.exit().remove();
// Remove old elements

Data Binding

selection.datum(value);
// Bind single datum (no join)
selection.data();
// Get bound data array

Scales

Linear Scale

const x = d3.scaleLinear().domain([0, 100]).range([0, 500]);

x(50); // → 250
x.invert(250); // → 50
// Map continuous to continuous

Time Scale

const x = d3
  .scaleTime()
  .domain([new Date(2020, 0, 1), new Date(2021, 0, 1)])
  .range([0, 600]);
// Dates to pixels

Ordinal Scale

const color = d3
  .scaleOrdinal()
  .domain(["a", "b", "c"])
  .range(["red", "green", "blue"]);
// Discrete to discrete
const color = d3.scaleOrdinal(d3.schemeCategory10);
// With color scheme

Band Scale

const x = d3.scaleBand().domain(["A", "B", "C"]).range([0, 300]).padding(0.1);

x("A"); // → start position
x.bandwidth(); // → width of band
// For bar charts

Other Scale Types

Scale Description
scaleLog Logarithmic transform
scalePow Power transform
scaleSqrt Square root transform
scaleQuantile Discrete output from quantiles
scaleQuantize Discrete output from thresholds
scaleThreshold Arbitrary breakpoints
scaleSequential Sequential color interpolation
scaleDiverging Diverging color schemes

Scale Methods

scale.domain([0, 100]);
// Set input domain
scale.range([0, 500]);
// Set output range
scale.clamp(true);
// Clamp values to range
scale.nice();
// Round domain to nice values
scale.invert(value);
// Reverse mapping (continuous scales)

Axes

Creating Axes

const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale);

svg.append("g").attr("transform", `translate(0,${height})`).call(xAxis);

svg.append("g").attr("transform", `translate(${marginLeft},0)`).call(yAxis);
// Standard axis setup

Axis Constructors

Constructor Description
d3.axisTop(scale) Horizontal, ticks above
d3.axisRight(scale) Vertical, ticks right
d3.axisBottom(scale) Horizontal, ticks below
d3.axisLeft(scale) Vertical, ticks left

Configuring Axes

xAxis.ticks(10);
// Number of ticks (hint)
xAxis.tickFormat(d3.format(".2f"));
// Custom tick format
xAxis.tickSize(6);
// Tick line length
xAxis.tickPadding(3);
// Space between tick and label
xAxis.tickValues([0, 25, 50, 75, 100]);
// Explicit tick values

Updating Axes

gx.transition().duration(750).call(xAxis);
// Animate axis changes

Shapes

Line Generator

const line = d3
  .line()
  .x((d) => x(d.date))
  .y((d) => y(d.value))
  .curve(d3.curveMonotoneX);

svg
  .append("path")
  .datum(data)
  .attr("fill", "none")
  .attr("stroke", "steelblue")
  .attr("d", line);
// Line chart path

Area Generator

const area = d3
  .area()
  .x((d) => x(d.date))
  .y0(height)
  .y1((d) => y(d.value));

svg.append("path").datum(data).attr("fill", "steelblue").attr("d", area);
// Area chart path

Arc Generator

const arc = d3.arc().innerRadius(0).outerRadius(100);

svg.append("path").datum({ startAngle: 0, endAngle: Math.PI }).attr("d", arc);
// Pie/donut slice

Pie Layout

const pie = d3
  .pie()
  .value((d) => d.value)
  .sort(null);

svg
  .selectAll("path")
  .data(pie(data))
  .join("path")
  .attr("d", arc)
  .attr("fill", (d, i) => color(i));
// Pie chart

Curve Types

Curve Description
curveLinear Straight line segments
curveBasis Smooth B-spline
curveCardinal Cardinal spline
curveCatmullRom Catmull-Rom spline
curveMonotoneX Monotonic in x
curveMonotoneY Monotonic in y
curveNatural Natural cubic spline
curveStep Step function
curveStepBefore Step before point
curveStepAfter Step after point

Transitions

Basic Transition

selection.transition().duration(750).attr("r", 10);
// Animate attribute change

Named Transitions

const t = d3.transition().duration(750).ease(d3.easeCubicInOut);

selection.transition(t).attr("fill", "red");
// Reusable transition

Chained Transitions

selection
  .transition()
  .duration(500)
  .attr("r", 10)
  .transition()
  .attr("fill", "blue");
// Sequential animations

Staggered Transitions

selection
  .transition()
  .delay((d, i) => i * 10)
  .duration(500)
  .attr("opacity", 1);
// Delay by index

Easing Functions

Easing Variants
easeLinear
easeQuad In, Out, InOut
easeCubic In, Out, InOut
easeSin In, Out, InOut
easeExp In, Out, InOut
easeCircle In, Out, InOut
easeElastic In, Out, InOut
easeBounce In, Out, InOut
easeBack In, Out, InOut

Transition Methods

transition.duration(750);
// Animation length (ms)
transition.delay(100);
// Start delay (ms)
transition.ease(d3.easeCubicOut);
// Easing function
transition.on("start", callback);
// Event listener
transition.on("end", callback);
// On completion

Data Loading

CSV Files

const data = await d3.csv("data.csv");
// Load CSV with headers
const data = await d3.csv("data.csv", (d) => ({
  date: new Date(d.date),
  value: +d.value,
}));
// With type conversion

JSON Files

const data = await d3.json("data.json");
// Load JSON

Other Formats

const data = await d3.tsv("data.tsv");
// Tab-separated values
const text = await d3.text("data.txt");
// Plain text
const xml = await d3.xml("data.xml");
// XML document
const html = await d3.html("page.html");
// HTML document

Type Inference

d3.autoType({ date: "2020-01-01", value: "42" });
// → {date: Date, value: 42}
// Auto-convert strings

Data Utilities

Statistics

d3.min(data, (d) => d.value);
// Minimum value
d3.max(data, (d) => d.value);
// Maximum value
d3.extent(data, (d) => d.value);
// [min, max] array
d3.mean(data, (d) => d.value);
// Average
d3.median(data, (d) => d.value);
// Median value
d3.sum(data, (d) => d.value);
// Sum of values
d3.quantile(values, 0.5);
// Quantile (0.5 = median)

Grouping

d3.group(data, (d) => d.category);
// Map {category => [items]}
d3.groups(data, (d) => d.category);
// Array [[category, [items]], ...]
d3.rollup(
  data,
  (v) => d3.sum(v, (d) => d.value),
  (d) => d.category,
);
// Aggregate by group
d3.index(data, (d) => d.id);
// Map {id => item}

Binning

const bins = d3.bin().domain([0, 100]).thresholds(10)(values);
// Histogram bins
const bins = d3
  .bin()
  .value((d) => d.value)
  .thresholds(d3.thresholdScott)(data);
// With accessor and threshold function

Arrays

d3.range(10);
// [0, 1, 2, ..., 9]
d3.range(5, 10);
// [5, 6, 7, 8, 9]
d3.range(0, 1, 0.2);
// [0, 0.2, 0.4, 0.6, 0.8]
d3.shuffle(array);
// Random permutation
d3.ticks(0, 10, 5);
// Nice tick values

SVG Helpers

SVG Creation

const svg = d3
  .create("svg")
  .attr("width", 640)
  .attr("height", 400)
  .attr("viewBox", [0, 0, 640, 400]);

document.body.append(svg.node());
// Create SVG element

Transforms

selection.attr("transform", `translate(${x},${y})`);
// Move element
selection.attr("transform", `rotate(${angle})`);
// Rotate (degrees)
selection.attr("transform", `scale(${k})`);
// Scale element
selection.attr("transform", `translate(${x},${y}) rotate(${angle})`);
// Combined transforms

Paths

const path = d3.path();
path.moveTo(x, y);
path.lineTo(x, y);
path.arc(x, y, radius, startAngle, endAngle);
path.closePath();
const d = path.toString();
// Manual path construction

Events

Event Handlers

selection.on("click", function (event, d) {
  // this = DOM element
  // event = native event
  // d = bound datum
});
// Standard event handler
selection.on("click", (event, d) => {
  // Arrow function (no this binding)
});
// Arrow function style

Common Events

selection.on("mouseover", handler);
// Mouse enters
selection.on("mouseout", handler);
// Mouse leaves
selection.on("mousemove", handler);
// Mouse moves
selection.on("click", handler);
// Mouse click
selection.on("dblclick", handler);
// Double click

Removing Handlers

selection.on("click", null);
// Remove event listener

Event Object

function handler(event, d) {
  event.target; // Element
  event.currentTarget; // Element with listener
  event.pageX; // Mouse X
  event.pageY; // Mouse Y
  event.preventDefault();
  event.stopPropagation();
}

Color Schemes

Categorical Schemes

d3.schemeCategory10;
// 10 colors
d3.schemeTableau10;
// Tableau 10 colors
d3.schemeSet1;
d3.schemeSet2;
d3.schemeSet3;
// ColorBrewer sets

Sequential Schemes

d3.interpolateBlues;
d3.interpolateGreens;
d3.interpolateReds;
// Single hue
d3.interpolateViridis;
d3.interpolatePlasma;
d3.interpolateInferno;
// Perceptually uniform

Diverging Schemes

d3.interpolateRdBu;
d3.interpolatePiYG;
d3.interpolateBrBG;
// Diverging color schemes

Using Schemes

const color = d3.scaleOrdinal(d3.schemeCategory10);
// Categorical scale
const color = d3.scaleSequential(d3.interpolateViridis).domain([0, 100]);
// Sequential scale

Gotchas

Data Propagation

⚠️ Data does NOT propagate through .selectAll() — Use .select() for single-child selections to propagate parent data.

// WRONG - data is lost
parent.selectAll("g").selectAll("circle");

// RIGHT - data propagates
parent.selectAll("g").select("circle");

API Changes (v3 → v7)

⚠️ D3 v7 has breaking changes from v3:

  • d3.scale.linear()d3.scaleLinear()
  • .enter().append().join()
  • Removed d3.layout.* — use separate packages

Selection Types

⚠️ Selections are NOT DOM nodes — Use .node() to access the underlying element:

const svg = d3.select("svg");
svg.getAttribute("width"); // ❌ Undefined
svg.node().getAttribute("width"); // ✅ Works

Method Chaining Returns

⚠️ Different methods return different types:

  • .data() → update selection
  • .enter() → enter selection
  • .transition() → transition (not selection)

Transition Limitations

⚠️ Transitions only interpolate attrs/styles, not data — Rebind data first:

// WRONG
selection.transition().data(newData);

// RIGHT
selection.data(newData).transition();

Scale Edge Cases

⚠️ Scales don't handle null/NaN — Filter invalid data before scaling:

data.filter((d) => d.value != null);

Also see