当前位置: 首页 > article >正文

【D3.js in Action 3 精译_035】4.1 D3 中的坐标轴的创建(下篇):坐标轴与轴标签的具体实现

当前内容所在位置(可进入专栏查看其他译好的章节内容)

  • 第一部分 D3.js 基础知识
    • 第一章 D3.js 简介(已完结)
      • 1.1 何为 D3.js?
      • 1.2 D3 生态系统——入门须知
      • 1.3 数据可视化最佳实践(上)
      • 1.3 数据可视化最佳实践(下)
      • 1.4 本章小结
    • 第二章 DOM 的操作方法(已完结)
      • 2.1 第一个 D3 可视化图表
      • 2.2 环境准备
      • 2.3 用 D3 选中页面元素
      • 2.4 向选择集添加元素
      • 2.5 用 D3 设置与修改元素属性
      • 2.6 用 D3 设置与修改元素样式
      • 2.7 本章小结
    • 第三章 数据的处理(已完结)
      • 3.1 理解数据
      • 3.2 准备数据
      • 3.3 将数据绑定到 DOM 元素
        • 3.3.1 利用数据给 DOM 属性动态赋值
      • 3.4 让数据适应屏幕
        • 3.4.1 比例尺简介(上篇)
        • 3.4.2 线性比例尺(中篇)
          • 3.4.2.1 基于 Mocha 测试 D3 线性比例尺(DIY 实战)
        • 3.4.3 分段比例尺(下篇)
          • 3.4.3.1 使用 Observable 在线绘制 D3 条形图(DIY 实战)
      • 3.5 加注图表标签(上篇)
        • 3.5.1 人物专访:Krisztina Szűcs(下篇)
      • 3.6 本章小结
    • 第四章 直线、曲线与弧线的绘制 ✔️
      • 4.1 坐标轴的创建(上篇)
        • 4.1.1 D3 中的边距约定(中篇)
        • 4.1.2 坐标轴的生成(中篇)
          • 4.1.2.1 比例尺的声明(中篇)
          • 4.1.2.2 坐标轴的添加(下篇) ✔️
          • 4.1.2.3 轴标签的添加(下篇) ✔️
      • 4.2 D3 折线图的绘制(精译中 ⏳)

文章目录

      • 4.1.2 坐标轴的生成 Generating axes
        • 1 比例尺的声明 Declaring the scales
        • 2 坐标轴的添加 Appending the axes ✔️
        • 3 轴标签的添加 Adding axis labels ✔️

《D3.js in Action》全新第三版封面

《D3.js in Action》全新第三版封面

译者按
上一篇我们完成了折线图比例尺的定义,它们是为本篇将要实现的坐标轴做铺垫。D3 坐标轴的相关知识点是 D3 基础知识中的重中之重,是后续定制各种可视化效果的前提。让我们跟着作者的思路,一口气拿下这块高地!

4.1.2 坐标轴的生成 Generating axes

(详见本专栏 【第 034 篇】)

1 比例尺的声明 Declaring the scales

(详见本专栏 【第 034 篇】)

2 坐标轴的添加 Appending the axes ✔️

两个比例尺的初始化完毕后,接下来添加坐标轴。D3 有四个坐标轴生成器:axisTop()axisRight()axisBottom()axisLeft(),分别用于创建顶部、右侧、底部及左侧的坐标轴。它们都归属于 d3-axis 模块。

上一节提过,坐标轴生成器函数接受一个比例尺作为参数。例如创建折线图的底部坐标轴,就该调用 axisBottom() 生成器,并将比例尺 xScale 作为参数传入,因为该比例尺负责分配底部坐标轴上的数据。然后将结果赋给常量 bottomAxis

const bottomAxis = d3.axisBottom(xScale);

