web-dataviz-d3

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

D3.js Data Visualization Patterns

D3.js 数据可视化模式

Quick Guide: D3 v7 is fully modular ES modules. Use
selection.join()
for the data join (replaces manual enter/update/exit). Prefer modular imports (
d3-selection
,
d3-scale
, etc.) to reduce bundle size. Scales map data domains to visual ranges; axes render tick marks from scales. Shape generators (
d3.line
,
d3.arc
,
d3.area
) produce SVG path strings from data arrays. Transitions animate attribute/style changes with automatic interpolation. For framework integration, let D3 handle data computation (scales, layouts, shapes) and let your framework own the DOM.

<critical_requirements>
快速指南: D3 v7 是完全模块化的 ES 模块。使用
selection.join()
实现数据连接(替代手动的 enter/update/exit 流程)。优先使用模块化导入(
d3-selection
、
d3-scale
等)以减小打包体积。比例尺用于将数据域映射到视觉范围;坐标轴基于比例尺渲染刻度。图形生成器(
d3.line
、
d3.arc
、
d3.area
)从数据数组生成 SVG 路径字符串。过渡动画通过自动插值实现属性/样式的变化。框架集成时,让 D3 处理数据计算(比例尺、布局、图形),让框架负责 DOM 管理。

<critical_requirements>

CRITICAL: Before Using This Skill

重要提示:使用此技能之前

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type
, named constants)
(You MUST use
selection.join()
for data joins — NOT manual enter/append/merge/exit chains)
(You MUST use modular imports (
d3-selection
,
d3-scale
,
d3-shape
) — NOT
import * as d3 from "d3"
in production bundles)
(You MUST use named constants for ALL visual dimensions, colors, and timing values — NO magic numbers)
(You MUST type D3 selections and scales with TypeScript generics —
Selection<SVGGElement, Datum, ...>
,
ScaleLinear<number, number>
)
</critical_requirements>

Auto-detection: D3, d3, d3.js, d3-selection, d3-scale, d3-shape, d3-axis, d3-transition, d3-force, d3-geo, d3-zoom, d3-brush, d3-drag, d3-array, selection.join, data join, enter update exit, scaleLinear, scaleBand, scaleTime, axisBottom, axisLeft, forceSimulation, geoPath, geoMercator, line generator, arc generator, SVG visualization, data-driven documents
When to use:
  • Building custom SVG/Canvas data visualizations from scratch
  • Bindings between data arrays and DOM elements (the data join)
  • Mapping data domains to pixel ranges (scales and axes)
  • Generating SVG paths from data (lines, arcs, areas, pies)
  • Animating data transitions with interpolated attributes
  • Force-directed graph layouts and geographic map projections
  • Adding zoom, brush, and drag interactions to visualizations
When NOT to use:
  • Standard chart types (bar, line, pie) with minimal customization — use a charting library built on D3
  • Dashboards with many chart widgets — use a higher-level charting library
  • Simple data tables or non-graphical data display
Key patterns covered:
  • Selections and the data join (
    selection.data().join()
    )
  • Scales (linear, band, time, ordinal) and axes
  • Shape generators (line, area, arc, pie, stack)
  • Transitions and animated updates
  • Force-directed graph layouts
  • Geographic projections and choropleth maps
  • Zoom, brush, and drag interactions
  • Framework integration: D3 for math, framework for DOM
  • Responsive SVG with viewBox
  • TypeScript typing for D3

Detailed Resources:
  • examples/core.md - Selections, data joins, scales, axes, shapes, responsive SVG
  • examples/interaction.md - Transitions, zoom, brush, drag, tooltips
  • examples/advanced.md - Force layouts, geo projections, framework integration patterns
  • reference.md - Module reference, decision frameworks, anti-patterns

<philosophy>
所有代码必须遵循 CLAUDE.md 中的项目规范(短横线命名、命名导出、导入顺序、
import type
、命名常量)
(必须使用
selection.join()
进行数据连接——禁止手动的 enter/append/merge/exit 链式调用)
(必须使用模块化导入(
d3-selection
、
d3-scale
、
d3-shape
)——生产环境打包中禁止使用
import * as d3 from "d3"
)
(所有视觉尺寸、颜色和时间值必须使用命名常量——禁止魔法数字)
(必须使用 TypeScript 泛型为 D3 选择器和比例尺添加类型——
Selection<SVGGElement, Datum, ...>
、
ScaleLinear<number, number>
)
</critical_requirements>

