首页
/ D3 d3-axis 坐标轴组件深度解析:渲染、刻度配置、样式定制与动画实战

D3 d3-axis 坐标轴组件深度解析:渲染、刻度配置、样式定制与动画实战

2026-09-05 18:29:48作者:蔡怀权

d3-axis 是 D3 生态中负责将比例尺“翻译”为人类可读坐标轴的组件:它把 scale 的刻度位置与格式渲染为 SVG 的 pathlinetext 元素,并支持 selection 与 transition 两种调用方式以实现平滑动画。本文基于当前仓库(d3 v7.9.0)中的 d3-axis 官方参考文档示例组件版本变更记录,完整覆盖 axisTop / axisRight / axisBottom / axisLeft 四个构造函数、ticks / tickValues / tickFormat / tickSize / tickPadding / offset 等全部 API 的默认值与行为细节,并给出可复制、可运行的代码示例与源码级佐证。读完后你可以独立完成坐标轴的创建、定位、样式定制、显式刻度控制与响应式动画更新。

下图展示了 D3 3.x(未手动设置样式时)与 D3 4.0 起(内置默认样式并做半像素偏移)的坐标轴渲染差异,这一演进背景在本文“版本演进”一节展开:

D3 3.x 未设置样式的坐标轴:粗黑横线,刻度文字与轴线重叠,无刻度线

D3 4.0 起带默认样式的坐标轴:细刻度线、清晰刻度标签、半像素偏移的整洁外观

核心机制:坐标轴是比例尺的“人类可读层”

根据 d3-axis 文档 的定义:坐标轴组件(axis component)为位置比例尺渲染人类可读的参考标记,支持线性、对数、band、时间等多种比例尺类型。文档中的四个演示分别对应这四类 scale:

d3.axisBottom(d3.scaleLinear([0, 100], range))                       // 线性
d3.axisBottom(d3.scaleLog([1, 1000], range))                         // 对数
d3.axisBottom(d3.scaleBand([...'ABCDEFGHIJKL'], range))              // band
d3.axisBottom(d3.scaleUtc([new Date('2011-01-01'),
                          new Date('2013-01-01')], range))           // 时间

在当前仓库中,d3-axis 是通过 src/index.js 第 2 行 export * from "d3-axis" 统一暴露的,package.json 声明其版本约束为 d3-axis ^3.0.0test/d3-test.js 会逐项断言 d3 命名空间导出 d3-axis 子包的全部属性,因此文档中出现的每个 d3.axisXxx API 都保证存在于 v7 的 d3 全局命名空间。

基本用法:在 G 元素上渲染坐标轴

调用坐标轴生成器时,需要作用在一个 SVG 容器(通常是一个单独的 g 元素)的 selection 上。坐标轴默认渲染在原点处,要改变其在图表中的位置,需要给容器元素指定 SVG 的 transform 属性:

const gx = svg.append("g")
    .attr("transform", `translate(0,${height - marginBottom})`)
    .call(d3.axisBottom(x));

这段代码的含义是:先创建一个 g 分组,把它平移到画布底部(height - marginBottom 处),再调用 d3.axisBottom(x) 在该 g 内填充坐标轴。仓库文档页自身的 ExampleAxis.vue 组件演示了同样的模式——<g :transform="translate(x,y)"> 定位容器,然后在 onMounted 中执行 d3.select(g.value).call(props.axis) 完成首次渲染。

四个方向构造函数

D3 4.0 起(见 CHANGES.md 的 Axes 章节),d3.svg.axis().orient(...) 的写法被四个构造函数取代,每个构造函数都以指定 scale 创建对应方向的坐标轴,默认空的 tick 参数、刻度尺寸(tick size)为 6、填充间距(padding)为 3:

构造函数 方向 刻度绘制位置
d3.axisTop(scale) 水平 domain path 的上方
d3.axisRight(scale) 垂直 domain path 的右侧
d3.axisBottom(scale) 水平 domain path 的下方
d3.axisLeft(scale) 垂直 domain path 的左侧

例如右侧 y 轴的标准写法:

const gy = svg.append("g")
    .attr("transform", `translate(${marginLeft},0)`)
    .call(d3.axisLeft(y));

方向是固定的:若需改变坐标轴方向,必须移除旧轴并重新创建,而不能在既有生成器上切换方向。

更新与动画:对 transition 再次调用

当 scale 变化(例如 domain 改变)时,再次调用坐标轴生成器即可更新。若要平滑动画,则对 transition 调用:

gx.transition()
    .duration(750)
    .call(d3.axisBottom(x));

仓库文档页中“动态 domain 的线性坐标轴”演示即基于此:每 5 秒用 d3.interval 随机生成新的 [x, x + l] domain,坐标轴在 1500ms 的 transition 中平滑迁移刻度位置。ExampleAxis.vue 第 21–23 行给出了 Vue 环境下的等价实现——onUpdated 钩子中执行:

d3.select(g.value).transition().duration(props.duration).call(props.axis);

