Modules and Packages

D3's Modular Design and Its Packages

The d3 package contains almost no code of its own: it re-exports 30 packages named d3-*, each with its own repository, version and tests. Most are pure functions that never touch a document (d3.scaleLinear() maps numbers to numbers, d3.line() turns points into a path string), so they also run in Node, in workers and inside React 7,897 components (D3 with React). Only a handful select, create or listen to elements.

The 30 packages re-exported by d3 7.9.0, grouped by what they do
The 30 packages re-exported by d3 7.9.0, grouped by what they do

A bundler given import { scaleLinear } from "d3" ships only what you use, not the full bundle (280 KB minified, 92 KB gzipped). All but one package use ISC, a short MIT-style licence; d3-ease is BSD-3-Clause and carries Robert Penner's 2001 copyright, since its easing equations derive from his.

The 30 packages inside d3, packed by purpose; outlined ones touch the DOMHTMLLive
<!doctype html>
<style>
  body { margin: 0; padding: 8px; background: #fafaf7; font: 12px system-ui, sans-serif; color: #263238; }
  svg { width: 100%; max-width: 600px; display: block; }
  #out { min-height: 1.4em; }
</style>
<script src="https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js"></script>
<svg viewBox="0 0 600 380" font-size="10" text-anchor="middle"></svg>
<p id="out">Hover a package.</p>
<script>
  // The packages d3 7.9.0 re-exports, by what they do; dom: true selects, creates or listens to elements
  const groups = {
    'Data': ['array', 'dsv', 'fetch', 'format', 'time', 'time-format', 'random'],
    'Scales and color': ['scale', 'scale-chromatic', 'color', 'interpolate', 'ease'],
    'Shapes and layouts': ['shape', 'path', 'polygon', 'hierarchy', 'force', 'quadtree', 'delaunay', 'chord', 'contour'],
    'Maps': ['geo'],
    'DOM and interaction': ['selection', 'transition', 'axis', 'brush', 'drag', 'zoom'],
    'Utilities': ['dispatch', 'timer'],
  };
  const dom = new Set(['selection', 'transition', 'axis', 'brush', 'drag', 'zoom', 'fetch']);
  const colors = ['#1f5f8b', '#e09a10', '#3f7d3a', '#2a9d8f', '#b5452f', '#5b3f99'];

  // A two-level hierarchy: root → purpose → package (each package counts 1)
  const root = d3.hierarchy({ children: Object.entries(groups).map(([name, pkgs]) =>
    ({ name, children: pkgs.map(p => ({ name: `d3-${p}` })) })) })
    .count();
  d3.pack().size([600, 380]).padding(d => d.depth === 0 ? 10 : 3)(root);

  const svg = d3.select('svg');
  const groupsG = svg.selectAll('g.group').data(root.children).join('g').attr('class', 'group');
  groupsG.append('circle').attr('cx', d => d.x).attr('cy', d => d.y).attr('r', d => d.r)
    .attr('fill', (d, i) => colors[i]).attr('fill-opacity', 0.12);
  groupsG.append('text').attr('x', d => d.x).attr('y', d => d.y - d.r - 3)
    .attr('font-weight', 'bold').attr('font-size', 11).text(d => d.data.name);

  const leaf = svg.selectAll('g.leaf').data(root.leaves()).join('g').attr('class', 'leaf')
    .attr('transform', d => `translate(${d.x},${d.y})`);
  leaf.append('circle').attr('r', d => d.r)
    .attr('fill', d => colors[root.children.indexOf(d.parent)])
    .attr('stroke', d => dom.has(d.data.name.slice(3)) ? '#263238' : 'none').attr('stroke-width', 2.5);
  leaf.append('text').attr('dy', '0.35em').attr('fill', 'white')
    .text(d => d.data.name.slice(3)).attr('font-size', d => Math.min(11, d.r / 2.6));
  leaf.on('pointerenter', (event, d) => d3.select('#out').text(
    `${d.data.name}: ${dom.has(d.data.name.slice(3)) ? 'touches the document' : 'pure functions, runs anywhere'}` +
    (d.data.name === 'd3-ease' ? ' · BSD-3-Clause (Robert Penner\'s equations); the rest are ISC' : ' · ISC licence')));
</script>