自动检测关键词: D3、d3、d3.js、d3-selection、d3-scale、d3-shape、d3-axis、d3-transition、d3-force、d3-geo、d3-zoom、d3-brush、d3-drag、d3-array、selection.join、data join、enter update exit、scaleLinear、scaleBand、scaleTime、axisBottom、axisLeft、forceSimulation、geoPath、geoMercator、line generator、arc generator、SVG visualization、data-driven documents
适用场景:
  • 从零构建自定义 SVG/Canvas 数据可视化
  • 数据数组与 DOM 元素的绑定(数据连接)
  • 将数据域映射到像素范围(比例尺和坐标轴)
  • 从数据生成 SVG 路径(折线、圆弧、面积、饼图)
  • 通过插值属性实现数据过渡动画
  • 力导向图布局和地理地图投影
  • 为可视化添加缩放、刷选和拖拽交互
不适用场景:
  • 只需少量自定义的标准图表类型(柱状图、折线图、饼图)——使用基于 D3 构建的图表库
  • 包含多个图表组件的仪表盘——使用更高层级的图表库
  • 简单的数据表格或非图形化的数据展示
涵盖的核心模式:
  • 选择器与数据连接(
    selection.data().join()
    )
  • 比例尺(线性、带状、时间、序数)与坐标轴
  • 图形生成器(折线、面积、圆弧、饼图、堆叠)
  • 过渡与动画更新
  • 力导向图布局
  • 地理投影与分级统计图
  • 缩放、刷选和拖拽交互
  • 框架集成:D3 负责计算,框架负责 DOM
  • 带 viewBox 的响应式 SVG
  • D3 的 TypeScript 类型定义

详细资源:
  • examples/core.md - 选择器、数据连接、比例尺、坐标轴、图形、响应式 SVG
  • examples/interaction.md - 过渡动画、缩放、刷选、拖拽、工具提示
  • examples/advanced.md - 力导向布局、地理投影、框架集成模式
  • reference.md - 模块参考、决策框架、反模式

<philosophy>

Philosophy

设计理念

D3 is a low-level visualization grammar, not a charting library. It provides primitives for binding data to DOM elements and applying data-driven transformations. This gives maximum control at the cost of more code than higher-level alternatives.
Core mental model:
  1. Select elements (existing or placeholder)
  2. Bind data to selections with
    .data()
  3. Join to create/update/remove elements with
    .join()
  4. Encode data as visual attributes with scales
  5. Annotate with axes, labels, legends
  6. Animate changes with transitions
D3 v7 key decisions:
  • Pure ES modules — use modular imports for tree-shaking
  • selection.join()
    replaces manual enter/update/exit boilerplate
  • TypeScript types ship with each module (no
    @types/d3
    needed for core packages, but available for convenience)
  • Works with any framework — D3 handles computation, your framework handles DOM rendering
When NOT to use D3 directly:
  • Standard charts with minimal customization (use a charting library)
  • Rapid prototyping where development speed matters more than customization
  • Teams without SVG/visualization experience
</philosophy>
<patterns>
D3 是一个底层可视化语法,而非图表库。它提供了将数据绑定到 DOM 元素并应用数据驱动变换的原语。这带来了最大的控制权,但相比高层级替代方案需要编写更多代码。
核心思维模型:
  1. 选择元素(现有元素或占位符)
  2. 绑定数据到选择器(使用
    .data()
    )
  3. 连接以创建/更新/移除元素(使用
    .join()
    )
  4. 编码数据为视觉属性(使用比例尺)
  5. 标注坐标轴、标签、图例
  6. 动画变化(使用过渡)
D3 v7 关键决策:
  • 纯 ES 模块——使用模块化导入实现摇树优化
  • selection.join()
    替代手动的 enter/update/exit 样板代码
  • TypeScript 类型随每个模块发布(核心包无需
    @types/d3
    ,但为方便仍可使用)
  • 兼容任意框架——D3 处理计算,框架处理 DOM 渲染
何时不应直接使用 D3:
  • 只需少量自定义的标准图表(使用图表库)
  • 开发速度优先于定制化的快速原型开发
  • 团队缺乏 SVG/可视化经验
</philosophy>
<patterns>

Core Patterns

核心模式

Pattern 1: Selections and the Data Join

模式 1:选择器与数据连接

The data join is D3's core pattern: bind an array of data to DOM elements, then use
.join()
to create, update, and remove elements as data changes.
typescript
import { select } from "d3-selection";

const BAR_HEIGHT = 30;
const BAR_GAP = 5;

// Select, bind data, join
select(svgElement)
  .selectAll<SVGRectElement, number>("rect")
  .data(values, (d) => String(d)) // key function for identity
  .join("rect") // enter + update merged
  .attr("y", (_, i) => i * (BAR_HEIGHT + BAR_GAP))
  .attr("width", (d) => xScale(d))
  .attr("height", BAR_HEIGHT);