这说明无论框架是原生 DOM 还是 Vue/React,d3-axis 的更新契约都一样:持有对 g 元素的 selection,对新数据调用 transition + call

生成的 DOM 结构:样式定制的公开 API

文档明确:坐标轴创建的元素属于其公开 API,可以施加外部样式表或直接修改生成的元素来定制外观。一个典型的 bottom 方向坐标轴渲染结果如下(该结构同样可直接作为 CSS 选择器的依据):

<g fill="none" font-size="10" font-family="sans-serif" text-anchor="middle">
  <path class="domain" stroke="currentColor" d="M0.5,6V0.5H880.5V6"></path>
  <g class="tick" opacity="1" transform="translate(0.5,0)">
    <line stroke="currentColor" y2="6"></line>
    <text fill="currentColor" y="9" dy="0.71em">0.0</text>
  </g>
  <g class="tick" opacity="1" transform="translate(176.5,0)">
    <line stroke="currentColor" y2="6"></line>
    <text fill="currentColor" y="9" dy="0.71em">0.2</text>
  </g>
  <g class="tick" opacity="1" transform="translate(352.5,0)">
    <line stroke="currentColor" y2="6"></line>
    <text fill="currentColor" y="9" dy="0.71em">0.4</text>
  </g>
  <g class="tick" opacity="1" transform="translate(528.5,0)">
    <line stroke="currentColor" y2="6"></line>
    <text fill="currentColor" y="9" dy="0.71em">0.6</text>
  </g>
  <g class="tick" opacity="1" transform="translate(704.5,0)">
    <line stroke="currentColor" y2="6"></line>
    <text fill="currentColor" y="9" dy="0.71em">0.8</text>
  </g>
  <g class="tick" opacity="1" transform="translate(880.5,0)">
    <line stroke="currentColor" y2="6"></line>
    <text fill="currentColor" y="9" dy="0.71em">1.0</text>
  </g>
</g>

结构解读:

  • g 元素自带 fill="none"font-size="10"font-family="sans-serif"text-anchor="middle"默认属性,这是 D3 4.0 引入“默认样式以属性形式应用”的改进(见 CHANGES.md:默认外观做了半像素偏移,修复了 Safari 上轴线渲染成两像素粗的问题);
  • path.domain:代表 scale 值域范围的 domain 路径,stroke="currentColor",注意其 d 属性中的 0.5 偏移即半像素偏移(crisp edges);
  • 每个刻度是一个带 transform="translate(x,0)"g.tick 分组,内部含一条 line(刻度线,长度由 tick size 决定)和一个 text(刻度标签,dy="0.71em" 使文字垂直居中于基线);
  • 颜色全部使用 currentColor,因此修改容器 gcolor 属性即可整体换色。

API 参考

以下方法与 d3-axis 文档 的条目一一对应,补充了默认值与源码行为说明。

axis(context)

将坐标轴渲染到给定的 context 上,context 可以是 SVG 容器(svgg 元素)的 selection,也可以是对应的 transition

svg.append("g")
    .attr("transform", `translate(0,${height - marginBottom})`)
    .call(d3.axisBottom(x));

这也是为什么“selection 上调用”与“transition 上调用”是同一个入口——d3-axis 内部对两者采用统一的元素遍历逻辑。

axis.scale(scale)

若指定 scale,设置坐标轴使用的 scale 并返回坐标轴生成器;若未指定,返回当前 scale:

const xAxis = d3.axisBottom().scale(x);

d3.axisBottom(x) 直接传参等价,但支持“先创建、后换 scale”的场景。

axis.ticks(...arguments)

设置渲染时传给 scale.ticksscale.tickFormat 的参数并返回生成器。参数的含义取决于坐标轴所用 scale 的类型:最常见的是建议的刻度数量(时间 scale 则传时间 interval),以及可选的 format specifier

axis.ticks(20, "s");                  // 线性 scale:约 20 个刻度,SI 前缀格式化
axis.ticks(d3.timeMinute.every(15));  // 时间 scale:每 15 分钟一个刻度

它本质上是 axis.tickArguments 的便捷函数,axis.ticks(10) 等价于 axis.tickArguments([10])

重要限制:如果 scale 未实现 scale.ticks(如 bandpoint scale),此方法无效——band 类 scale 的刻度天然就是每个 domain 值。此时应改用:

  • axis.tickValues(...):显式指定刻度值;
  • axis.tickFormat(...):显式指定刻度格式;
  • 或直接调用 scale.ticks(...) 生成刻度值。

axis.tickArguments(arguments)

axis.ticks 语义相同,但以数组形式设置/读取参数,并且支持读取当前值(这是它比 ticks 多出的能力):

axis.tickArguments([20, "s"]);          // 设置
axis.tickArguments([d3.timeMinute.every(15)]);
axis.tickArguments();                   // 读取,默认为空数组 []

CHANGES.md 指出 tickArguments 正是 D3 4.0 新增的方法,动机就是让 tick 参数可以被检视(inspect),而 v3 时代的 d3.svg.axis() 只能写不能读。

