{T}

形状生成器

D3 的形状生成器(Shape Generators)将数据转换为 SVG 路径字符串(d 属性),是绘制折线、面积、弧线、饼图等复杂图形的核心工具。

1. 生成器体系

图表渲染中…

2. 折线生成器 d3.line()

2.1 基础用法

javascript
import { line, scaleLinear, scaleTime } from 'd3'

const data = [
  { date: new Date(2024, 0, 1), value: 30 },
  { date: new Date(2024, 1, 1), value: 45 },
  { date: new Date(2024, 2, 1), value: 38 },
  { date: new Date(2024, 3, 1), value: 62 }
]

const xScale = scaleTime()
  .domain([data[0].date, data[data.length - 1].date])
  .range([0, 600])

const yScale = scaleLinear()
  .domain([0, 70])
  .range([300, 0])

// 创建折线生成器
const lineGenerator = line()
  .x(d => xScale(d.date))   // x 访问器
  .y(d => yScale(d.value))  // y 访问器

// 生成路径字符串
const pathD = lineGenerator(data)
// → "M0,171.4L200,107.1L400,137.1L600,34.3"

// 渲染
svg.append('path')
  .datum(data)
  .attr('d', lineGenerator)
  .attr('fill', 'none')
  .attr('stroke', 'steelblue')
  .attr('stroke-width', 2)

2.2 曲线插值

javascript
import { line, curveBasis, curveCardinal, curveMonotoneX, curveStep } from 'd3'

const lineGen = line()
  .x(d => x(d.date))
  .y(d => y(d.value))
  .curve(curveMonotoneX)  // 单调插值(推荐,不会过冲)
曲线类型特点适用场景
curveLinear直线连接(默认)精确数据展示
curveMonotoneX单调平滑,不过冲时间序列(推荐)
curveBasisB 样条,极度平滑趋势展示
curveCardinal可调张力平滑通用平滑
curveStep阶梯状离散状态变化
curveNatural自然样条数学可视化

2.3 处理缺失数据

javascript
const lineGen = line()
  .x(d => x(d.date))
  .y(d => y(d.value))
  .defined(d => d.value !== null)  // null 值处断开线条

// 数据中有 null 时,线条会在该处断开
const data = [
  { date: d1, value: 30 },
  { date: d2, value: null },  // 断开
  { date: d3, value: 45 }
]

3. 面积生成器 d3.area()

javascript
import { area, curveMonotoneX } from 'd3'

const areaGenerator = area()
  .x(d => xScale(d.date))
  .y0(height)              // 基线(底部)
  .y1(d => yScale(d.value)) // 顶线
  .curve(curveMonotoneX)

svg.append('path')
  .datum(data)
  .attr('d', areaGenerator)
  .attr('fill', 'steelblue')
  .attr('fill-opacity', 0.3)
  .attr('stroke', 'steelblue')

3.1 堆叠面积图

javascript
import { stack, stackOrderNone, stackOffsetNone, area } from 'd3'

const stackGenerator = stack()
  .keys(['desktop', 'mobile', 'tablet'])
  .order(stackOrderNone)
  .offset(stackOffsetNone)

const stackedData = stackGenerator(data)
// → [[{0,1,data}, ...], [{0,1,data}, ...], ...]

const areaGen = area()
  .x(d => xScale(d.data.date))
  .y0(d => yScale(d[0]))  // 下边界
  .y1(d => yScale(d[1]))  // 上边界

svg.selectAll('.layer')
  .data(stackedData)
  .join('path')
  .attr('class', 'layer')
  .attr('d', areaGen)
  .attr('fill', (d, i) => colorScale(i))

4. 弧与饼图

4.1 d3.arc() 弧生成器

javascript
import { arc } from 'd3'

// 环形图的一段弧
const arcGenerator = arc()
  .innerRadius(60)   // 内半径(0 = 实心扇形)
  .outerRadius(100)  // 外半径
  .startAngle(0)     // 起始角度(弧度)
  .endAngle(Math.PI / 2)  // 结束角度

const pathD = arcGenerator()
// → "M0,-100A100,100,0,0,1,100,0L60,0A60,60,0,0,0,0,-60Z"

4.2 d3.pie() 饼图布局

javascript
import { pie, arc } from 'd3'

const data = [
  { name: 'React', value: 40 },
  { name: 'Vue', value: 30 },
  { name: 'Angular', value: 20 },
  { name: 'Others', value: 10 }
]

// pie() 计算每个扇区的角度
const pieGenerator = pie()
  .value(d => d.value)
  .sort(null)  // 不排序,保持原始顺序
  .padAngle(0.02)  // 扇区间隙

const arcs = pieGenerator(data)
// → [{ startAngle, endAngle, value, data }, ...]

// arc() 将角度转为路径
const arcGenerator = arc()
  .innerRadius(0)
  .outerRadius(120)

// 渲染
const g = svg.append('g')
  .attr('transform', 'translate(200, 200)')

g.selectAll('path')
  .data(arcs)
  .join('path')
  .attr('d', arcGenerator)
  .attr('fill', (d, i) => colorScale(i))
  .attr('stroke', '#fff')
  .attr('stroke-width', 2)

// 标签
const labelArc = arc().innerRadius(80).outerRadius(80)
g.selectAll('text')
  .data(arcs)
  .join('text')
  .attr('transform', d => `translate(${labelArc.centroid(d)})`)
  .attr('text-anchor', 'middle')
  .text(d => d.data.name)

4.3 环形图(Donut)

javascript
const arcGenerator = arc()
  .innerRadius(70)   // 内半径 > 0 = 环形
  .outerRadius(120)
  .cornerRadius(4)   // 圆角弧

// 悬停放大效果
const arcHover = arc()
  .innerRadius(70)
  .outerRadius(130)  // 悬停时外半径增大
  .cornerRadius(4)

5. 符号生成器 d3.symbol()

javascript
import { symbol, symbolCircle, symbolTriangle, symbolStar, symbolCross } from 'd3'

const symbolGenerator = symbol()
  .type(symbolTriangle)
  .size(64)  // 面积(平方像素)

svg.selectAll('path')
  .data(data)
  .join('path')
  .attr('d', symbolGenerator)
  .attr('transform', d => `translate(${x(d.x)},${y(d.y)})`)
  .attr('fill', d => colorScale(d.category))
符号常量形状
圆形symbolCircle
三角形symbolTriangle
星形symbolStar
十字symbolCross
菱形symbolDiamond
方形symbolSquare

6. 最佳实践

生成器是纯函数

D3 的形状生成器不操作 DOM,只返回路径字符串。你负责将字符串设置到 <path d="..."> 上。这种分离使生成器可测试、可复用。

时间序列优先使用 curveMonotoneX

curveBasis 虽然平滑但会"吃掉"极值点,curveMonotoneX 保证曲线经过每个数据点且不过冲。

饼图排序

pie().sort(null) 保持数据原始顺序。默认排序会按值从大到小排列,可能不符合业务预期。