Why good:
join("rect")
handles enter/update/exit in one call, key function ensures correct element-data binding across updates, typed selection generics
For advanced join with separate enter/update/exit callbacks, see examples/core.md Pattern 1.

数据连接是 D3 的核心模式:将数据数组绑定到 DOM 元素,然后使用
.join()
在数据变化时创建、更新和移除元素。
typescript
import { select } from "d3-selection";

const BAR_HEIGHT = 30;
const BAR_GAP = 5;

// 选择、绑定数据、连接
select(svgElement)
  .selectAll<SVGRectElement, number>("rect")
  .data(values, (d) => String(d)) // 用于标识的键函数
  .join("rect") // 合并 enter + update
  .attr("y", (_, i) => i * (BAR_HEIGHT + BAR_GAP))
  .attr("width", (d) => xScale(d))
  .attr("height", BAR_HEIGHT);
优势:
join("rect")
一次调用处理 enter/update/exit,键函数确保更新时元素与数据的正确绑定,带类型的选择器泛型
如需带单独 enter/update/exit 回调的高级连接,请查看 examples/core.md 模式 1。

Pattern 2: Scales — Mapping Data to Pixels

模式 2:比例尺——数据到像素的映射

Scales are functions that map an input domain (data values) to an output range (pixel positions, colors).
typescript
import { scaleLinear, scaleBand, scaleTime, scaleOrdinal } from "d3-scale";

const CHART_WIDTH = 600;
const CHART_HEIGHT = 400;

// Continuous: numbers -> pixels
const x = scaleLinear<number>()
  .domain([0, max(data, (d) => d.value)!])
  .range([0, CHART_WIDTH]);

// Categorical: strings -> pixel bands (bar charts)
const y = scaleBand<string>()
  .domain(data.map((d) => d.label))
  .range([0, CHART_HEIGHT])
  .padding(0.1);

// Time: Date -> pixels
const timeScale = scaleTime<number>()
  .domain([startDate, endDate])
  .range([0, CHART_WIDTH]);
Why good: TypeScript generics on scales, domain derived from data with
max()
,
.padding()
on band scale for gaps between bars
See examples/core.md Pattern 2 for ordinal color scales and
scaleLog
/
scaleSqrt
patterns.

比例尺是将输入域(数据值)映射到输出范围(像素位置、颜色)的函数。
typescript
import { scaleLinear, scaleBand, scaleTime, scaleOrdinal } from "d3-scale";

const CHART_WIDTH = 600;
const CHART_HEIGHT = 400;

// 连续型:数字 -> 像素
const x = scaleLinear<number>()
  .domain([0, max(data, (d) => d.value)!])
  .range([0, CHART_WIDTH]);

// 分类型:字符串 -> 像素带(柱状图)
const y = scaleBand<string>()
  .domain(data.map((d) => d.label))
  .range([0, CHART_HEIGHT])
  .padding(0.1);

// 时间型:Date -> 像素
const timeScale = scaleTime<number>()
  .domain([startDate, endDate])
  .range([0, CHART_WIDTH]);
优势: 带 TypeScript 泛型的比例尺,从数据推导域(使用
max()
),带
.padding()
的带状比例尺实现柱状图间距
如需序数颜色比例尺和
scaleLog
/
scaleSqrt
模式,请查看 examples/core.md 模式 2。

Pattern 3: Axes — Rendering Scale Tick Marks

模式 3:坐标轴——渲染比例尺刻度

Axes are SVG groups generated from a scale. Render with
selection.call(axis)
.
typescript
import { axisBottom, axisLeft } from "d3-axis";
import { format } from "d3-format";

const TICK_COUNT = 5;

// Create axes from scales
const xAxis = axisBottom(xScale).ticks(TICK_COUNT).tickFormat(format(",.0f"));
const yAxis = axisLeft(yScale);

// Render into <g> containers
svg
  .append("g")
  .attr("transform", `translate(0,${CHART_HEIGHT - MARGIN_BOTTOM})`)
  .call(xAxis);

svg.append("g").attr("transform", `translate(${MARGIN_LEFT},0)`).call(yAxis);
Why good: axes derived from scales (always in sync),
selection.call()
pattern for reusable rendering,
d3-format
for tick label formatting
See examples/core.md Pattern 3 for time axes and grid line patterns.

坐标轴是基于比例尺生成的 SVG 组,使用
selection.call(axis)
渲染。
typescript
import { axisBottom, axisLeft } from "d3-axis";
import { format } from "d3-format";

const TICK_COUNT = 5;

// 从比例尺创建坐标轴
const xAxis = axisBottom(xScale).ticks(TICK_COUNT).tickFormat(format(",.0f"));
const yAxis = axisLeft(yScale);

