【D3.js in Action 3 精译_036】4.1 DIY 实战:在 Observable 平台实现 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.1.2.4 DIY 实战:在 Observable 平台实现折线图坐标轴的绘制 ✔️
      • 4.2 D3 折线图的绘制(精译中 ⏳)

文章目录

  • DIY 实战:在 Observable 平台实现 D3折线图坐标轴的绘制
    • 1 需求描述
    • 2 实现过程
    • 3 单元测试示例
    • 4 复盘与小结

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

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

译者按
4.1.2 节介绍了很多坐标轴的新知识,除了在本地实际敲一遍代码,还应该有意识地在 Observable 平台进行同步实战。要想掌握 D3,窃以为这是必不可少的重要环节。

DIY 实战:在 Observable 平台实现 D3折线图坐标轴的绘制

1 需求描述

根据 4.1 小节介绍的内容,在 Observable 平台实现一版完整的 D3 折线图坐标轴,如图 1 所示:

图 1 需要在 Observable 实现的折线图坐标轴效果

【图 1 需要在 Observable 实现的折线图坐标轴效果】

2 实现过程

登录 Observable 官网 https://observablehq.com/,在默认工作空间下新建一个空白记事本,写上标题:

图 2 在 Observable 新建一个空白记事本,并写上标题

【图 2 在 Observable 新建一个空白记事本,并写上标题】

按照 D3 的边距约定,定义一个尺寸常量 sizes

sizes = {// Conform to D3's margin conventionconst [top, right, bottom, left] = [40, 170, 25, 40];const margin = { top, right, bottom, left };const [width, height] = [1000, 500];const innerWidth = width - margin.left - margin.right;const innerHeight = height - margin.top - margin.bottom;return {marginTop: top,marginLeft: left,width,height,innerWidth,innerHeight};
}

注意:这里根据边距尺寸的使用情况做了些优化,实际绘制中只需要用到 6 个尺寸,因此单独导出为 sizes 的相应属性值。

接着另起一个单元格,定义一个 SVG 元素,并将边距约定中会用到的各个尺寸导入进来:

svg = {// Conform to D3's margin conventionconst { marginLeft, marginTop, width, height } = sizes;// create SVG containerconst svg = d3.create("svg").attr("viewBox", `0, 0, ${width}, ${height}`);// create lineChart selectionconst lineChart = svg.append("g").attr("transform", `translate(${marginLeft}, ${marginTop})`);appendTimeAxis(lineChart);appendTemperatureAxis(lineChart);stylingLineChartAxes(lineChart);appendAxisLabel(svg);return svg.node();
}

然后分别实现绘制 SVG 折线图坐标轴需要的四个工具方法:时间轴的绘制、温度轴的绘制、通用样式的处理、以及纵轴标签的添加,分别对应 appendTimeAxis(lineChart)appendTemperatureAxis(lineChart)stylingLineChartAxes(lineChart)appendAxisLabel(svg)。在实现这四个方法之前,先将原始数据文件 weekly_temperature.csv 上传到记事本页面(经测试,数据集也可以直接用 await 语法得到,无需放在 IIFE 结构中):

data = await FileAttachment("weekly_temperature.csv").csv({ typed: true });

将原始数据集上传到 Observable 记事本页面

【图 3 将原始数据集上传到 Observable 记事本页面】

图 3 经过处理的折线图数据集

【图 4 经过处理的折线图数据集】

然后就可以实现上述四个子方法了。首先是时间轴的绘制方法 appendTimeAxis(lineChart)

function appendTimeAxis(lineChart) {const { innerWidth, innerHeight } = sizes;// create xScaleconst firstDate = new Date(2021, 0, 1, 0, 0, 0);const lastDate = d3.max(data, (d) => d.date);const xScale = d3.scaleTime().domain([firstDate, lastDate]).range([0, innerWidth]);// create bottom axisconst bottomAxis = d3.axisBottom(xScale).tickFormat(d3.timeFormat("%b"));// draw the bottom time axislineChart.append("g").attr("class", "axis-x").attr("transform", `translate(0, ${innerHeight})`).call(bottomAxis);// center the tick labelslineChart.selectAll(".axis-x text").attr("x", (curr) => {const nextMonth = curr.getMonth() + 1;const nextDate = new Date(2021, nextMonth, 1);return (xScale(nextDate) - xScale(curr)) / 2;}).attr("y", "10px");
}

