D3 简介

比例尺

理解 domain 和 range 的概念,掌握 scaleLinear、scaleBand 和 scaleTime 三种常用比例尺。

🎯 引言

前面的例子里,柱子高度直接等于数据:数据是 120,柱子就画 120px。但如果数据是 12000 的销量呢?画 12000px 高的柱子,屏幕根本放不下。学会这篇文章,你能掌握 D3 的比例尺——一个把“数据值”自动换算成“像素值”的换算器。


🧱 比例尺是什么

比例尺(scale)就是一个函数:喂给它一个数据,它返回一个像素值。

这跟我们生活中的地图一个道理:地图上 1 厘米代表实际 10 公里。你不用背着卷尺去量地球,查比例换算一下就能在纸上画地图。

创建一个比例尺只需要告诉它两个范围:

const yScale = d3
    .scaleLinear()
    .domain([0, 10000]) // 数据的范围:0 到 10000
    .range([300, 0]); // 像素的范围:300 到 0
  • domain数据的取值范围(输入)。
  • range像素的对应范围(输出)。

创建好之后它就是普通函数,调用就能看到换算结果:

调用含义结果
yScale(0)最小数据300(画布底部)
yScale(5000)一半的数据150(画布中间)
yScale(10000)最大数据0(画布顶部)

注意看:数据 50000~10000 的一半,像素 150 也正好是 300~0 的一半——等比例换算,这就是 scaleLinear(线性比例尺)名字里“线性”的意思。

拖动滑块改变数据值,红色横线左端指着数据尺上的输入,右端指着像素尺上的输出——yScale 干的就是这个换算:

yScale(5000) = 150
注意 range 写成了 [300, 0],是反着的。因为 SVG 的 y 轴向下(顶部是 0、底部是 300),数据越大柱子应该越靠上,所以大数值要映射成小像素。如果写成 [0, 300],图表就上下颠倒了,这是初学者最常犯的错。

🧱 scaleLinear:让 D3 自己算范围

实际项目中数据范围不固定,写死 [0, 10000] 不合适。让 d3.max 帮我们算出最大值:

const data = [120, 200, 150, 80, 70];

const yScale = d3
    .scaleLinear()
    .domain([0, d3.max(data)]) // d3.max 算出 200
    .range([300, 0])
    .nice();

两个小帮手:

  • d3.max(data):返回数组最大值,类似的还有 d3.min
  • .nice():把范围边界“取整”。比如最大值是 198 时,domain 会变成 [0, 200],坐标轴刻度就是 0、50、100 这样整齐的数字,而不是 0、39.6、79.2。

🧱 scaleBand:把宽度平分给分类

柱状图的横轴是“周一、周二”这类分类,不是数字,没法做线性换算。这时用 scaleBand,它的工作很简单:把总宽度平均分给每个分类

const xScale = d3
    .scaleBand()
    .domain(['周一', '周二', '周三', '周四', '周五'])
    .range([0, 260])
    .padding(0.2); // 柱子之间留 20% 的间隙

260px 分给 5 个分类,每个分类先分到一个 50px 的“格子”;padding(0.2) 表示格子里留 20% 做间隙,剩下 40px 才是柱子的宽度:

调用结果含义
xScale('周一')10“周一”柱子的起始位置
xScale('周三')110“周三”柱子的起始位置
xScale.bandwidth()40每个柱子的宽度

以前我们要手写 i * 60 算位置、手写 40 当宽度,现在 xScale(d.day)xScale.bandwidth() 全包了,改数据、改宽度都不用自己重新算。

拖动滑块调整 padding,直观看看“格子”(虚线框)和“柱子宽度”(实心)的变化:

padding = 0.20step ≈ 50,bandwidth ≈ 40,间隙 ≈ 10

🧱 scaleTime:处理日期

横轴是日期时用 scaleTimedomain 直接传 Date 对象:

const xScale = d3
    .scaleTime()
    .domain([new Date('2026-01-01'), new Date('2026-01-07')])
    .range([0, 300]);

xScale(new Date('2026-01-04')); // 150,1 月 4 日正好在中间

时间比例尺最常配合坐标轴使用,D3 会自动按“天”生成合适的刻度:


⚖️ 三种比例尺怎么选

比例尺domain 类型典型用途
scaleLinear连续数值y 轴(数值轴)
scaleBand离散分类柱状图的 x 轴
scaleTime日期对象时间序列图表的横轴

记忆技巧:数值用 Linear,分类用 Band,时间用 Time


🧾 小节总结

  • 比例尺是“数据值 → 像素值”的换算函数,domain 定输入范围,range 定输出范围。
  • SVG 的 y 轴向下,y 方向比例尺的 range 要反着写 [height, 0]
  • scaleLineard3.max 自动算范围,配 .nice() 让刻度取整。
  • scaleBand 把宽度平分给分类,xScale('周一') 拿位置、bandwidth() 拿柱子宽度。
  • scaleTime 处理日期,domain 直接传 Date 对象。

❓ 知识问答

Q1:range 写反了会怎样?

比例尺照样工作,但映射关系会反过来,数值越大柱子越矮。发现图表“上下颠倒”时,先检查 range 是不是该写成 [height, 0]

Q2:scaleBand 的 padding 取值范围是多少?

0 到 1 之间的小数,表示间隙占每个格子的比例。常用 0.1 到 0.3,太大会让柱子变得又细又挤。

Q3:数据是对象数组时,d3.max 怎么用?

传第二个参数告诉它取哪个字段:d3.max(data, d => d.value)

Q4:比例尺能用来映射颜色吗?

可以,把 range 写成颜色数组即可,如 .range(['#e0f3ff', '#08589e']),数值越大颜色越深,常用于热力图。


🧪 小练习

给下面的数据创建两个比例尺,并在控制台验证换算结果:

  1. scaleBand 创建 x 轴比例尺,domain 为五个科目,range 为 [0, 400]
  2. scaleLinear 创建 y 轴比例尺,domain 为 0 到最高成绩,range 为 [300, 0]
  3. 打印 xScale('数学')xScale.bandwidth()yScale(88),观察输出是否符合预期。
const data = [
    { subject: '语文', score: 80 },
    { subject: '数学', score: 95 },
    { subject: '英语', score: 88 },
    { subject: '物理', score: 72 },
    { subject: '化学', score: 90 },
];

// 请在这里编写代码

🎉 恭喜你已经掌握比例尺这个核心零件!下一篇我们把它和坐标轴组合起来,画出一个带刻度的完整柱状图。