坐标轴生成器负责将构成坐标轴的各要素组装到一起,为了让这些要素渲染到屏幕上,还需要调用 D3 选择集上的 call() 方法。在下列代码片段中,注意观察选择集 innerChart 的用法:在调用坐标轴生成器前我们先添加了一个类名为 axis-x 的分组元素,以便后续给坐标轴定位并设计样式。

const bottomAxis = d3.axisBottom(xScale);
innerChart
  .append("g")
    .attr("class", "axis-x")
    .call(bottomAxis);

然后在浏览器中查看生成的坐标轴效果。默认情况下,D3 坐标轴会出现在选择集的原点位置,本例中即为内部图表的左上角,如图 4.6 所示。此时可以通过平移坐标轴所在的 SVG 分组元素将其移动到图表底部。顺便强调一下:在分组元素上设置的样式变换会被旗下所有子元素继承。以下代码片段中,包含坐标轴元素的分组元素向下平移,平移量为内部图表的高度值:

const bottomAxis = d3.axisBottom(xScale);
innerChart
  .append("g")
    .attr("class", "axis-x")
    .attr("transform", `translate(0, ${innerHeight})`)
    .call(bottomAxis);

图 4.6 默认情况下,D3 坐标轴会在选择集的原点位置生成,即内部图表的左上角位置。需要通过平移操作定位到目标位置。

【图 4.6 默认情况下,D3 坐标轴会在选择集的原点位置生成,即内部图表的左上角位置。需要通过平移操作定位到目标位置。】

另一个需要调整的是坐标轴上的标签格式。默认情况下,D3 会根据定义域自动设置轴标签格式,在屏幕上渲染出相应的小时、天数、月份及年份标签。但默认格式未必是我们想要的效果。为此,D3 提供了多种方式来更改标签格式。

先来看一下,x 轴已经有了二月到十二月的标签,唯独没有一月份的。考虑到您所在的时区,数据集中的最早日期未必就是一月一日的零点,这样 D3 就无法将其视为首月的起点;又由于数据集不是动态的,硬编码一个变量 firstDate 代表最早日期不失为一个合理的解决方案。它可以通过 JavaScriptDate() 构造函数实现。

在以下代码片段中,firstDate 的值改为了一个 Date() 日期,并指定了年份(2021)、月份(00,月份索引值从零开始)以及日期(01),同时指定了小时、分钟及秒数(0, 0, 0):

const firstDate = new Date(2021, 00, 01, 0, 0, 0);
const lastDate = d3.max(data, d => d.date);
const xScale = d3.scaleTime()
  .domain([firstDate, lastDate])
  .range([0, innerWidth]);

保存项目后,会看到 1 月 1 日的位置有了一个轴标签;但该标签只显示了 2021 年,如图 4.7 所示。这没不能算错,因为 Fri Jan 01 2021 00:00:00 对应的就是 2021 年年初位置,只是我们想换成一个显示月份的标签。

图 4.7 默认情况下,D3 会自动调整轴标签上的时间格式。本例中 2021 年 1 月 1 日为一年的起点。这本身并没有错,但就可读性而言不是很理想。

【图 4.7 默认情况下,D3 会自动调整轴标签上的时间格式。本例中 2021 年 1 月 1 日为一年的起点。这本身并没有错,但就可读性而言不是很理想。】

还可以使用 d3-axis 模块下的 axis.tickFormat() 方法来设置轴标签的格式。刻度线(Ticks) 是上述坐标轴上的短竖线。它们通常伴随刻度标签一同显示,但也可以不显示。