这里需要注意一点,第 13 行其实可以赋给一个常量,并作为函数结果返回。这样做方便 SVG 节点对绘制出的坐标轴进行统一管理(本例中暂不考虑)。另外,关于日期格式化函数 d3.timeFormat() 的写法和功能,这次实战还做了些深入源码的发散工作,将在下一篇详细讲解,本篇只介绍 d3.timeFormat() 的单元测试方法。

再来练练垂直方向温度轴的绘制方法 appendTemperatureAxis(lineChart) 的实现:

function appendTemperatureAxis(lineChart) {const { innerHeight } = sizes;// create yScaleconst yScale = d3.scaleLinear().domain([0, d3.max(data, (d) => d.max_temp_F)]).range([innerHeight, 0]);const leftAxis = d3.axisLeft(yScale);// append vertical axislineChart.append("g").attr("class", "axis-y").attr("x", "-5px").call(leftAxis);
}

接着是字体样式的统一设置:

function stylingLineChartAxes(lineChart) {lineChart.selectAll(".axis-x text, .axis-y text").style("font-family", "Roboto, sans-serif").style("font-size", "14px");
}

最后添加温度轴的轴标签文本:

function appendAxisLabel(svg) {svg.append("text").attr("y", "20px").text("Temperature (°F)");
}

注意:SVG 的文本元素 <text> 在填入文本时不是用的 attr() 方法,而是直接调用 text()

最后按 Shift + Enter 查看 SVG 节点中的坐标轴:

图 4 绘制在 Observable 上的折线图坐标轴实测效果

【图 5 绘制在 Observable 上的折线图坐标轴实测效果】

3 单元测试示例

根据 【第 031 篇】 实现的单元测试模块,这里以 d3.timeFormat() 函数为例进行单元测试。

首先导入单元测试模块(自定义的 MyMocha 类,以及来自 Chai.js 的断言方法 expect):

import { MyMocha as Mocha, expect } from "@anton-playground/combined-unit-tests"

然后编程 Observable 版的单元测试用例,分别测试一至十二月的短月份格式化情况:

suit = {const suit = new Mocha("测试 D3.js 的日期格式化函数 d3.timeFormat(specifier):");const describe = suit.describe.bind(suit);const it = suit.it.bind(suit);const monthArray = ["Jan", "Feb", "Mar", "Apr", "May", "Jun","Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];const formatShortMonth = d3.timeFormat("%b");const fn = (date) => monthArray[date.getMonth()];describe("当标识符 specifier 为 '%b':", () => {monthArray.forEach((expected, index) => {const iDate = new Date(2024, index, 1, 0, 0, 0);// check d3.timeFormatit(`给定日期 2024-${index + 1}-1, 应该得到 "${expected}"`, () => {const actual = formatShortMonth(iDate);expect(actual).to.equal(expected);});});});return await suit.showResults();
}

测试运行效果:

图 6 用自定义测试类测试 d3.timeFormat() 方法

【图 6 用自定义测试类测试 d3.timeFormat() 方法】

4 复盘与小结

  • 书中出于代码复用的考虑,将坐标轴的样式设置集中到一个 D3 选择集中;实测时发现,按功能拆成四个方法后,样式设置必须放到坐标轴的后面执行,也就是说本次实战中的模块拆分存在副作用(需要等坐标轴先绘制完毕才行)。如果不考虑顺序,则最好将字体设置分别写入 appendTemperatureAxis(lineChart)appendTemperatureAxis(lineChart) 内;

  • 绘制坐标轴并未使用熟悉的 D3 数据绑定写法:d3.selectAll().data().join();而是通过定义坐标轴所需的比例尺间接关联数据集 data

  • D3 的坐标轴生成器共有四个,其中的方向(axisLeftaxisRightaxisTopaxisBottom)与坐标轴整体的坐标无关,方法名中的方向仅仅表示刻度线相对于坐标轴线的位置(因此水平日期轴需要平移,而温度轴不需要):

    • axisLeft:刻度线及刻度标签位于坐标轴线的 左侧
    • axisRight:刻度线及刻度标签位于坐标轴线的 右侧
    • axisTop:刻度线及刻度标签位于坐标轴线的 顶部
    • axisBottom:刻度线及刻度标签位于坐标轴线的 底部
  • 书中对 d3.timeFormat 的说明不多,只给了 D3 的参考文档。实测时可以自定义一个日期转短月份的格式化函数,例如:

    const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun","Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
    const bottomAxis = d3.axisBottom(xScale).tickFormat(date => months[date.getMonth() + 1]);
    

至于最后一条为什么要写成 d3.timeFormat('%b'),我会在下一篇文章中结合 D3 源码进行详细说明,敬请关注!

对 D3 及 Observable 平台感兴趣的朋友也可以访问本次实战的 Observable 页面:https://observablehq.com/@anton-playground/d3-line-chart-axes-with-customized-unit-test

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/57018.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

百度统计提示:为了满足法律法规最新规定和要求,您的网站由于存在合规风险将被暂停使用百度统计服务

今天打开百度统计后看到提示&#xff1a; 为了满足法律法规及政府监管的最新规定和要求&#xff0c;保护广大网民的合法权益&#xff0c;您的网站由于存在合规风险将被暂停使用百度统计服务。 请您在10日内完成整改&#xff0c;否则将删除违规站点及数据&#xff0c;有问题可发…

使用dotnet-counters和dotnet-dump 分析.NET Core 项目内存占用问题

在.NET Core 项目部署后&#xff0c;我们往往会遇到内存占用越来越高的问题&#xff0c;但是由于项目部署在Linux上&#xff0c;因此无法使用VS的远程调试工具来排查内存占用问题。那么这篇文章我们大家一起来学习一下如何排查内存占用问题。 首先&#xff0c;我们来看一下应用…

手机数据恢复技巧:适用于手机的恢复应用程序

发现自己意外删除了 Android 设备上的照片&#xff0c;这让人很痛苦。这些照片可能是值得纪念的文件&#xff0c;会让您想起一些难忘的回忆。删除它们后&#xff0c;您知道如何恢复它们。在这种情况下&#xff0c;您需要使用 Android 的照片恢复应用程序。 无论您需要直接从 A…

【控制系统】深入理解反步控制(Backstepping) | 反步法控制器原理与应用实例解析(附Matlab/Simulink仿真实现)

&#x1f4af; 欢迎光临清流君的博客小天地&#xff0c;这里是我分享技术与心得的温馨角落 &#x1f4af; &#x1f525; 个人主页:【清流君】&#x1f525; &#x1f4da; 系列专栏: 运动控制 | 决策规划 | 机器人数值优化 &#x1f4da; &#x1f31f;始终保持好奇心&…

使用Airtest自动化某云音乐爬取歌曲名称

简介 本文将介绍如何使用Airtest自动化工具来模拟用户操作&#xff0c;从某云音乐中爬取与特定关键词相关的歌曲名称。我们将以搜索“文字”相关的歌曲为例&#xff0c;并将结果保存到本地文件。 准备工作 安装Airtest并配置好Android设备或模拟器。确保你的设备上已安装某云…

【独家:AI编程助手Cursor如何revolutionize Java设计模式学习】

【独家:AI编程助手Cursor如何revolutionize Java设计模式学习】 导语 在Java高级编程的世界里,设计模式是每个开发者必须掌握的利器。但是,如何快速理解并灵活运用这些模式呢?让我们一起探索如何借助AI编程助手Cursor,轻松掌握设计模式,提升Java编程技能! 正文 设计模式:J…

QUIC(Quick UDP Internet Connections)与 RTMP(Real Time Messaging Protocol)

QUIC&#xff08;Quick UDP Internet Connections&#xff09;和 RTMP&#xff08;Real Time Messaging Protocol&#xff09;是两种不同的网络传输协议&#xff0c;它们在一些方面有不同的特点和应用场景。 QUIC 协议 特点 基于 UDP&#xff1a;QUIC 建立在 UDP 之上&#xff…

探索 Jupyter 核心:nbformat 库的神秘力量

文章目录 探索 Jupyter 核心&#xff1a;nbformat 库的神秘力量1. 背景介绍&#xff1a;为何选择 nbformat&#xff1f;2. nbformat 是什么&#xff1f;3. 如何安装 nbformat&#xff1f;4. 简单的库函数使用方法4.1 读取 Notebook 文件4.2 修改 Notebook 中的单元格4.3 添加 M…

灵当CRM index.php 任意文件上传漏洞复现

0x01 产品描述&#xff1a; 灵当CRM是一款专为中小企业量身定制的智能客户关系管理工具&#xff0c;由上海灵当信息科技有限公司开发和运营。该系统广泛应用于多个行业&#xff0c;包括金融、教育、医疗、IT服务及房地产等领域&#xff0c;旨在满足企业对客户个性化管理的需求&…

【2024版】sql-liabs靶场前十关解题过程和思路----适合入门小白

在你们看到这个靶场之前&#xff0c;你们可以先去听一下课&#xff0c;然后再来做这个靶场你们的感悟就会比较深&#xff0c;当你听过课再来做就不会觉得这么懵了&#xff0c;重庆橙子科技-sql注入&#xff0c;我之前学习是听的他的课&#xff0c;我觉得是全网讲的最好的一个师…

vue2使用pdfjs-dist实现pdf预览(iframe形式,不修改pdfjs原来的ui和控件,dom层可以用display去掉一部分组件)

前情提要 在一开始要使用pdf预览的时候&#xff0c;第一次选的是vue-pdf&#xff0c;但是vue-pdf支持的功能太少&#xff0c;缺少了项目中需要的一项-复制粘贴功能 之后我一顿搜搜搜&#xff0c;最终貌似只有pdfjs能用 但是网上支持text-layer的貌似都是用的2.09那个版本。 使…

MySQL同步到ES的方案选型

文章目录 1. 同步双写优点缺点实现方式 2. 异步双写优点缺点实现方式 3. 另起应用 SQL 查询写入优点缺点实现方式 4. Binlog 实时同步优点缺点实现方式 5. 应用场景 本文参考: https://www.bilibili.com/video/BV13hvZeaErr/?vd_sourceb7e4d17fd13ffa91c4da6d37c08a6c7c 最近在…

【题解】—— LeetCode一周小结42

&#x1f31f;欢迎来到 我的博客 —— 探索技术的无限可能&#xff01; &#x1f31f;博客的简介&#xff08;文章目录&#xff09; 【题解】—— 每日一道题目栏 上接&#xff1a;【题解】—— LeetCode一周小结41 14.鸡蛋掉落 题目链接&#xff1a;887. 鸡蛋掉落 给你 k 枚…

c++迷宫游戏

1、问题描述 程序开始运行时显示一个迷宫地图&#xff0c;迷宫中央有一只老鼠&#xff0c;迷宫的右下方有一个粮仓。游戏的任务是使用键盘上的方向健操纵老鼠在规定的时间内走到粮仓处。 基本要求: 老鼠形象可以辨认,可用键盘操纵老鼠上下左右移动&#xff1b;迷宫的墙足够结…

Springboot指定扫描路径

方式一&#xff1a;通过在启动类的SpringbootApplication中指定包扫描或类扫描 指定需要扫描的包 scanBasePackages{"待扫描包1","待扫描包2", . . . ," "} 指定需要扫描的类 scanBasePackageClasses{类1.class,类2.class,...} 方式二&#xff…

C语言函数实现:深入理解strcpy

文章目录 一、strcpy函数的基本用法二、strcpy函数的实现原理三、strcpy函数的应用场景四、strcpy函数的安全性问题五、结论 C语言函数实现&#xff1a;深入理解strcpy 在C语言编程中&#xff0c;字符串处理是一项基础且重要的任务。 strcpy函数作为C标准库中的一个基本函数&a…

CDC变更数据捕捉技术是什么?和ETL有什么不同?

一、什么是CDC技术? 变更数据捕获&#xff08;Change Data Capture&#xff0c;简称 CDC&#xff09;是一种用于识别和跟踪数据源中发生变化的数据的技术。 工作原理&#xff1a; 1.监测数据源&#xff1a;CDC 工具会持续监测指定的数据源&#xff0c;如数据库表、文件系统…

【C++复习】经典笔试题

文章目录 八大排序快排过程 卡特兰数反转链表链表的回文结构左叶子之和另一棵树的子树归并排序类与对象编程训练杨辉三角字符串乘积二叉树前序遍历成字符串数组的交集二叉树的非递归前序遍历连续子数组的最大乘积 八大排序 插冒归稳定 快排过程 以 [3,4,6,1,2,4,7] 为例&#…

MySQL 聚合函数

1. AVG函数求平均值 以 teacher 表为例&#xff0c;先查所有 teacher 信息&#xff1a; SELECT * FROM teacher;查询结果如下图&#xff1a; 可以使用 AVG() 函数求出全部教师平均年龄&#xff1a; SELECT AVG(age) FROM teacher;执行结果如下图&#xff1a; Tips&#…

Javascript 脚本查找B站限时免费番剧

目录 前言 脚本编写 脚本 前言 B站的一些番剧时不时会“限时免费”&#xff0c;白嫖党最爱&#xff0c;主打一个又占到便宜的快乐。但是在番剧索引里却没有搜索选项可以直接检索“限时免费”的番剧&#xff0c;只能自己一页一页的翻去查看&#xff0c;非常麻烦。 自己找限…