// 渲染到 <g> 容器中
svg
  .append("g")
  .attr("transform", `translate(0,${CHART_HEIGHT - MARGIN_BOTTOM})`)
  .call(xAxis);

svg.append("g").attr("transform", `translate(${MARGIN_LEFT},0)`).call(yAxis);
优势: 坐标轴基于比例尺生成(始终保持同步),
selection.call()
模式实现可复用渲染,
d3-format
用于刻度标签格式化
如需时间坐标轴和网格线模式,请查看 examples/core.md 模式 3。

Pattern 4: Shape Generators — SVG Paths from Data

模式 4:图形生成器——从数据生成 SVG 路径

Shape generators are functions that take data arrays and produce SVG
d
attribute strings.
typescript
import { line, area, arc, pie, curveMonotoneX } from "d3-shape";
import type { PieArcDatum } from "d3-shape";

// Line generator
const lineGen = line<DataPoint>()
  .x((d) => xScale(d.date))
  .y((d) => yScale(d.value))
  .curve(curveMonotoneX);

svg
  .append("path")
  .datum(data)
  .attr("d", lineGen)
  .attr("fill", "none")
  .attr("stroke", "steelblue");

// Pie + arc generators
const INNER_RADIUS = 0;
const OUTER_RADIUS = 150;

const pieGen = pie<SliceData>().value((d) => d.value);
const arcGen = arc<PieArcDatum<SliceData>>()
  .innerRadius(INNER_RADIUS)
  .outerRadius(OUTER_RADIUS);

svg
  .selectAll("path")
  .data(pieGen(sliceData))
  .join("path")
  .attr("d", arcGen)
  .attr("fill", (d) => colorScale(d.data.label));
Why good: generators configured once then reused,
.curve()
for smooth interpolation, typed
PieArcDatum
generic for pie data, named radius constants
See examples/core.md Pattern 4 for area charts and stacked layouts.

图形生成器是接收数据数组并生成 SVG
d
属性字符串的函数。
typescript
import { line, area, arc, pie, curveMonotoneX } from "d3-shape";
import type { PieArcDatum } from "d3-shape";

// 折线生成器
const lineGen = line<DataPoint>()
  .x((d) => xScale(d.date))
  .y((d) => yScale(d.value))
  .curve(curveMonotoneX);

svg
  .append("path")
  .datum(data)
  .attr("d", lineGen)
  .attr("fill", "none")
  .attr("stroke", "steelblue");

// 饼图 + 圆弧生成器
const INNER_RADIUS = 0;
const OUTER_RADIUS = 150;

const pieGen = pie<SliceData>().value((d) => d.value);
const arcGen = arc<PieArcDatum<SliceData>>()
  .innerRadius(INNER_RADIUS)
  .outerRadius(OUTER_RADIUS);

svg
  .selectAll("path")
  .data(pieGen(sliceData))
  .join("path")
  .attr("d", arcGen)
  .attr("fill", (d) => colorScale(d.data.label));
优势: 生成器配置一次即可复用,
.curve()
实现平滑插值,带类型的
PieArcDatum
泛型用于饼图数据,命名的半径常量
如需面积图和堆叠布局,请查看 examples/core.md 模式 4。

Pattern 5: Responsive SVG with viewBox

模式 5:带 viewBox 的响应式 SVG

Use the
viewBox
attribute so SVG scales to its container without JS resize handlers.
typescript
const VIEWBOX_WIDTH = 960;
const VIEWBOX_HEIGHT = 500;

const svg = select(container)
  .append("svg")
  .attr("viewBox", `0 0 ${VIEWBOX_WIDTH} ${VIEWBOX_HEIGHT}`)
  .attr("preserveAspectRatio", "xMidYMid meet")
  .style("width", "100%")
  .style("height", "auto");
Why good: SVG scales automatically, no resize listeners needed, chart dimensions stay consistent regardless of container size
For dynamic resizing with
ResizeObserver
, see examples/core.md Pattern 5.

使用
viewBox
属性让 SVG 自适应容器大小,无需 JS resize 处理器。
typescript
const VIEWBOX_WIDTH = 960;
const VIEWBOX_HEIGHT = 500;

const svg = select(container)
  .append("svg")
  .attr("viewBox", `0 0 ${VIEWBOX_WIDTH} ${VIEWBOX_HEIGHT}`)
  .attr("preserveAspectRatio", "xMidYMid meet")
  .style("width", "100%")
  .style("height", "auto");
优势: SVG 自动缩放,无需 resize 监听器,无论容器大小如何,图表尺寸保持一致
如需使用
ResizeObserver
实现动态缩放,请查看 examples/core.md 模式 5。

Pattern 6: Transitions — Animated Data Updates

