Skip to main content
Technology & EngineeringData Visualization132 lines

D3

Expert guidance for building custom, data-driven visualizations with D3.js

Quick Summary26 lines
You are an expert in creating data visualizations with D3.js.

## Key Points

- **Transitions and Animations**: Use `selection.transition().duration(750)` to animate attribute changes smoothly.
- **Zooming and Panning**: Apply `d3.zoom()` to enable user-driven navigation of large datasets.
- **Brush and Linked Views**: Use `d3.brush()` to let users select regions and filter across coordinated views.
- **Force-Directed Graphs**: Use `d3.forceSimulation()` for network and graph layouts.
- **Geographic Maps**: Use `d3.geoPath()` and `d3.geoProjection()` with GeoJSON/TopoJSON data.
- Use the `.join()` pattern (D3 v5+) instead of manual enter/update/exit for cleaner code.
- Always set explicit domains on scales rather than relying on defaults; use `d3.extent()` or `d3.max()` to compute domains from data.
- Separate data transformation logic from rendering logic to keep code maintainable.
- Forgetting to handle the exit selection, which causes stale elements to remain in the DOM when data changes.
- Mutating data arrays in place instead of creating new references, which breaks data binding identity checks.

## Quick Example

```bash
npm install d3
```

```html
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
```
skilldb get data-visualization-skills/D3Full skill: 132 lines
Paste into your CLAUDE.md or agent config

D3.js — Data Visualization

You are an expert in creating data visualizations with D3.js.

Overview

D3.js (Data-Driven Documents) is a low-level JavaScript library for producing dynamic, interactive data visualizations in the browser using SVG, Canvas, and HTML. It gives you full control over every visual element by binding data to the DOM and applying data-driven transformations. Use D3 when you need highly custom or novel visualizations that go beyond what charting libraries offer out of the box.

Setup & Configuration

Install via npm:

npm install d3

Or use the CDN:

<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>

Basic project setup with ES modules:

import * as d3 from "d3";

// Create an SVG container
const svg = d3.select("#chart")
  .append("svg")
  .attr("width", 800)
  .attr("height", 500);

Core Patterns

Selections and Data Binding (Enter-Update-Exit)

const data = [10, 20, 30, 40, 50];

const bars = svg.selectAll("rect")
  .data(data)
  .join("rect")
    .attr("x", (d, i) => i * 60)
    .attr("y", d => 500 - d * 8)
    .attr("width", 50)
    .attr("height", d => d * 8)
    .attr("fill", "steelblue");

Scales and Axes

const xScale = d3.scaleBand()
  .domain(data.map((d, i) => i))
  .range([0, 800])
  .padding(0.1);

const yScale = d3.scaleLinear()
  .domain([0, d3.max(data)])
  .range([500, 0]);

svg.append("g")
  .attr("transform", "translate(0, 500)")
  .call(d3.axisBottom(xScale));

svg.append("g")
  .call(d3.axisLeft(yScale));

Line and Area Charts

const line = d3.line()
  .x((d, i) => xScale(i))
  .y(d => yScale(d))
  .curve(d3.curveMonotoneX);

svg.append("path")
  .datum(data)
  .attr("d", line)
  .attr("fill", "none")
  .attr("stroke", "steelblue")
  .attr("stroke-width", 2);

Advanced Features

  • Transitions and Animations: Use selection.transition().duration(750) to animate attribute changes smoothly.
  • Zooming and Panning: Apply d3.zoom() to enable user-driven navigation of large datasets.
  • Brush and Linked Views: Use d3.brush() to let users select regions and filter across coordinated views.
  • Force-Directed Graphs: Use d3.forceSimulation() for network and graph layouts.
  • Geographic Maps: Use d3.geoPath() and d3.geoProjection() with GeoJSON/TopoJSON data.

Best Practices

  • Use the .join() pattern (D3 v5+) instead of manual enter/update/exit for cleaner code.
  • Always set explicit domains on scales rather than relying on defaults; use d3.extent() or d3.max() to compute domains from data.
  • Separate data transformation logic from rendering logic to keep code maintainable.

Core Philosophy

D3 is not a charting library -- it is a vocabulary for expressing data-driven transformations of the DOM. This distinction matters because D3 does not give you a bar chart function; it gives you scales, axes, selections, and data joins, and you compose them into a bar chart. The learning curve is steep precisely because you are learning primitives, not recipes. But once you internalize the primitives, you can build any visualization that can be expressed in SVG or Canvas, including ones that no charting library has anticipated.

The data join is the conceptual heart of D3. When you bind data to DOM elements with .data() and handle enter, update, and exit selections, you are declaring a relationship between your data and the visual representation. This declarative binding is what makes D3 visualizations reactive to data changes: add a data point and a new element appears; remove one and the corresponding element exits with a transition. Understanding this model -- not memorizing API calls -- is the key to productive D3 development.

Separate your data logic from your rendering logic. D3 encourages (but does not enforce) a pattern where data loading, transformation, and scale computation happen in one layer, and DOM manipulation happens in another. When these concerns are mixed, the code becomes difficult to test, debug, and adapt. Clean D3 code reads like a pipeline: load data, compute scales, join data to elements, apply attributes from scales.

Anti-Patterns

  • Mutating data arrays in place: Changing the data array that was passed to .data() and expecting D3 to detect the change. D3 uses object identity for data joins; mutating in place breaks the enter/update/exit cycle and causes stale elements to persist.

  • Manual enter/update/exit when .join() suffices: Writing verbose .enter().append() / .exit().remove() patterns for straightforward bindings that could be expressed with the .join() shorthand introduced in D3 v5. The older pattern is only needed when enter and update require genuinely different behavior.

  • Hardcoding dimensions instead of computing from data: Setting axis domains, ranges, and SVG dimensions to magic numbers rather than deriving them from the data with d3.extent(), d3.max(), or container measurements. Hardcoded values break as soon as the data changes.

  • Embedding data transformation inside rendering code: Performing filtering, aggregation, or reshaping inside the selection chain rather than before it. This makes the rendering code unreadable and impossible to test independently.

  • Ignoring accessibility: Producing SVG charts without titles, descriptions, ARIA labels, or keyboard navigation. D3 gives you full control over the markup, which means accessibility is your responsibility -- the library will not add it for you.

Common Pitfalls

  • Forgetting to handle the exit selection, which causes stale elements to remain in the DOM when data changes.
  • Mutating data arrays in place instead of creating new references, which breaks data binding identity checks.

Install this skill directly: skilldb add data-visualization-skills

Get CLI access →