颜色 — 从色彩空间到配色方案
颜色 — 从色彩空间到配色方案
这一章讲什么: 颜色在图里不是装饰,是编码信息的通道:区分类别、表达强弱、标出极值。 本章三层讲透 D3 的颜色工具:单个颜色怎么表示和操作(色彩空间)、两个颜色之间怎么过渡(插值器)、 成组的颜色去哪拿(配色方案)。配色的理论依据(色盲、灰度打印)借 Color Brewer 的现成成果, D3 的方案几乎全是它的移植1。
1. 顶层全景:三层结构
③ 方案层:d3-scale-chromatic
scheme*(离散数组)+ interpolate*(连续函数)
└ 按数据类型分:类别 categorical / 顺序 sequential / 发散 diverging
② 插值层:d3-interpolate
interpolateHsl/Lab/Rgb/Cubehelix… + Long 版 + gamma 校正
└ 吃两个颜色,吐一个 0–1 → 颜色字符串的函数
① 对象层:d3-color
d3.rgb / hsl / lab / gray / hcl(lch) / cubehelix
└ 通道级读写 + formatHex/brighter/darker
图说:底层操作单个颜色,中层生成颜色之间的路径,顶层给成组的颜色。
可视化代码里 90% 只用到第 ③ 层。2. 颜色在网页里怎么写
SVG/CSS 认的颜色写法,按 CSS Color Level 4 规格2:
| 写法 | 例子 |
|---|---|
| 颜色名 | orangered、purple、transparent |
| 十六进制 | #ff093c(6 位)、#f00(3 位)、#ff00007f(8 位,末两位是透明度) |
| 颜色空间函数 | rgb(r,g,b)、hsl(h,s,l)、hwb()、lab()、lch()(可带第 4 个透明度参数) |
| 系统色 | LinkText、ActiveText(UI 组件默认色) |
currentColor | 取当前元素 color 属性的值——第 08 章给轴一键变色就是它 |
一个坑原书用「Important note」框标出:d3-color 3.1(D3 v7 时代)并不支持完整的
Level 4 函数写法——颜色空间函数只认 CSS 3 的 rgb()/rgba()/hsl()/hsla(),
必须逗号分隔、透明度必须是 0–1 的小数3。
3. 色彩空间:颜色的五种坐标系
色彩空间(color space)是用几个通道(数)表示颜色的数学模型。 同一个颜色在不同空间坐标不同,而不同空间让不同的调整变容易4:
| 空间(D3 函数) | 通道 | 调什么最顺手 | 通道范围 |
|---|---|---|---|
RGB d3.rgb(r,g,b) | 红、绿、蓝 | 屏幕显示(像素本位) | 各 0–255 |
HSL d3.hsl(h,s,l) | 色相、饱和度、明度 | 换颜色种类(h 是角度,循环) | h 0–359;s,l 0–1 |
CIELAB d3.lab(l,a,b) | 亮度、绿←→红、蓝←→黄 | 感知均匀:数值差 = 视觉差 | l 0–100;a,b −100–100 |
HCL/LCH d3.hcl()/d3.lch() | 色相、彩度、亮度 | 感知均匀 + 单通道换色相 | h 0–360;c,l 0–100 |
Cubehelix d3.cubehelix(h,s,l) | 色相、饱和、亮度 | 天文图像;降灰度不失信息 | h 0–360;s,l 0–100 |
各空间的脾气,原书的 console 实验给了实感5:
d3.color("#ff0000f7") // {r:255, g:0, b:0, opacity:0.969}
d3.color("hsla(16,50%,50%,.5)") // {h:16, s:0.5, l:0.5, opacity:0.5}
// ↑ d3.color 返回哪种对象,取决于你喂的字符串是哪种写法
color = d3.hsl(color); color.h += 180
// ↑ 色相加 180° → 补色(HSL 的色相是循环的,360 = 0)
d3.gray(80) // = d3.lab(80, 0, 0) —— 零彩度的灰颜色对象的方法(各空间通用)6:
| 方法 | 返回 |
|---|---|
.rgb() / .formatRgb() / .toString() | RGB 对象 / rgb(255,0,0) 字符串 |
.formatHsl() | hsl(0,100%,50%) 字符串 |
.formatHex() / .formatHex8() | #ff0000 / #ff0000ff |
.brighter(k) / .darker(k) | 更亮/更暗的副本(k 默认 1,可传 0–1) |
brighter/darker 的行为随空间而异:RGB 对象三个通道一起动;
LAB/HCL 这类有亮度通道的空间只动亮度7:
d3.rgb(128,90,50).darker() // {r:89.6, g:63, b:35} —— 三个通道全变
d3.lab("red").brighter() // {l:72.3, a:80.8, b:69.9} —— 只有 l 变,a/b 不动
.darker(0.5) // 半档效果,可链式叠加默认包里还有 d3-cam16、d3-cam02、d3-hsv、d3-hcg 等扩展空间,要单独引8。
4. 插值器:颜色之间的路径
d3.interpolateHsl(a, b) 这类工厂吃两个颜色字符串,返回插值函数;
函数吃 0–1,吐颜色字符串(不是对象,要操作就再包一层 d3.color())9:
const rednavy = d3.interpolateHsl("red", "navy");
rednavy(0.5) // 'rgb(192, 0, 192)' —— 短路径:红→紫→藏青
const longrednavy = d3.interpolateHslLong("red", "navy");
longrednavy(0.5) // 'rgb(0, 192, 0)' —— 长路径:绕光谱另一圈,经绿
图说:HSL/LCH/cubehelix 的色相是循环的,两个颜色之间有两条路——
短路径默认,Long 后缀走对面。红→藏青的短路径过紫色,长路径过绿色。全家桶一览10:
| 工厂 | 空间/特点 |
|---|---|
interpolateRgb | RGB 空间 |
interpolateHsl / Lab / Hcl / Cubehelix | 各空间,色相短路径 |
…Long 三件套 | HSL/HCL/Cubehelix 的长路径版 |
interpolateHue(a1, a2) | 只插色相角度,配合 d3.hsl(hue,1,0.5) 造色 |
interpolateRgbBasis(颜色数组) | B 样条穿过全部颜色(不止两端) |
interpolateRgbBasisClosed | 首尾再闭合一圈 → 循环配色 |
插值器有两个高级旋钮:
- gamma 校正:
d3.interpolateRgb.gamma(1.8)("red","blue")—— 人眼对暗部变化更敏感,gamma(典型 1.8–2.5)让中点更亮;gamma 1.8时中点从rgb(128,0,128)变rgb(174,0,174)11。 - 接到比例尺上:连续尺的
.interpolate(工厂)与顺序/发散尺的构造参数, 决定了渐变在哪个色彩空间里发生——同一个红到蓝的 range,RGB 空间会过灰紫, HSL 空间过紫,Lab 空间感知更均匀12。
5. 配色方案:别自己调,拿现成的
**方案(scheme)**是按用途排好的颜色组合。选型的第一性问题:数据是哪种?13
| 数据类型 | 视觉任务 | 用哪类方案 |
|---|---|---|
| 类别(互不相干) | 只求分得开 | categorical:等亮度饱和度的 8–12 色 |
| 顺序(有大有小) | 表达强弱 | sequential:同一色相由浅入深 |
| 发散(两极+中点) | 表达偏离 | diverging:两端撞色、中间亮 |
5.1 离散方案:scheme* 数组
类别(10 套,8–12 色):d3.schemeCategory10、d3.schemeTableau10、
d3.schemeAccent…… 配 scaleOrdinal 是最常见用法——超出色数自动循环14:
const getColor = d3.scaleOrdinal(d3.schemeCategory10);
getColor(25) // '#1f77b4' —— 序号、id、字符串都行
getColor("Dinosaur") // '#d62728' —— 同一输入永远同色顺序(18 套):每套是数组的数组——按需要的色数取子调色板
(d3.schemeReds[3] 是 3 色,[9] 是 9 色)。配序数尺会出现「第 4 个类别循环回第 1 色」的隐患,
原书给的解法:配切桶尺(quantize/threshold)让颜色按数值区间分配15:
const colors = d3.scaleThreshold(d3.schemeYlOrRd[4]).domain([4,8,12]);
colors(0) // '#ffffb2' —— <4
colors(4) // '#fecc5c' —— 4–8
colors(13) // '#e31a1c' —— ≥12
图说:threshold 的 domain 是切点,n 个切点配 n+1 个颜色——数值直接决定颜色档位。发散(9 套):d3.schemeSpectral[3] → ['#fc8d59','#ffffbf','#99d594']——
橙-米-绿,两端饱和、中间亮16。
5.2 插值方案:interpolate* 函数
离散方案的连续版:函数形式,domain [0,1],想要多少种颜色就有多少种,永不重复。 用法就是第 09 章的插值比例尺17:
const scale = d3.scaleSequential(d3.interpolateReds).domain([0,600]);
d3.interpolateRdYlGn(0) // 深红
d3.interpolateRdYlGn(0.5) // 浅黄 —— 中点
d3.interpolateRdYlGn(1) // 深绿
图说:发散插值器两端深、中间亮;domain 三段 [负极, 中点, 正极] 决定中点落在哪个数。除了离散方案的连续版,还有一批为感知与无障碍专门设计的方案18:
| 方案 | 来历 |
|---|---|
| Viridis / Inferno / Magma / Plasma | matplotlib 的标准色带(Van der Walt & Smith 设计) |
| Cividis | 为色觉缺陷者优化,信息损失最小 |
| Turbo | Google 的 Anton Mikhailov 设计,替代彩虹 |
| Warm / Cool | Matteo Niccoli 的双段彩虹 |
| Cubehelix | 天文图像出身,降灰度保信息 |
以及循环方案(interpolateRainbow 等):首尾同色、中间过光谱——
经度、周期、闭合路径这类「终点接回起点」的数据专用19。
6. 在线教程:散点图(第二部分,成品是气泡图)
给第 9 章的散点加颜色与大洲筛选、悬浮提示,再把人口编码进圆半径——散点升级成气泡图。
全套代码在 StepByStep2/,Observable 上有可跑版本20。
7. 作者的判断与证据
| 判断 | 性质 | 依据 |
|---|---|---|
| 「配色方案优于手工调色」 | 实践取向 | 理由:好配色不容易,现成方案更易维护21 |
| 「顺序数据用单色相由浅入深」 | Color Brewer 体系 | 借 colorbrewer2.org 的分类法(三类数据 × 无障碍)1 |
| 「Cividis 等感知优化方案适合科学数据」 | 设计声明 | 各方案的设计者与设计目标照实列出18 |
8. 边界与局限
- d3-color 对 CSS Color Level 4 的支持不完整(函数式只到 CSS 3),8 位 hex 是 Level 4 里唯一被支持的函数式写法3。
- 颜色无障碍只到「用现成方案」这一层;如何为已有图表检验色盲可读性(模拟工具、对比度计算)没有展开。
- 印刷 CMYK 转换只在 LAB 一节一句带过(「frequently used to convert RGB to CMYK」)22。
interpolateRgbBasis不支持透明度插值10。
判断(我们的,不是书里的): 本章的隐含主张值得单独记住:颜色的数值操作(①②层) 和颜色的选择(③层)是两件事。工程师容易沉迷于调通道(brighter/darker、空间互转), 而图表好不好看、可不可靠,九成取决于第 ③ 层「选对 scheme」——那是设计学的沉淀, 不是算法的输出。D3 把 Color Brewer 体系直接打包,本质是把设计决策外包给设计师。 如果错,会错在: 感知均匀空间(Lab/HCL)也不是万灵——极端饱和色在屏幕上显示时 仍会超出设备色域,通道数值「均匀」不等于渲染结果「均匀」;高精度色彩工作仍要校色。
9. 可带走的
- 配色先问数据类型:类别 → categorical,有强弱 → sequential,有正负两极 → diverging;
d3.scheme*:离散方案(数组/数组的数组),配scaleOrdinal/scaleThreshold;d3.interpolate*:连续方案(函数),配scaleSequential/scaleDiverging,颜色永不重复;schemeXxx[n]按色数取子调色板;类别不够会循环,档位分配改用 threshold/quantize;- 感知均匀调整走 Lab/HCL:
brighter/darker只动亮度通道;RGB 对象三通道全动; - 色相是循环角度:
h += 180得补色;两色之间插值有短路径与…Long长路径; - gamma 校正(
interpolateRgb.gamma(1.8))让渐变中点更亮,符合人眼暗部敏感的特性; - 色盲与科学数据优先 Viridis/Cividis 系;周期数据用 Rainbow 等循环方案;
- d3-color 3.1 不认空格分隔的 CSS 4 颜色函数,逗号分隔的 CSS 3 写法才保险;
- 插值器返回字符串,要继续操作就
d3.color(结果)包回对象。
10. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| CSS 颜色写法 | Colors | text/17-ch10-chapter-10-colors.txt:59(搜「specifier strings」) · text/17-ch10-chapter-10-colors.txt:66(搜「#ff093c」) |
| d3-color 支持范围警告 | Colors | text/17-ch10-chapter-10-colors.txt:147(搜「Not all CSS Level 4」) |
| 色彩空间函数表 | Colors | text/17-ch10-chapter-10-colors.txt:83(搜「Using color spaces」) · text/17-ch10-chapter-10-colors.txt:95(搜「d3.color(specifier)」) |
| console 实验(d3.color/通道改写) | Colors | text/17-ch10-chapter-10-colors.txt:143(搜「#ff0000f7」) · text/17-ch10-chapter-10-colors.txt:158(搜「d3.rgb(255,127,63)」) · text/17-ch10-chapter-10-colors.txt:180(搜「d3.rgb(0,100,200)」) · text/17-ch10-chapter-10-colors.txt:210(搜「complementary hue」) |
| 颜色对象方法表 | Colors | text/17-ch10-chapter-10-colors.txt:303(搜「Using color objects」) · text/17-ch10-chapter-10-colors.txt:312(搜「formatRgb()」) |
| brighter/darker 行为 | Colors | text/17-ch10-chapter-10-colors.txt:338(搜「d3.rgb(128,90,50)」) · text/17-ch10-chapter-10-colors.txt:347(搜「d3.lab("red")」) · text/17-ch10-chapter-10-colors.txt:353(搜「darker(0.5)」) |
| 插值器工厂表 | Colors | text/17-ch10-chapter-10-colors.txt:362(搜「Color space interpolation」) · text/17-ch10-chapter-10-colors.txt:372(搜「interpolateHsl(a,b)」) · text/17-ch10-chapter-10-colors.txt:385(搜「interpolateRgbBasis」) |
| 红-藏青长短路径 | Colors | text/17-ch10-chapter-10-colors.txt:405(搜「rednavy」) · text/17-ch10-chapter-10-colors.txt:421(搜「interpolateHslLong」) |
| gamma 校正 | Colors | text/17-ch10-chapter-10-colors.txt:477(搜「gamma(1.8)」) · text/17-ch10-chapter-10-colors.txt:480(搜「rgb(174, 0, 174)」) |
| scheme 与 interpolate 的分类 | Colors | text/17-ch10-chapter-10-colors.txt:499(搜「Using color schemes」) · text/17-ch10-chapter-10-colors.txt:513(搜「prefixed with d3.scheme*」) |
| 类别方案与序数尺 | Colors | text/17-ch10-chapter-10-colors.txt:535(搜「Categorical color schemes」) · text/17-ch10-chapter-10-colors.txt:546(搜「schemeCategory10[2]」) · text/17-ch10-chapter-10-colors.txt:553(搜「scaleOrdinal(d3.schemeCategory10)」) |
| 顺序方案的取色与子调色板 | Colors | text/17-ch10-chapter-10-colors.txt:563(搜「Sequential color schemes」) · text/17-ch10-chapter-10-colors.txt:577(搜「schemeReds[3]」) · text/17-ch10-chapter-10-colors.txt:585(搜「schemeYlOrRd[3]」) |
| threshold 配档位色 | Colors | text/17-ch10-chapter-10-colors.txt:592(搜「partitioning scales」) · text/17-ch10-chapter-10-colors.txt:597(搜「scaleThreshold(d3.schemeYlOrRd[4])」) |
| 发散方案与插值 | Colors | text/17-ch10-chapter-10-colors.txt:606(搜「Diverging color schemes」) · text/17-ch10-chapter-10-colors.txt:616(搜「schemeSpectral[3]」) · text/17-ch10-chapter-10-colors.txt:667(搜「interpolateRdYlGn(0)」) |
| 感知优化与循环方案 | Colors | text/17-ch10-chapter-10-colors.txt:687(搜「enhance color perception」) · text/17-ch10-chapter-10-colors.txt:702(搜「interpolateTurbo(0.2)」) · text/17-ch10-chapter-10-colors.txt:714(搜「interpolateRainbow(0)」) |
| 气泡图教程 | Colors | text/17-ch10-chapter-10-colors.txt:734(搜「scatterplot – part 2」) |
| 章小结 | Colors | text/17-ch10-chapter-10-colors.txt:755(搜「Summary」) |
Footnotes
-
出处:「Colors」第 505–512 段(
text/17-ch10-chapter-10-colors.txt:505,搜「Color Brewer」;:511,搜「most derived from Color Brewer」)。 ↩ ↩2 -
出处:「Colors」第 59–77 段(
text/17-ch10-chapter-10-colors.txt:63,搜「color name」;:66,搜「#ff093c」;:68,搜「Color space functions」)。 ↩ -
出处:「Colors」第 146–152 段(
text/17-ch10-chapter-10-colors.txt:147,搜「Not all CSS Level 4」)。原文:「only supports Level 4 color strings for hexadecimal representations… only rgb(), rgba(), hsl() and hsla() specifier strings are supported.」 ↩ ↩2 -
出处:「Colors」第 83–90 段(
text/17-ch10-chapter-10-colors.txt:84,搜「mathematical model」;:89,搜「hue-saturation-lightness」)。 ↩ -
出处:「Colors」第 137–160、199–213 段(
text/17-ch10-chapter-10-colors.txt:143,搜「#ff0000f7」;:158,搜「d3.rgb(255,127,63)」;:210,搜「complementary hue」)。 ↩ -
出处:「Colors」第 303–331 段(
text/17-ch10-chapter-10-colors.txt:312,搜「formatRgb()」;:320,搜「formatHex()」)。方法表照原书 Table 10.2 转写。 ↩ -
出处:「Colors」第 333–354 段(
text/17-ch10-chapter-10-colors.txt:338,搜「d3.rgb(128,90,50)」;:347,搜「d3.lab("red")」;:344,搜「only this channel will be affected」)。 ↩ -
出处:「Colors」第 296–299 段(
text/17-ch10-chapter-10-colors.txt:298,搜「d3-cam16」)。 ↩ -
出处:「Colors」第 405–422 段(
text/17-ch10-chapter-10-colors.txt:405,搜「rednavy」;:412,搜「don't return a color object」;:416,搜「d3.color(rednavy(0.5))」)。 ↩ -
出处:「Colors」第 370–398 段(
text/17-ch10-chapter-10-colors.txt:372,搜「interpolateHsl(a,b)」;:385,搜「interpolateRgbBasis」;:389,搜「Opacity interpolation is not supported」)。工厂表照原书 Table 10.3 转写。 ↩ ↩2 -
出处:「Colors」第 473–484 段(
text/17-ch10-chapter-10-colors.txt:477,搜「gamma(1.8)」;:480,搜「rgb(174, 0, 174)」)。 ↩ -
出处:「Colors」第 425–436 段(
text/17-ch10-chapter-10-colors.txt:428,搜「scaleLinear()」;:431,搜「interpolate(d3.interpolateHsl)」;:436,搜「scaleSequential(rednavy)」)。 ↩ -
出处:「Colors」第 517–523 段(
text/17-ch10-chapter-10-colors.txt:519,搜「flavors: categorical」;:520,搜「categorical」;:521,搜「sequential」;:522,搜「diverging」)。 ↩ -
出处:「Colors」第 535–560 段(
text/17-ch10-chapter-10-colors.txt:546,搜「schemeCategory10[2]」;:553,搜「scaleOrdinal(d3.schemeCategory10)」;:555,搜「getColor(25)」)。 ↩ -
出处:「Colors」第 563–603 段(
text/17-ch10-chapter-10-colors.txt:577,搜「schemeReds[3]」;:585,搜「schemeYlOrRd[3]」;:597,搜「scaleThreshold(d3.schemeYlOrRd[4])」)。 ↩ -
出处:「Colors」第 606–620 段(
text/17-ch10-chapter-10-colors.txt:612,搜「brighter as they approach the center」;:616,搜「schemeSpectral[3]」)。 ↩ -
出处:「Colors」第 626–675 段(
text/17-ch10-chapter-10-colors.txt:638,搜「interpolatePurples(0.5)」;:646,搜「scaleSequential(d3.interpolateReds)」;:667,搜「interpolateRdYlGn(0)」)。 ↩ -
出处:「Colors」第 686–699 段(
text/17-ch10-chapter-10-colors.txt:692,搜「Viridis, Inferno, Magma」;:694,搜「Cividis」;:696,搜「Turbo」)。 ↩ ↩2 -
出处:「Colors」第 707–716 段(
text/17-ch10-chapter-10-colors.txt:707,搜「cyclic」;:714,搜「interpolateRainbow(0)」)。 ↩ -
出处:「Colors」第 734–751 段(
text/17-ch10-chapter-10-colors.txt:735,搜「second part of the tutorial」;:740,搜「StepByStep/10-final」)。 ↩ -
出处:「Colors」第 494–496 段(
text/17-ch10-chapter-10-colors.txt:496,搜「easier to use and maintain」)。 ↩ -
出处:「Colors」第 222–227 段(
text/17-ch10-chapter-10-colors.txt:227,搜「CMYK for printing」)。 ↩