OpenMontage D3.js 比例尺(Scale)全参考:从连续映射到颜色编码的完整实战指南
2026/9/10 0:23:31 网站建设 项目流程

OpenMontage D3.js 比例尺(Scale)全参考:从连续映射到颜色编码的完整实战指南

【免费下载链接】OpenMontageWorld's first open-source, agentic video production system. 12 production pipelines, 100+ tools, 700+ agent skill and production-knowledge files. Turn your AI coding assistant into a full video production studio.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMontage

本篇指南以 OpenMontage 仓库中 d3-viz 技能的 scale-reference.md 为骨架,系统梳理 D3.js 全部比例尺类型:连续比例尺(linear / power / log / time)、分箱比例尺(quantize / quantile / threshold)、颜色比例尺(sequential / diverging / sequentialQuantile)、序数比例尺(band / point / ordinal),以及.nice().clamp().ticks()等实用工具与颜色插值空间。读完你将能够为任意数据集选择合适的比例尺,写出坐标轴、柱状图、散点图、热力图和等值区域图(choropleth)中最核心的「数据→像素」映射代码。

什么是 D3 比例尺:一切可视化的第一块基石

D3.js(Data-Driven Documents)的核心思想是把数据绑定到 DOM 元素,并对元素施加数据驱动的变换。而**比例尺(scale)**正是数据值与视觉值之间那座桥:它把抽象的输入域(domain,如温度 0~100)映射到具体的输出范围(range,如屏幕像素 0~500),是坐标轴、条长、圆半径、颜色深浅等一切位置/大小/颜色编码的基础。

在 OpenMontage 中,d3-viz 技能(见 SKILL.md)被设计为「创建自定义图表、网络图、地理可视化或任何需要精细控制视觉元素的 SVG 可视化」时使用,可嵌入 React、Vue、Svelte 或原生 JavaScript 环境。配套的资源体系包括:

  • scale-reference.md:本文讲解的完整比例尺参考;
  • d3-patterns.md:树图、热力图、弦图、地理可视化等完整模式;
  • colour-schemes.md:颜色方案与调色板推荐;
  • chart-template.jsx 与 interactive-template.jsx:可直接运行的 React 模板;
  • sample-data.json:时间序列、类别、散点、层级、网络等六类测试数据集。

下文将按「连续 → 颜色 → 序数 → 工具 → 插值 → 组合模式」的顺序完整展开。

连续比例尺:把连续数值映射到连续视觉通道

连续比例尺负责把连续输入域映射到连续输出范围,是量化数据可视化中使用最频繁的一族。

Linear scale:最通用的定量比例尺

d3.scaleLinear()以线性插值方式把连续输入域映射到连续输出范围,OpenMontage 的 d3-viz 技能将其列为「大多数定量数据的首选」。

const scale = d3.scaleLinear() .domain([0, 100]) .range([0, 500]); scale(50); // Returns 250 scale(0); // Returns 0 scale(100); // Returns 500 // Invert scale (get input from output) scale.invert(250); // Returns 50

适用场景:

  • 定量数据最常用的比例尺
  • 坐标轴、柱条长度、位置编码
  • 温度、价格、计数、测量值

核心方法:

  • .domain([min, max])— 设置输入域
  • .range([min, max])— 设置输出范围
  • .invert(value)— 由范围值反推域值
  • .clamp(true)— 将输出限制在范围内
  • .nice()— 将定义域扩展到整洁的取整值

一个值得注意的细节:当range使用字符串颜色时,linear scale 默认执行 RGB 插值(详见后文「颜色空间与插值」),这使它同时能充当最简单的连续色标。

Power scale:非线性感知缩放

d3.scalePow()以指数变换处理连续映射,通过.exponent()控制非线性程度;d3.scaleSqrt()则是指数为 0.5 的简写形式:

const sqrtScale = d3.scalePow() .exponent(0.5) // Square root .domain([0, 100]) .range([0, 500]); const squareScale = d3.scalePow() .exponent(2) // Square .domain([0, 100]) .range([0, 500]); // Shorthand for square root const sqrtScale2 = d3.scaleSqrt() .domain([0, 100]) .range([0, 500]);

适用场景:

  • 感知缩放(人类感知本身就是非线性的)
  • 面积编码(用平方根把数值映射为圆的半径,避免面积被高估)
  • 强调小值或大值之间的差异

Logarithmic scale:跨越多个数量级的数据

d3.scaleLog()以对数变换处理连续映射,非常适合人口、GDP、财富分布这类跨越多个数量级的数据:

const logScale = d3.scaleLog() .domain([1, 1000]) // Must be positive .range([0, 500]); logScale(1); // Returns 0 logScale(10); // Returns ~167 logScale(100); // Returns ~333 logScale(1000); // Returns 500

适用场景:

  • 跨越多个数量级的数据
  • 人口、GDP、财富分布
  • 对数坐标轴
  • 指数增长可视化

重要约束:域值必须严格为正(>0)。

从代码实现角度看,scaleLinear内部存储domainrange两个数组,求值时先做双线性归一化再做线性插值;scaleLog则先把 domain 映射为Math.log10,再走同一套插值管线。理解这一共性后,你可以在需要时自行组合interpolate函数替换默认插值器(详见后文)。

Time scale:时间序列的专用线性比例尺

d3.scaleTime()是专为时间数据定制的线性比例尺,域由Date对象组成:

const timeScale = d3.scaleTime() .domain([new Date(2020, 0, 1), new Date(2024, 0, 1)]) .range([0, 800]); timeScale(new Date(2022, 0, 1)); // Returns 400 // Invert to get date timeScale.invert(400); // Returns Date object for mid-2022

适用场景:

  • 时间序列可视化
  • 时间线坐标轴
  • 时间驱动的动画
  • 基于日期的交互

方法:

  • .nice()— 将域扩展到整洁的时间间隔
  • .ticks(count)— 生成间隔均匀的刻度值
  • 所有 linear scale 的方法同样适用

Quantize scale:连续输入 → 离散输出桶

d3.scaleQuantize()把连续输入映射到离散输出桶:输入域被等分成range.length段,每段对应一个输出值:

const quantizeScale = d3.scaleQuantize() .domain([0, 100]) .range(['low', 'medium', 'high']); quantizeScale(25); // Returns 'low' quantizeScale(50); // Returns 'medium' quantizeScale(75); // Returns 'high' // Get the threshold values quantizeScale.thresholds(); // Returns [33.33, 66.67]

适用场景:

  • 连续数据分箱
  • 热力图颜色
  • 风险等级(低/中/高)
  • 年龄段、收入区间

Quantile scale:按分位数分组

d3.scaleQuantile()根据样本数据的分位数把连续输入映射为离散输出,其特点是每组元素数量大致相等,不受分布形态影响:

const quantileScale = d3.scaleQuantile() .domain([3, 6, 7, 8, 8, 10, 13, 15, 16, 20, 24]) // Sample data .range(['low', 'medium', 'high']); quantileScale(8); // Returns based on quantile position quantileScale.quantiles(); // Returns quantile thresholds

适用场景:

  • 与分布无关的等量分组
  • 基于百分位的分类
  • 处理偏态分布

Threshold scale:自定义断点的分箱

d3.scaleThreshold()用自定义阈值把连续输入映射为离散输出。注意其规律:域中 n 个阈值把数轴切成 n+1 段,因此range长度总是domain.length + 1

const thresholdScale = d3.scaleThreshold() .domain([0, 10, 20]) .range(['freezing', 'cold', 'warm', 'hot']); thresholdScale(-5); // Returns 'freezing' thresholdScale(5); // Returns 'cold' thresholdScale(15); // Returns 'warm' thresholdScale(25); // Returns 'hot'