假设我们想要的刻度标签格式为月份的缩写形式。在 D3 中可以使用 d3-time-format 模块下的 d3.timeFormat() 方法来设置格式。该方法接受一个格式字符串作为参数,月份名称的缩写对应格式字符串为 %b。这些格式的完整列表可以在该模块的官方文档中进行查看(译注:详见官方文档:https://d3js.org/d3-time-format#locale_format)。

以下代码片段通过底部坐标轴的选择集链式调用了 tickFormat() 方法,并将时间格式作为参数传入,最终效果如图 4.8 所示。

const bottomAxis = d3.axisBottom(xScale)
  .tickFormat(d3.timeFormat("%b"));

图 4.8 将底部轴的标签格式设为月份缩写形式的效果图

【图 4.8 将底部轴的标签格式设为月份缩写形式的效果图】

这样就设置好了日期标签的格式,每个标签都是各月的第一天,效果还不错。还可以再进一步,将标签放在两个刻度之间来提高可读性,表示每月是从前一个时间刻度延伸至后一个时间刻度。

要调整刻度标签的位置,首先得选中它们。打开浏览器的检查工具(Inspector),仔细查看 D3 生成的 SVG 坐标轴元素,会看到一个类名为 domainpath 元素。它在定义域的上方绘制了一条水平线段。该路径元素还包含两个外部刻度线,即图形两端的短竖线,如图 4.9 所示。而坐标轴的每个刻度与刻度标签则由一条短竖线和文本元素构成,并统一放在一个类名为 tick 的 SVG 分组元素内。这些 SVG 分组元素通过沿坐标轴方向的平移量来确定各刻度线与标签文本的方位。坐标轴生成器所创建的 SVG 元素标签及样式类均由 D3 的公共 API 接口控制。我们可以利用这些接口来自定义坐标轴的外观。

图 4.9 构成坐标轴的 SVG 元素示意图

【图 4.9 构成坐标轴的 SVG 元素示意图】

了解了坐标轴的结构,就可以通过选择器 ".axis-x text" 选中 x 轴的所有标签,即 axis-x 样式类下的所有文本元素。然后进行如下调整:利用其 y 属性将文本元素下移 10px,以进一步提高可读性;再将其 font-family 设置为之前用过的 Roboto,因为 D3 会默认将 axisfont-family 改为 sans-serif,从而阻断了文本标签对项目根节点的字体样式的继承。最后再将字号增大到 14px

出于分离关注点原则(separation of concerns)的考虑,以下示例代码中的最后两个样式最好通过 CSS 样式表来设置。但这里使用 D3 来简化问题:

d3.selectAll(".axis-x text")
  .attr("y", "10px")
  .style("font-family", "Roboto, sans-serif")
  .style("font-size", "14px");

为了将月份标签在对应的刻度间居中显示,这里需要调整 x 的属性值。由于每个月的天数不尽相同(28 到 31 天不等),我们需要找出各标签当月第一天与下月第一天的中间位置。同时需要注意,D3 已经在 g.asix-x 分组元素上将 text-anchor 属性自动设为了 "middle"

由于 D3 绑定到各标签的日期数据对应该月第一天,以下代码片段中,我们利用 JavaScriptgetMonth() 方法获取到当前月份。该方法返回一个介于 0 到 11 之间的整数值,分别代表一到十二月。然后将月份加一并通过 Date() 构造出一个新的 JavaScript 日期值。我们在第三章学过,回调函数里的第一个参数,通常命名为 d,代表绑定数据集中的每一个数据项,类似于遍历数据时用到的 forEach() 方法。

最后,再用 xScale 求出本月一号与下个月一号之间的中间距离。完成后的坐标轴效果将如图 4.10 所示(第 2 ~ 6 行):

d3.selectAll(".axis-x text")
  .attr("x", d => {
    const currentMonth = d;
    const nextMonth = new Date(2021, currentMonth.getMonth() + 1, 1);
    return (xScale(nextMonth) - xScale(currentMonth)) / 2;
  })
  .attr("y", "10px")
  .style("font-family", "Roboto, sans-serif")
  .style("font-size", "14px");

图 4.10 让月份标签居中显示的 x 坐标轴效果图

【图 4.10 让月份标签居中显示的 x 坐标轴效果图】

以上代码涉及很多操作,想必已经让您对自定义 D3 坐标轴的不同方法有了一个大致的了解。

接着再来为折线图添加 y 坐标轴,它的实现要简单得多。由于 y 轴靠左显示,这里用到的坐标轴生成器为 d3.axisLeft(),同时将 yScale 比例尺作为参数传入,并将结果赋给一个常量 leftAxis

const leftAxis = d3.axisLeft(yScale);

类似地,想要将坐标轴添加到内部绘图区,需要在内部图表的选择集上添加一个分组元素,并指定一个样式类 axis-y:(第 2 ~ 4 行)

const leftAxis = d3.axisLeft(yScale);
innerChart
  .append("g")
  .attr("class", "axis-y")
  .call(leftAxis);

保存设置后将看到 y 轴已经定位好了无需平移,如图 4.11 所示。剩下的工作就是设置标签的字体并增大字号。以下代码片段先选中样式类 axis-y 下的所有 SVG 文本元素,然后通过 x 属性令其向左稍作平移,以便提高可读性;之后再分别设置其 font-familyfont-size 属性:

d3.selectAll(".axis-y text")
  .attr("x", "-5px")
  .style("font-family", "Roboto, sans-serif")
  .style("font-size", "14px");

图 4.11 完成设置后的 x 坐标轴与 y 坐标轴效果图

【图 4.11 完成设置后的 x 坐标轴与 y 坐标轴效果图】

您可能也注意到了两个轴标签在设置 font-familyfont-size 时存在重复代码。在学习阶段,这样写并不算什么大问题;要是在专业项目环境下,则应当尽量避免像这样的代码冗余。除了前面提过的使用 CSS 样式表来统一设置外,还可以使用组合选择器,如以下代码所示(第 1 行):

d3.selectAll(".axis-x text, .axis-y text")
  .style("font-family", "Roboto, sans-serif")
  .style("font-size", "14px");
3 轴标签的添加 Adding axis labels ✔️

实现了折线图的坐标轴后,还有一件事需要完成——添加轴标签——它可以帮助读者更好地理解我们的图表。x 轴上的刻度标签不言而喻,但 y 轴则不然:虽然读者知道它们的取值范围在 0 到 90 之间,但可能并不知道它们的含义是什么。

这时就需要通过设置轴标签来解决这个问题。在 D3 项目中,标签就是 SVG 文本元素。因此只需将文本元素添加到 SVG 容器中即可。轴标签的内容就设为 "Temperature (°F)",然后将其垂直坐标指定到距离 SVG 容器原点 20px 的位置:

svg
  .append("text")
  .text("Temperature (°F)")
  .attr("y", 20);

大功告成!您本地的折线图效果此时应该如图 4.12 所示。

图 4.12 完成设置后的坐标轴及轴标签效果图

【图 4.12 完成设置后的坐标轴及轴标签效果图】

下一节我们将实现折线图的绘制。


http://www.kler.cn/news/361058.html

相关文章:

  • 【Go进阶】协程的创建以及通信
  • 我是类(最终版)
  • Java | Leetcode Java题解之第491题非递减子序列
  • 在windows上开发的python程序能直接在linux上跑吗?
  • echarts柱状图数据太多,如何实现鼠标滑动查看
  • 【五】企业级JavaScript开发之入门
  • Android 原生程序使用gdb, addr2line, readelf调试
  • CTFHUB技能树之XSS——DOM反射
  • JMeter详细介绍和相关概念
  • LLM----BERT+HuggingFace理论及基础入门实战
  • 【Flutter】基础入门:自定义Widget
  • 空文件夹,python项目来启动
  • 《Python基础教程》笔记(ch0-1)
  • 2024年软件设计师中级(软考中级)详细笔记【7】面向对象技术(下)23种设计模式(分值10+)
  • js读取.txt文件内容
  • RIP协议
  • 刷爆leetccode Day5
  • 7-1 大写字母转换为小写字母(C++;cin;cout)
  • 数据库表的创建
  • Spring配置/管理bean-IOC(控制反转) 非常详细!基于XML及其注解!案例分析! 建议复习收藏!