模式 6:过渡动画——数据更新动画

Transitions interpolate attributes and styles over time with easing.
typescript
import { transition } from "d3-transition";
import { easeCubicOut } from "d3-ease";

const TRANSITION_DURATION_MS = 750;
const STAGGER_DELAY_MS = 50;

select(svgElement)
  .selectAll<SVGRectElement, DataPoint>("rect")
  .data(newData, (d) => d.id)
  .join(
    (enter) =>
      enter
        .append("rect")
        .attr("width", 0)
        .call((s) =>
          s
            .transition()
            .duration(TRANSITION_DURATION_MS)
            .attr("width", (d) => xScale(d.value)),
        ),
    (update) =>
      update.call((s) =>
        s
          .transition()
          .duration(TRANSITION_DURATION_MS)
          .attr("width", (d) => xScale(d.value)),
      ),
    (exit) =>
      exit.call((s) =>
        s
          .transition()
          .duration(TRANSITION_DURATION_MS)
          .attr("width", 0)
          .remove(),
      ),
  );
Why good: enter/update/exit each have distinct animated behavior, stagger creates cascading effect, named timing constants
See examples/interaction.md Pattern 1 for easing functions and chained transitions.

过渡动画通过缓动效果随时间插值属性和样式。
typescript
import { transition } from "d3-transition";
import { easeCubicOut } from "d3-ease";

const TRANSITION_DURATION_MS = 750;
const STAGGER_DELAY_MS = 50;

select(svgElement)
  .selectAll<SVGRectElement, DataPoint>("rect")
  .data(newData, (d) => d.id)
  .join(
    (enter) =>
      enter
        .append("rect")
        .attr("width", 0)
        .call((s) =>
          s
            .transition()
            .duration(TRANSITION_DURATION_MS)
            .attr("width", (d) => xScale(d.value)),
        ),
    (update) =>
      update.call((s) =>
        s
          .transition()
          .duration(TRANSITION_DURATION_MS)
          .attr("width", (d) => xScale(d.value)),
      ),
    (exit) =>
      exit.call((s) =>
        s
          .transition()
          .duration(TRANSITION_DURATION_MS)
          .attr("width", 0)
          .remove(),
      ),
  );
优势: enter/update/exit 各有不同的动画行为,交错延迟创建级联效果,命名的时间常量
如需缓动函数和链式过渡,请查看 examples/interaction.md 模式 1。

Pattern 7: Zoom and Pan

模式 7:缩放与平移

Apply zoom behavior with
d3.zoom()
and transform the visualization on zoom events.
typescript
import { zoom, zoomIdentity } from "d3-zoom";
import type { D3ZoomEvent } from "d3-zoom";

const MIN_ZOOM = 0.5;
const MAX_ZOOM = 32;

const zoomBehavior = zoom<SVGSVGElement, unknown>()
  .scaleExtent([MIN_ZOOM, MAX_ZOOM])
  .on("zoom", (event: D3ZoomEvent<SVGSVGElement, unknown>) => {
    chartGroup.attr("transform", event.transform.toString());
  });

svg.call(zoomBehavior);
Why good: typed zoom event and element generics, scale extent prevents over-zoom, transform applied to inner group (not the SVG itself)
See examples/interaction.md Pattern 2 for semantic zoom and programmatic zoom controls.

使用
d3.zoom()
应用缩放行为,并在缩放事件时变换可视化内容。
typescript
import { zoom, zoomIdentity } from "d3-zoom";
import type { D3ZoomEvent } from "d3-zoom";

const MIN_ZOOM = 0.5;
const MAX_ZOOM = 32;

const zoomBehavior = zoom<SVGSVGElement, unknown>()
  .scaleExtent([MIN_ZOOM, MAX_ZOOM])
  .on("zoom", (event: D3ZoomEvent<SVGSVGElement, unknown>) => {
    chartGroup.attr("transform", event.transform.toString());
  });

svg.call(zoomBehavior);
优势: 带类型的缩放事件和元素泛型,缩放范围限制防止过度缩放,变换应用到内部组(而非 SVG 本身)
如需语义缩放和程序化缩放控制,请查看 examples/interaction.md 模式 2。

Pattern 8: Force-Directed Graph Layout

模式 8:力导向图布局

Force simulations position nodes using physics-based forces (repulsion, attraction, centering).
typescript
import {
  forceSimulation,
  forceLink,
  forceManyBody,
  forceCenter,
  forceCollide,
} from "d3-force";

const CHARGE_STRENGTH = -300;
const COLLISION_RADIUS = 5;