适用场景:

  • 自定义断点
  • 成绩等级(A、B、C、D、F)
  • 温度类别
  • 空气质量指数

顺序(Sequential)颜色比例尺:连续值 → 连续色带

顺序颜色比例尺把连续输入映射为连续颜色渐变,是热力图、等值区域图、密度图的核心编码手段。

Sequential colour scale

const colourScale = d3.scaleSequential(d3.interpolateBlues) .domain([0, 100]); colourScale(0); // Returns lightest blue colourScale(50); // Returns mid blue colourScale(100); // Returns darkest blue

可用插值器:

单色相(Single hue):

  • d3.interpolateBluesd3.interpolateGreensd3.interpolateReds
  • d3.interpolateOrangesd3.interpolatePurplesd3.interpolateGreys

多色相(Multi-hue):

  • d3.interpolateViridisd3.interpolateInfernod3.interpolateMagma
  • d3.interpolatePlasmad3.interpolateWarmd3.interpolateCool
  • d3.interpolateCubehelixDefaultd3.interpolateTurbo

适用场景:

  • 热力图、等值区域图(choropleth map)
  • 连续数据可视化
  • 温度、海拔、密度

补充说明:根据同技能包的 colour-schemes.md,interpolateViridis被推荐为通用首选——它感知均匀、对色盲友好、可安全打印且没有视觉死区;每个连续插值器都有对应的 9 步离散版本,例如d3.schemeBlues[9],可配合 quantize/threshold 使用。

Diverging colour scale:带中点的发散色带

d3.scaleDiverging()把连续输入映射为以中点为对称的发散渐变,用于表达「围绕某个有意义的中点(零、均值、中性值)偏离」的语义:

const divergingScale = d3.scaleDiverging(d3.interpolateRdBu) .domain([-10, 0, 10]); divergingScale(-10); // Returns red divergingScale(0); // Returns white/neutral divergingScale(10); // Returns blue

可用插值器:

  • d3.interpolateRdBu— 红到蓝
  • d3.interpolateRdYlBu— 红、黄、蓝
  • d3.interpolateRdYlGn— 红、黄、绿
  • d3.interpolatePiYG— 粉、黄、绿
  • d3.interpolateBrBG— 棕、蓝绿
  • d3.interpolatePRGn— 紫、绿
  • d3.interpolatePuOr— 紫、橙
  • d3.interpolateRdGy— 红、灰
  • d3.interpolateSpectral— 彩虹光谱

适用场景:

  • 具有语义中点的数据(零、均值、中性值)
  • 正负值对比
  • 高于/低于基准的比较
  • 相关性矩阵

domain传入三个值[min, midpoint, max],中点对应的插值位置是 0.5(即色带中央颜色),非常适合表达盈亏、偏差与相关性。colour-schemes.md 还提醒:在做正负对比时,若无特殊语义要求,优先用蓝-橙(BrBGPuOr)替代红-绿,以规避色盲问题。

Sequential quantile scale:顺序色 × 分位数

d3.scaleSequentialQuantile()把顺序色与分位数映射结合:它从样本域自动计算分位断点,再按分位位置分配颜色,天然抗离群值与偏态分布:

const sequentialQuantileScale = d3.scaleSequentialQuantile(d3.interpolateBlues) .domain([3, 6, 7, 8, 8, 10, 13, 15, 16, 20, 24]); // Maps based on quantile position

适用场景:

  • 感知均匀的分箱
  • 处理离群值
  • 偏态分布

序数(Ordinal)比例尺:离散类别 → 位置或颜色

序数比例尺处理离散类别输入,是柱状图、散点图类别轴、图例与多系列图表的基石。

Band scale:为柱状图而生

d3.scaleBand()把离散输入映射为连续的「带」(矩形),支持内/外间距配置:

const bandScale = d3.scaleBand() .domain(['A', 'B', 'C', 'D']) .range([0, 400]) .padding(0.1); bandScale('A'); // Returns start position (e.g., 0) bandScale('B'); // Returns start position (e.g., 110) bandScale.bandwidth(); // Returns width of each band (e.g., 95) bandScale.step(); // Returns total step including padding bandScale.paddingInner(); // Returns inner padding (between bands) bandScale.paddingOuter(); // Returns outer padding (at edges)

适用场景:

  • 柱状图(最常见的用法)
  • 分组元素
  • 类别坐标轴
  • 热力图单元格

间距选项:

  • .padding(value)— 同时设置内、外间距(0-1)
  • .paddingInner(value)— 带与带之间的间距(0-1)
  • .paddingOuter(value)— 边缘间距(0-1)
  • .align(value)— 带的对齐方式(0-1,默认 0.5)

bandwidth()step()的区别值得强调:step = bandwidth + paddingInner × step,绘制rect宽度时务必使用bandwidth()而不是step()

Point scale:类别位置的锚点

d3.scalePoint()把离散输入映射为连续的点位置(没有宽度),是折线图类别横轴、散点图类别轴与网络图节点定位的标准选择:

const pointScale = d3.scalePoint() .domain(['A', 'B', 'C', 'D']) .range([0, 400]) .padding(0.5); pointScale('A'); // Returns position (e.g., 50) pointScale('B'); // Returns position (e.g., 150) pointScale('C'); // Returns position (e.g., 250) pointScale('D'); // Returns position (e.g., 350) pointScale.step(); // Returns distance between points

适用场景:

  • 折线图的类别横轴
  • 带类别轴的散点图
  • 网络图中的节点位置
  • 任何类别的点定位

Ordinal colour scale:类别 → 离散颜色

d3.scaleOrdinal()把离散输入映射为离散输出(颜色、形状等)。其确定性映射保证同一类别始终得到同一颜色:

const colourScale = d3.scaleOrdinal(d3.schemeCategory10); colourScale('apples'); // Returns first colour colourScale('oranges'); // Returns second colour colourScale('apples'); // Returns same first colour (consistent) // Custom range const customScale = d3.scaleOrdinal() .domain(['cat1', 'cat2', 'cat3']) .range(['#FF6B6B', '#4ECDC4', '#45B7D1']);

内置颜色方案:

Categorical(分类):

  • d3.schemeCategory10— 10 色
  • d3.schemeAccent— 8 色
  • d3.schemeDark2— 8 色
  • d3.schemePaired— 12 色
  • d3.schemePastel1— 9 色
  • d3.schemePastel2— 8 色
  • d3.schemeSet1— 9 色
  • d3.schemeSet2— 8 色
  • d3.schemeSet3— 12 色
  • d3.schemeTableau10— 10 色

适用场景:

  • 类别颜色
  • 图例项
  • 多系列图表
  • 网络节点类型

colour-schemes.md 补充了选型建议:一般用途默认schemeCategory10;仪表盘/商业报告可选schemeTableau10;超过 10 个类别时优先schemePaired(12 色)或对类别分组,而不是堆更多颜色。

比例尺工具:nice、clamp、copy 与刻度生成

所有比例尺都共享一组实用工具方法,本节完整列出。

Nice domain:扩展到整洁值

.nice()将 domain 扩展到「好看的」取整值,让坐标轴刻度更易读:

const scale = d3.scaleLinear() .domain([0.201, 0.996]) .nice(); scale.domain(); // Returns [0.2, 1.0] // With count (approximate tick count) const scale2 = d3.scaleLinear() .domain([0.201, 0.996]) .nice(5);

可选参数count给出近似刻度数提示,scaleTime().nice()则会扩展到整洁的时间间隔。

Clamping:限制输出边界

.clamp(true)把超出域的输出严格限制在 range 范围内,防止散点或柱条溢出绘图区:

const scale = d3.scaleLinear() .domain([0, 100]) .range([0, 500]) .clamp(true); scale(-10); // Returns 0 (clamped) scale(150); // Returns 500 (clamped)

Copy:独立副本

.copy()返回与原比例尺互不影响的副本,方便基于同一配置派生多个变体:

const scale1 = d3.scaleLinear() .domain([0, 100]) .range([0, 500]); const scale2 = scale1.copy(); // scale2 is independent of scale1

Tick generation:坐标轴刻度

.ticks()生成整洁的刻度值,.tickFormat()返回对应的格式化函数,二者可直接喂给d3.axisBottom()/d3.axisLeft()使用:

const scale = d3.scaleLinear() .domain([0, 100]) .range([0, 500]); scale.ticks(10); // Generate ~10 ticks scale.tickFormat(10); // Get format function for ticks scale.tickFormat(10, ".2f"); // Custom format (2 decimal places) // Time scale ticks const timeScale = d3.scaleTime() .domain([new Date(2020, 0, 1), new Date(2024, 0, 1)]); timeScale.ticks(d3.timeYear); // Yearly ticks timeScale.ticks(d3.timeMonth, 3); // Every 3 months timeScale.tickFormat(5, "%Y-%m"); // Format as year-month

时间比例尺的ticks()可接受d3.timeYeard3.timeMonth等时间间隔对象,tickFormat支持%Y-%m这类 d3-time-format 语法。

颜色空间与插值:RGB、HSL、Lab、HCL

连续比例尺(尤其是 linear scale)的颜色插值方式可以通过.interpolate()切换,不同颜色空间影响过渡的「质感」:

const scale = d3.scaleLinear() .domain([0, 100]) .range(["blue", "red"]); // Default: RGB interpolation
const scale = d3.scaleLinear() .domain([0, 100]) .range(["blue", "red"]) .interpolate(d3.interpolateHsl); // Smoother colour transitions
const scale = d3.scaleLinear() .domain([0, 100]) .range(["blue", "red"]) .interpolate(d3.interpolateLab); // Perceptually uniform
const scale = d3.scaleLinear() .domain([0, 100]) .range(["blue", "red"]) .interpolate(d3.interpolateHcl); // Perceptually uniform with hue
  • RGB(默认):分量线性插值,简单直接,但中间色可能发灰、不够「干净」;
  • HSL:按色相角走捷径,过渡更平滑,但可能产生意外的色相环绕;
  • Lab:感知均匀,中间色符合人眼亮度感知,适合追求专业过渡的场合;
  • HCL:感知均匀且保留色相维度,是多色相渐变的首选。

在自定义发散/顺序渐变时,colour-schemes.md 中的做法也是同样的思路:d3.scaleLinear().range([...]).interpolate(d3.interpolateLab)即可得到感知均匀的自定义渐变。

常见组合模式:把比例尺用出生产力

本节是原文档的精华实战部分——六个高频组合模式,完整保留。

自定义中点的发散比例尺

const scale = d3.scaleLinear() .domain([min, midpoint, max]) .range(["red", "white", "blue"]) .interpolate(d3.interpolateHcl);

多段渐变比例尺

const scale = d3.scaleLinear() .domain([0, 25, 50, 75, 100]) .range(["#d53e4f", "#fc8d59", "#fee08b", "#e6f598", "#66c2a5"]);

圆的半径比例尺(感知正确)

const radiusScale = d3.scaleSqrt() .domain([0, d3.max(data, d => d.value)]) .range([0, 50]); // Use with circles circle.attr("r", d => radiusScale(d.value));

注意这里刻意使用scaleSqrt而不是scaleLinear:圆的面积成正比,直接用线性比例尺会让大值圆显得「过大」。d3-patterns.md 的 Bubble chart 模式(sizeScale = d3.scaleSqrt().domain([0, d3.max(data, d => d.size)]).range([0, 50]))印证了这一约定。