axis.tickValues(values)

若指定可迭代的 values,则用这些值代替 scale 的自动刻度生成器:

const axis = d3.axisBottom(x).tickValues([1, 2, 3, 5, 8, 13, 21]);

行为规则(原文逐条继承):

  • 显式刻度值优先于 axis.tickArguments 设置的参数;
  • 但若未同时显式设置 tick 格式,tick arguments 仍会传给 scale 的 tickFormat;
  • valuesnull,清除显式刻度值,回退到 scale 的刻度生成器;
  • 若未指定参数,返回当前刻度值,默认为 null

axis.tickFormat(format)

若指定 format,设置刻度格式函数并返回坐标轴。例如用千位分隔显示整数:

axis.tickFormat(d3.format(",.0f"));

更常见的做法是把 format specifier 传给 axis.ticks,因为它能基于刻度间隔自动推断格式精度

axis.ticks(10, ",f");

格式函数的创建可参考 d3-formatd3-time-format。若未指定参数,返回当前格式函数,默认为 nullnull 表示使用 scale 的默认格式器(由调用 scale.tickFormat 生成),此时 axis.tickArguments 设置的参数会一并传给 scale.tickFormat。

axis.tickSize(size)

同时设置内部外部刻度尺寸并返回坐标轴;未指定时返回当前内部刻度尺寸(默认 6):

const axis = d3.axisBottom(x).tickSize(0);   // 隐藏刻度线
axis.tickSize();                               // 0

axis.tickSizeInner(size)

只控制刻度线长度(相对坐标轴原始终点位置的偏移),默认 6:

const axis = d3.axisBottom(x).tickSizeInner(0);
axis.tickSizeInner();   // 0

axis.tickSizeOuter(size)

控制 domain path 两端“方形端头”的长度,默认 6。注意一个易混淆点:outer ticks 并不是真正的刻度,而是 domain path 的一部分,其位置由 scale 的 domain 范围决定,因此可能与第一个或最后一个 inner tick 重叠。设为 0 时,方形端头被抑制,domain path 变成一条直线——这也是文档中 band scale 示例 .tickSizeOuter(0) 的用途:

const axis = d3.axisBottom(x).tickSizeOuter(0);
axis.tickSizeOuter();   // 0

axis.tickPadding(padding)

设置刻度线末端到标签之间的像素间距,默认 3:

const axis = d3.axisBottom(x).tickPadding(0);
axis.tickPadding();   // 0

axis.offset(offset)

设置整个坐标轴的像素偏移,默认规则是:

  • devicePixelRatio > 1 的高分辨率设备上默认为 0
  • 否则默认为 0.5(半像素)。

这个默认值保证了低分辨率设备上坐标轴边缘的清晰渲染(crisp edges)——回顾上方 DOM 示例中 path.domaind="M0.5,6V0.5H880.5V6",其中 0.5 正是该偏移的体现。手动覆盖示例:

const axis = d3.axisBottom(x).offset(0);
axis.offset();   // 0

版本演进:从 v3 的手动样式到 v7 的默认样式

CHANGES.md 的 Axes (d3-axis) 章节(约第 386–436 行)完整记录了这一模块的 API 演化,对阅读旧代码的开发者尤其重要。D3 3.x 中坐标轴渲染正确的前提是手写 CSS:

<style>
.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}
.axis text {
  font: 10px sans-serif;
}
</style>

不做这一步会得到文章开头 axis-v3.png 所示的“粗黑线 + 重叠文字”效果;而 D3 4.0 之后,样式以属性形式默认应用,语法也缩短为:

d3.select(".axis")
    .call(d3.axisBottom(x));

同一章节还记录了三项 API 变更,与本文 API 参考呼应:

  1. 新增 axis.tickArguments,作为 axis.ticks 的可读值替代;
  2. axis.tickSize 改为只接受单个参数设置刻度尺寸;
  3. v3 的 innerTickSize / outerTickSize 更名为 axis.tickSizeInneraxis.tickSizeOuter

当前仓库(d3 v7.9.0,见 package.json)沿用了这套 v4 定型的 API,test/d3-test.js 的导出完整性断言保证了 d3.axisTop 等四个构造函数与全部 axisXxx 方法在 d3 命名空间可用。

小结

d3-axis 的完整心智模型可以归纳为四步:

  1. d3.axisTop/Right/Bottom/Left(scale) 选择方向并绑定 scale,方向固定不可改;
  2. svg.append("g").attr("transform", ...).call(axis),用 g 的 transform 控制位置;
  3. ticks 控制刻度生成参数、tickValues/tickFormat 做显式覆盖、tickSize*/tickPadding/offset 控制几何细节,各方法均支持无参读取当前值;
  4. :domain 变化后对 selectiontransition 再次 call,即可静态刷新或动画迁移。

需要进一步深入时,可继续阅读本仓库的 d3-scale 文档d3-selection 文档d3-transition 文档,它们分别是 d3-axis 在刻度生成、元素操作与动画三个维度上的直接依赖。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384