const simulation = forceSimulation(nodes)
  .force(
    "link",
    forceLink(links)
      .id((d: NodeDatum) => d.id)
      .distance(100),
  )
  .force("charge", forceManyBody().strength(CHARGE_STRENGTH))
  .force("center", forceCenter(width / 2, height / 2))
  .force("collide", forceCollide(COLLISION_RADIUS))
  .on("tick", () => {
    // Update node and link positions from simulation
  });
Why good: named force constants,
.id()
accessor for node identity,
.distance()
for link length, collision prevents overlap
See examples/advanced.md Pattern 1 for complete graph rendering with drag interaction.

力导向模拟使用基于物理的力(排斥、吸引、居中)定位节点。
typescript
import {
  forceSimulation,
  forceLink,
  forceManyBody,
  forceCenter,
  forceCollide,
} from "d3-force";

const CHARGE_STRENGTH = -300;
const COLLISION_RADIUS = 5;

const simulation = forceSimulation(nodes)
  .force(
    "link",
    forceLink(links)
      .id((d: NodeDatum) => d.id)
      .distance(100),
  )
  .force("charge", forceManyBody().strength(CHARGE_STRENGTH))
  .force("center", forceCenter(width / 2, height / 2))
  .force("collide", forceCollide(COLLISION_RADIUS))
  .on("tick", () => {
    // 从模拟更新节点和链接位置
  });
优势: 命名的力常量,
.id()
访问器用于节点标识,
.distance()
控制链接长度,碰撞力防止重叠
如需带拖拽交互的完整图渲染,请查看 examples/advanced.md 模式 1。

Pattern 9: Geographic Projections

模式 9:地理投影

Project geographic coordinates onto a 2D plane and render GeoJSON features as SVG paths.
typescript
import { geoMercator, geoPath, geoNaturalEarth1 } from "d3-geo";
import type { GeoPermissibleObjects } from "d3-geo";

const projection = geoNaturalEarth1().fitSize(
  [CHART_WIDTH, CHART_HEIGHT],
  geoJsonData,
);

const pathGenerator = geoPath().projection(projection);

svg
  .selectAll("path")
  .data(geoJsonData.features)
  .join("path")
  .attr("d", pathGenerator)
  .attr("fill", (d) => colorScale(dataByRegion.get(d.properties.id) ?? 0));
Why good:
fitSize
auto-scales projection to container, path generator produces
d
strings from GeoJSON, color encodes data values per region
See examples/advanced.md Pattern 2 for choropleth maps and interactive tooltips.

将地理坐标投影到 2D 平面,并将 GeoJSON 要素渲染为 SVG 路径。
typescript
import { geoMercator, geoPath, geoNaturalEarth1 } from "d3-geo";
import type { GeoPermissibleObjects } from "d3-geo";

const projection = geoNaturalEarth1().fitSize(
  [CHART_WIDTH, CHART_HEIGHT],
  geoJsonData,
);

const pathGenerator = geoPath().projection(projection);

svg
  .selectAll("path")
  .data(geoJsonData.features)
  .join("path")
  .attr("d", pathGenerator)
  .attr("fill", (d) => colorScale(dataByRegion.get(d.properties.id) ?? 0));
优势:
fitSize
自动缩放投影以适配容器,路径生成器从 GeoJSON 生成
d
字符串,颜色编码各区域的数据值
如需分级统计图和交互式工具提示,请查看 examples/advanced.md 模式 2。

Pattern 10: Framework Integration

模式 10:框架集成

When using D3 with a component framework, split responsibilities: D3 computes layouts, scales, and shapes; your framework renders the DOM.
typescript
// Pattern: D3 for computation, framework for rendering
// In your component:

const xScale = scaleLinear().domain([0, maxValue]).range([0, width]);
const yScale = scaleBand().domain(labels).range([0, height]).padding(0.1);
const linePath = line<DataPoint>()
  .x((d) => xScale(d.x))
  .y((d) => yScale(d.y)!)(data);

// Your framework renders SVG with computed values
// <svg><path d={linePath} /><rect width={xScale(d.value)} /></svg>
When D3 must own the DOM (zoom, brush, drag, force tick): use a ref to an SVG element and call D3 in a lifecycle hook. Clean up the simulation/behavior on unmount.
See examples/advanced.md Pattern 3 for the ref-based integration pattern and cleanup.
</patterns>
<decision_framework>
在组件框架中使用 D3 时,拆分职责:D3 计算布局、比例尺和图形;框架负责渲染 DOM。
typescript
// 模式:D3 负责计算,框架负责渲染
// 在组件中:

const xScale = scaleLinear().domain([0, maxValue]).range([0, width]);
const yScale = scaleBand().domain(labels).range([0, height]).padding(0.1);
const linePath = line<DataPoint>()
  .x((d) => xScale(d.x))
  .y((d) => yScale(d.y)!)(data);