基于数据范围的自适应比例尺

function createAdaptiveScale(data) { const extent = d3.extent(data); const range = extent[1] - extent[0]; // Use log scale if data spans >2 orders of magnitude if (extent[1] / extent[0] > 100) { return d3.scaleLog() .domain(extent) .range([0, width]); } // Otherwise use linear return d3.scaleLinear() .domain(extent) .range([0, width]); }

d3.extent()一次性取回数据的最小值与最大值,配合比值判断(跨度超过两个数量级时切换为 log)即可实现通用自适应。colour-schemes.md 中还有类似的按数据正负动态选择 diverging/sequential 的写法,可与本模式结合。

带显式类别与未知值回退的颜色比例尺

const colourScale = d3.scaleOrdinal() .domain(['Low Risk', 'Medium Risk', 'High Risk']) .range(['#2ecc71', '#f39c12', '#e74c3c']) .unknown('#95a5a6'); // Fallback for unknown values

.unknown()为未出现在 domain 中的输入提供回退色(如灰色「缺数据」),避免空值产生undefined或错误颜色。

项目侧印证:从 D3 比例尺到 Remotion 图表组件

OpenMontage 的视频渲染侧(remotion-composer)提供了不依赖 D3 的图表组件(BarChart、LineChart、PieChart、KPIGrid,见 charts 目录)。对照 BarChart.tsx 的实现,可以看到本文所述比例尺思想的「手工版」:

  • const maxValue = Math.max(...data.map((d) => d.value), 1);求出数据极值,等价于d3.max().domain([0, max])
  • barHeightFull = (datum.value / maxValue) * chartHeight做线性归一化,等价于scaleLinear().domain([0, max]).range([0, chartHeight])
  • 网格线通过value = (maxValue / gridLineCount) * i均分生成,等价于scale.ticks(5)
  • 柱宽由barGapbarCount与可用宽度均摊,等价于scaleBand().paddingInner()

这说明两件事:其一,掌握比例尺的本质(归一化 + 插值)后,即使在没有 D3 的渲染环境(如 Remotion 的逐帧渲染管线)里,也能手动复现同样的编码逻辑;其二,在需要交互、缩放、复杂布局或颜色插值的自定义可视化中,直接用 D3 比例尺仍是生产力最高的路径——这正是 d3-viz 技能及其 scale-reference.md 存在的意义。

实战速查与总结

按需求快速选型:

你的数据推荐比例尺
定量数据 → 位置/长度scaleLinear(配.nice().clamp()
圆面积/感知缩放scaleSqrtscalePow
跨数量级数据scaleLog(domain 必须 >0)
时间序列scaleTime(配ticks(d3.timeMonth)
连续值 → 少数几个桶scaleQuantize/scaleThreshold
等量分组/抗偏态scaleQuantile
连续值 → 色带scaleSequential(interpolateViridis)
正负发散(含中点)scaleDiverging(interpolateRdBu)
类别 → 柱条scaleBand(用bandwidth()设宽)
类别 → 点/折线轴scalePoint
类别 → 颜色scaleOrdinal(schemeCategory10)

最后的工程建议(综合本参考与 colour-schemes.md):先想清楚「输入是什么、输出编码什么通道」,再在连续/序数/颜色三大族中定位;坐标轴一律用.ticks()+.tickFormat()生成;需要颜色渐变时优先感知均匀的 Viridis/Cividis 或 Lab/HCL 插值;涉及色觉障碍用户时避免红-绿组合,并叠加形状、图案、标签等多重编码。掌握本文全部比例尺及其方法,你就能从数据直达像素,写出清晰、准确且专业的 D3 可视化。

【免费下载链接】OpenMontageWorld's first open-source, agentic video production system. 12 production pipelines, 100+ tools, 700+ agent skill and production-knowledge files. Turn your AI coding assistant into a full video production studio.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMontage

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询