跳转至

颜色 — 从色彩空间到配色方案

这一章讲什么: 颜色在图里不是装饰,是编码信息的通道:区分类别、表达强弱、标出极值。 本章三层讲透 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. 可带走的

  1. 配色先问数据类型:类别 → categorical,有强弱 → sequential,有正负两极 → diverging;
  2. d3.scheme*:离散方案(数组/数组的数组),配 scaleOrdinal / scaleThreshold;
  3. d3.interpolate*:连续方案(函数),配 scaleSequential / scaleDiverging,颜色永不重复;
  4. schemeXxx[n] 按色数取子调色板;类别不够会循环,档位分配改用 threshold/quantize;
  5. 感知均匀调整走 Lab/HCL:brighter/darker 只动亮度通道;RGB 对象三通道全动;
  6. 色相是循环角度:h += 180 得补色;两色之间插值有短路径与 …Long 长路径;
  7. gamma 校正(interpolateRgb.gamma(1.8))让渐变中点更亮,符合人眼暗部敏感的特性;
  8. 色盲与科学数据优先 Viridis/Cividis 系;周期数据用 Rainbow 等循环方案;
  9. d3-color 3.1 不认空格分隔的 CSS 4 颜色函数,逗号分隔的 CSS 3 写法才保险;
  10. 插值器返回字符串,要继续操作就 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」)

  1. 出处:「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」)。 ↩

  3. 出处:「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.」 ↩↩

  4. 出处:「Colors」第 83–90 段(text/17-ch10-chapter-10-colors.txt:84,搜「mathematical model」;:89,搜「hue-saturation-lightness」)。 ↩

  5. 出处:「Colors」第 137–160、199–213 段(text/17-ch10-chapter-10-colors.txt:143,搜「#ff0000f7」;:158,搜「d3.rgb(255,127,63)」;:210,搜「complementary hue」)。 ↩

  6. 出处:「Colors」第 303–331 段(text/17-ch10-chapter-10-colors.txt:312,搜「formatRgb()」;:320,搜「formatHex()」)。方法表照原书 Table 10.2 转写。 ↩

  7. 出处:「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」)。 ↩

  8. 出处:「Colors」第 296–299 段(text/17-ch10-chapter-10-colors.txt:298,搜「d3-cam16」)。 ↩

  9. 出处:「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))」)。 ↩

  10. 出处:「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 转写。 ↩↩

  11. 出处:「Colors」第 473–484 段(text/17-ch10-chapter-10-colors.txt:477,搜「gamma(1.8)」;:480,搜「rgb(174, 0, 174)」)。 ↩

  12. 出处:「Colors」第 425–436 段(text/17-ch10-chapter-10-colors.txt:428,搜「scaleLinear()」;:431,搜「interpolate(d3.interpolateHsl)」;:436,搜「scaleSequential(rednavy)」)。 ↩

  13. 出处:「Colors」第 517–523 段(text/17-ch10-chapter-10-colors.txt:519,搜「flavors: categorical」;:520,搜「categorical」;:521,搜「sequential」;:522,搜「diverging」)。 ↩

  14. 出处:「Colors」第 535–560 段(text/17-ch10-chapter-10-colors.txt:546,搜「schemeCategory10[2]」;:553,搜「scaleOrdinal(d3.schemeCategory10)」;:555,搜「getColor(25)」)。 ↩

  15. 出处:「Colors」第 563–603 段(text/17-ch10-chapter-10-colors.txt:577,搜「schemeReds[3]」;:585,搜「schemeYlOrRd[3]」;:597,搜「scaleThreshold(d3.schemeYlOrRd[4])」)。 ↩

  16. 出处:「Colors」第 606–620 段(text/17-ch10-chapter-10-colors.txt:612,搜「brighter as they approach the center」;:616,搜「schemeSpectral[3]」)。 ↩

  17. 出处:「Colors」第 626–675 段(text/17-ch10-chapter-10-colors.txt:638,搜「interpolatePurples(0.5)」;:646,搜「scaleSequential(d3.interpolateReds)」;:667,搜「interpolateRdYlGn(0)」)。 ↩

  18. 出处:「Colors」第 686–699 段(text/17-ch10-chapter-10-colors.txt:692,搜「Viridis, Inferno, Magma」;:694,搜「Cividis」;:696,搜「Turbo」)。 ↩↩

  19. 出处:「Colors」第 707–716 段(text/17-ch10-chapter-10-colors.txt:707,搜「cyclic」;:714,搜「interpolateRainbow(0)」)。 ↩

  20. 出处:「Colors」第 734–751 段(text/17-ch10-chapter-10-colors.txt:735,搜「second part of the tutorial」;:740,搜「StepByStep/10-final」)。 ↩

  21. 出处:「Colors」第 494–496 段(text/17-ch10-chapter-10-colors.txt:496,搜「easier to use and maintain」)。 ↩

  22. 出处:「Colors」第 222–227 段(text/17-ch10-chapter-10-colors.txt:227,搜「CMYK for printing」)。 ↩