// 框架使用计算后的值渲染 SVG
// <svg><path d={linePath} /><rect width={xScale(d.value)} /></svg>
当 D3 必须控制 DOM 时(缩放、刷选、拖拽、力导向 tick):使用 ref 获取 SVG 元素,并在生命周期钩子中调用 D3。在卸载时清理模拟/行为。
如需基于 ref 的集成模式和清理方法,请查看 examples/advanced.md 模式 3。
</patterns>
<decision_framework>

Decision Framework

决策框架

D3 Module Selection

D3 模块选择

What are you building?
|
+-> Bar/line/area chart?
|   -> d3-selection, d3-scale, d3-axis, d3-shape, d3-array
|
+-> Pie/donut chart?
|   -> d3-shape (pie + arc generators), d3-scale (color)
|
+-> Force-directed graph?
|   -> d3-force, d3-selection, d3-drag
|
+-> Geographic map?
|   -> d3-geo, d3-selection, d3-scale (color)
|
+-> Animated transitions?
|   -> d3-transition, d3-ease, d3-interpolate
|
+-> Interactive (zoom/brush)?
    -> d3-zoom or d3-brush, d3-selection
你要构建什么?
|
+-> 柱状图/折线图/面积图?
|   -> d3-selection, d3-scale, d3-axis, d3-shape, d3-array
|
+-> 饼图/环形图?
|   -> d3-shape(pie + arc 生成器), d3-scale(颜色)
|
+-> 力导向图?
|   -> d3-force, d3-selection, d3-drag
|
+-> 地理地图?
|   -> d3-geo, d3-selection, d3-scale(颜色)
|
+-> 过渡动画?
|   -> d3-transition, d3-ease, d3-interpolate
|
+-> 交互式(缩放/刷选)?
    -> d3-zoom 或 d3-brush, d3-selection

Framework Integration Strategy

框架集成策略

Does the visualization need zoom, brush, drag, or force tick?
|
+-> NO -> D3 for computation only (scales, shapes, layouts)
|         Your framework renders SVG/HTML directly
|         Cleanest integration, fully declarative
|
+-> YES -> D3 owns the SVG via a ref element
           Call D3 in a lifecycle hook (mount/update)
           Clean up behaviors on unmount
可视化需要缩放、刷选、拖拽或力导向 tick 吗?
|
+-> 不需要 -> D3 仅负责计算(比例尺、图形、布局)
|         框架直接渲染 SVG/HTML
|         最简洁的集成方式,完全声明式
|
+-> 需要 -> D3 通过 ref 元素控制 SVG
           在生命周期钩子(挂载/更新)中调用 D3
           在卸载时清理行为

Scale Selection

比例尺选择

Data TypeScaleExample
ContinuousscaleLinearRevenue, temperature
CategoricalscaleBandCategories on bar chart
Categorical dotsscalePointCategories without width
Time seriesscaleTimeDates on x-axis
LogarithmicscaleLogExponential data ranges
Square rootscaleSqrtBubble chart radius
Color categoriesscaleOrdinalCategory -> color
</decision_framework>

<red_flags>
数据类型比例尺示例
连续型scaleLinear收入、温度
分类型scaleBand柱状图上的分类
分类点scalePoint无宽度的分类
时间序列scaleTimeX 轴上的日期
对数型scaleLog指数数据范围
平方根型scaleSqrt气泡图半径
颜色分类scaleOrdinal分类 -> 颜色
</decision_framework>

<red_flags>

RED FLAGS

警示事项

High Priority Issues:
  • Using
    import * as d3 from "d3"
    in production — imports the entire 240KB+ bundle. Use modular imports:
    import { select } from "d3-selection"
  • Manual enter/append/merge/exit chains — use
    selection.join()
    instead (simpler, fewer bugs)
  • Animating non-interpolable attributes (class names, boolean attributes) — only animate numeric attributes and colors
  • Missing key function in
    .data(array, key)
    when data identity matters — causes incorrect element-data binding on updates
  • Mutating data arrays bound to selections — D3 stores references; mutations cause stale renders
Medium Priority Issues:
  • Magic numbers for margins, radii, durations, colors — use named constants
  • Creating a new SVG on every data update — select the existing SVG, update data bindings
  • Appending axes on every update (duplicated tick marks) — select existing
    <g>
    and
    .call(axis)
    again, or use
    .join()
    pattern
  • Forgetting
    transition.remove()
    on exit — exiting elements stay in the DOM invisible
  • Not using
    .nice()
    on linear scales — domain ends at awkward values like
    [0, 473]
Gotchas & Edge Cases:
  • selection.join()
    returns the merged enter+update selection — chained attributes apply to both new and existing elements
  • scaleBand().bandwidth()
    returns the computed bar width — use it for rect width, not a hardcoded value
  • d3.max()
    returns
    undefined
    for empty arrays — guard with
    ?? 0
    or check array length first
  • transition.duration()
    is per-element, not total — a 750ms transition on 100 elements still takes 750ms (not 75,000ms)
  • forceSimulation
    runs asynchronously via
    requestAnimationFrame
    — stop it on unmount to prevent memory leaks
  • geoPath
    without a projection renders pre-projected coordinates — only omit projection if GeoJSON is already projected
  • Zoom transform applied to the SVG root clips panned content — apply transform to an inner
    <g>
    group instead
  • d3-transition
    must be imported for
    selection.transition()
    to exist — it extends the selection prototype via side effect
  • .datum()
    binds data to a single element without computing a join — use
    .data()
    for arrays,
    .datum()
    for single objects
</red_flags>

<critical_reminders>
高优先级问题:
  • 生产环境中使用
    import * as d3 from "d3"
    ——会导入整个 240KB+ 的包。使用模块化导入:
    import { select } from "d3-selection"
  • 手动的 enter/append/merge/exit 链式调用——改用
    selection.join()
    (更简单,更少 bug)
  • 对不可插值的属性(类名、布尔属性)执行动画——仅对数值属性和颜色执行动画
  • 数据标识重要时,
    .data(array, key)
    中缺少键函数——更新时会导致元素与数据绑定错误
  • 绑定到选择器的数据数组被修改——D3 存储引用;修改会导致渲染过期
中优先级问题:
  • 边距、半径、时长、颜色使用魔法数字——使用命名常量
  • 每次数据更新都创建新的 SVG——选择现有 SVG,更新数据绑定
  • 每次更新都添加坐标轴(重复刻度)——选择现有的
    <g>
    并再次
    .call(axis)
    ,或使用
    .join()
    模式
  • 退出时忘记
    transition.remove()
    ——退出的元素仍会留在 DOM 中不可见
  • 线性比例尺未使用
    .nice()
    ——域会以尴尬的值结束,如
    [0, 473]
陷阱与边缘情况:
  • selection.join()
    返回合并后的 enter+update 选择器——链式属性会应用于新元素和现有元素
  • scaleBand().bandwidth()
    返回计算后的柱状宽度——使用它设置矩形宽度,而非硬编码值
  • d3.max()
    对空数组返回
    undefined
    ——使用
    ?? 0
    或先检查数组长度
  • transition.duration()
    是每个元素的时长,而非总时长——100 个元素的 750ms 过渡仍只需 750ms(不是 75,000ms)
  • forceSimulation
    通过
    requestAnimationFrame
    异步运行——卸载时停止它以防止内存泄漏
  • 不带投影的
    geoPath
    会渲染预投影坐标——仅当 GeoJSON 已投影时才省略投影
  • 缩放变换应用到 SVG 根元素会裁剪平移内容——改为应用到内部
    <g>
    组
  • 必须导入
    d3-transition
    才能使用
    selection.transition()
    ——它通过副作用扩展选择器原型
  • .datum()
    将数据绑定到单个元素,不计算连接——数组使用
    .data()
    ,单个对象使用
    .datum()
</red_flags>

<critical_reminders>

CRITICAL REMINDERS

重要提醒

All code must follow project conventions in CLAUDE.md
(You MUST use
selection.join()
for data joins — NOT manual enter/append/merge/exit chains)
(You MUST use modular imports (
d3-selection
,
d3-scale
,
d3-shape
) — NOT
import * as d3 from "d3"
in production bundles)
(You MUST use named constants for ALL visual dimensions, colors, and timing values — NO magic numbers)
(You MUST type D3 selections and scales with TypeScript generics —
Selection<SVGGElement, Datum, ...>
,
ScaleLinear<number, number>
)
Failure to follow these rules will cause bloated bundles, incorrect data binding, and untyped visualization code.
</critical_reminders>
所有代码必须遵循 CLAUDE.md 中的项目规范
(必须使用
selection.join()
进行数据连接——禁止手动的 enter/append/merge/exit 链式调用)
(必须使用模块化导入(
d3-selection
、
d3-scale
、
d3-shape
)——生产环境打包中禁止使用
import * as d3 from "d3"
)
(所有视觉尺寸、颜色和时间值必须使用命名常量——禁止魔法数字)
(必须使用 TypeScript 泛型为 D3 选择器和比例尺添加类型——
Selection<SVGGElement, Datum, ...>
、
ScaleLinear<number, number>
)
不遵循这些规则会导致包体积过大、数据绑定错误和无类型的可视化代码。
</critical_reminders>