显然,任何数据可视化的共同点都是数据的潜在存在。作为数据可视化开发人员,我们会遇到不同类型的数据和数据集,我们需要理解和操作这些数据和数据集以生成可视化。在本章中,我们将讨论适用于大多数 D3 项目的数据工作流。如图 3.1 所示,此策略从为我们的项目查找数据开始。此数据可以包含不同的数据类型,如名义或有序数据,并且可以采用不同的数据集格式,如 CSV 或 json 文件。在这个阶段,通常有很多工作来准备和清理数据,我们不会介绍。
组装数据集后,我们使用 D3 将其加载到项目中,以格式化和测量数据。然后,我们准备根据数据生成视觉元素,通常是 SVG 形状。这个强大的过程称为数据绑定,我们将使用它为我们在第 2 章中开始的条形图生成所有矩形。
数据集中包含的值并不总是直接适用于屏幕。这些数字可能太大,无法直接用作视觉元素的大小(以像素为单位)。或者我们可能想用颜色表示特定的值。这就是D3尺度发挥作用的地方。在本章中,我们将讨论不同类型的秤以及如何使用它们。然后,我们将使用线性和波段刻度来定位条形图(https://d3js-in-action-third-edition.Github.io/most-popular-data-visualization-technologies)上的矩形并调整其大小。
图3.1 D3数据工作流
图 3.2 显示了数据工作流的简化版本,我们将在本章中继续使用它。
图3.2 D3数据工作流的简化示意图
3.1 理解数据在深入研究 D3 数据技术之前,我们将简要讨论作为 D3 开发人员将遇到的不同数据类型和数据集格式。这点理论将帮助您阅读和理解您使用的数据,这对于正确的数据可视化架构至关重要。它还将帮助您稍后为您的项目选择合适的比例。
图 3.3 D3 数据工作流的第一步是查找或收集数据。数据有不同的类型和数据集格式。
3.1.1 数据类型在构建数据可视化时,我们使用两种主要数据类型:定量和定性。定量数据是时间、重量或国家 GDP 等数字信息。如图3.4所示,定量数据可以是离散的,也可以是连续的。离散数据由整数(也称为整数)组成,无法细分。例如,公司可以有 16 名员工,但不能有 16.3 名员工。另一方面,连续数据可以划分为更小的单位,并且仍然有意义。连续数据的一个典型例子是温度。我们今天可以读到它是17°C,但我们也可以更精确地测量它,并意识到它实际上是16.8°C.通常,连续数据可以用仪器测量,而离散数据可以计数但不能测量。
同时,定性数据由文本等非数字信息组成。它可以是名义上的,也可以是序数。名义值没有特定的顺序,例如性别认同标签或城市名称。另一方面,序数值可以按数量级进行分类。如果我们以 T 恤尺码为例,我们通常按升序排列它们(XS、S、M、L、XL)。
数据类型将影响我们可以选择用于传达数据的可视化类型。折线图适用于连续数据,但不适用于离散值。标称值可以用一组分类颜色表示,而我们可以为顺序值选择顺序或发散调色板。
在选择 D3 刻度时,我们还会牢记数据类型。线性量表用于定量数据,而D3具有定性数据的特定量表。我们将在第 3.4 节中更详细地讨论比例。
图3.4 数据类型分类
3.1.2 数据格式和结构可以出于各种目的以各种方式格式化数据。尽管如此,它往往属于一些可识别的格式:表格数据、JavaScript 对象 (JSON)、嵌套数据、网络、地理数据或原始数据。
表格式的表格数据显示在列和行中,通常位于数据库中的电子表格或表格中。表格数据用特定字符(称为分隔符)分隔,该字符定义其格式。最常见的表格数据格式可能是逗号分隔值文件 (csv),其中分隔符是逗号。我们还会遇到制表符分隔值文件 (TSV) 或任何其他使用特定分隔符(如管道或分号)的分隔符分隔值文件 (DSV)。
例如,让我们从虚构的员工数据集中获取一个示例,在第 1 章中讨论。如果我们将此数据保存为 CSV、TSV 或 DSV 文件,则值将分别用逗号、制表符或其他分隔符分隔,如表 3.1 所示。在表格数据集中,第一行通常列出列标题,每行数据都列在新行上。
表 3.1 以 CSV、TSV 和 DSV 格式表示的分隔数据CSV:逗号分隔值 | TSV:制表符分隔值 | DSV:分隔符分隔值(使用管道分隔符) |
身份证,姓名,works_with_d3 | 身份证名 works_with_d3 | 编号|姓名|works_with_d3 |
1,佐伊,假 | 1 佐伊假 | 1|佐伊|假 |
2,詹姆斯,真 | 2 詹姆斯·真 | 2|詹姆斯|真 |
3,爱丽丝,真 | 3 爱丽丝真 | 3|爱丽丝|真 |
4,休伯特,假 | 4 休伯特假 | 4|休伯特|假 |
D3 提供了三种不同的函数来加载表格数据:d3.csv()、d3.tsv() 和 d3.dsv()。它们之间的唯一区别是 d3.csv() 是为逗号分隔的文件构建的,d3.tsv() 是为制表符分隔的文件构建的,d3.dsv() 允许您声明分隔符。你会在整本书中看到它们在行动。
jsonJavaScript 对象表示法文件或 JSON 是存储简单数据结构的常用方法。开发人员经常使用它们,尤其是在从 API 端点获取信息时。
如果我们以 JSON 格式而不是表格格式存储表 3.1 中的数据,它将看起来像下面的对象数组。虽然它不是最紧凑的,但对象表示法具有显着的优点,即使用第 1 章中讨论的 JavaScript 点表示法使数据键值对易于访问。
[
{
"id": 1,
"name": "Zoe",
"position": "Data analyst",
"works_with_d3": false
},
{
"id": 2,
"name": "James",
"position": "Frontend developer",
"works_with_d3": true
},
{
"id": 3,
"name": "Alice",
"position": "Fullstack developer",
"works_with_d3": true
},
{
"id": 4,
"name": "Hubert",
"position": "Designer",
"works_with_d3": false
}
]
在 d3 中,我们使用函数 d3.json() 来加载 JSON 文件。但是,即使加载其他类型的表格数据(如 CSV 文件),d3 也会将数据转换为对象数组。
嵌 套嵌套的数据很常见,对象作为对象的子级递归存在。D3 中许多最直观的布局都基于嵌套数据,这些数据可以表示为树,例如图 3.5 中的树或包装在圆圈或框中的树。数据通常不会以这种格式输出,并且需要一些脚本来组织它,但这种表示的灵活性值得付出努力。您将在第 10 章中详细查看分层数据。
图 3.5 嵌套数据表示对象的父/子关系,通常每个对象都有一个子对象数组,并以多种形式表示,例如此树状图。请注意,每个对象只能有一个父对象。
网络网络无处不在。无论是社交网络流、交通网络还是流程图的原始输出,网络都是提供对复杂系统理解的强大方法。网络通常表示为节点链接图,如图3.6所示。与地理数据一样,网络数据具有许多标准,但本文仅关注两种形式:节点/边缘列表和连接的数组。通过使用免费的网络分析工具(如 Gephi)(可在 gephi.org 获得),网络数据也可以轻松转换为这些数据类型。我们将在第11章中处理网络可视化时检查网络数据和网络数据标准。
图3.6 网络数据由对象及其之间的连接组成。对象通常称为节点或顶点,连接称为边或链接。网络通常使用力导向算法表示,例如此处的示例,该算法以将连接的节点相互拉动的方式排列网络。
地理学的地理数据将位置称为点或形状,用于创建当今在 Web 上看到的各种在线地图,例如图 3.7 中的美国地图。Web 制图的惊人普及意味着您可以访问任何项目的大量可公开访问的地理数据。地理数据有几个标准,但本书的重点是两个:GeoJSON和Topo-JSON标准。尽管地理数据可能有多种形式,但PostGIS(https://postgis.net)等现成的地理信息系统(GIS)工具允许开发人员将其转换为GIS格式,以便随时交付到Web。我们将在第 12 章中仔细研究地理数据。
图 3.7 地理数据存储对象的空间几何形状,例如状态。此图像中的每个状态都表示为一个单独的要素,其中包含指示其形状的值数组。地理数据还可以由点(例如城市)或线(例如道路)组成。
.raw当您探索数据可视化领域时,您会发现一切都是数据,包括图像、文本块和网站的标记。尽管信息可视化通常使用按颜色和大小编码的形状来表示数据,但有时在 D3 中表示数据的最佳方式是使用线性叙述文本、图像或视频。如果您为需要了解复杂系统的受众开发应用程序,但您认为文本或图像的操作在某种程度上与将数字或分类数据表示为形状是分开的,那么您会任意降低您的沟通能力。处理文本和图像时使用的布局和格式(通常与较旧的 Web 发布模式相关联)在 D3 中是可能的,我们将在本书中对此进行讨论。
3.2 准备数据准备好数据集后,我们使用 D3 将数据加载到项目中。然后,我们确保数据值的格式正确,并且可以测量数据的不同方面。在本节中,我们将讨论用于执行这些任务并为条形图准备数据的 D3 方法。
3.2.1 将数据集加载到 D3 项目中图 3.8 D3 数据工作流的第二步是使用 D3 提取函数之一将数据集加载到我们的项目中
D3 具有将数据集加载到项目中的便捷功能。我们选择的函数与数据集的格式有关。例如,CSV 文件使用 d3.csv() 加载,JSON 文件加载 d3.json() 。我们将数据文件的路径作为函数的第一个参数传递。D3还具有加载文本甚至XML文件的功能。它们都是 d3-fetch 模块 (https://github.com/d3/d3-fetch) 的一部分。
注意你可以在本书的Github存储库(https://github.com/d3js-in-action-third-edition/code-files/tree/main/chapter_3)上找到第03章的代码文件。每个部分的代码都可以在存储库下的相应文件夹中找到。您可以在以 3.2 开头的文件夹中找到第 3.2 节的代码,然后是主题:3.2-Preparing_data。每个文件夹都有一个“开始”和“结束”文件夹。如果要使用最少的代码重新开始,请使用启动文件夹。如果您遇到卡住,结束文件夹将显示该部分的“结束”结果。使用本章的代码文件时,在代码编辑器中仅打开一个开始或一个结束文件夹。如果一次打开章节的所有文件并使用 Live Server 扩展为项目提供服务,则某些路径将无法按预期工作,尤其是当我们将数据集加载到项目中时。
让我们回到第 2 章开始的条形图练习,并从 2021 年数据可视化 - 行业状况调查中加载我们的示例数据集。数据集采用 CSV 格式,位于 /data 文件夹中。您可以在清单 3.1 中看到文件数据的内容.csv。文件的第一行列出了列标题:技术和计数,分别代表调查中可用的数据可视化工具,从 ArcGIS 到 P5,以及每个工具有多少数据可视化从业者选择了每个工具,其中 D414 为 3 .js Python 为 530。
示例 3.1 数据从业者最常用的工具——数据.csvtechnology,count
ArcGIS,147
D3.js,414
Angular,20
Datawrapper,171
Excel,1078
Flourish,198
ggplot2,435
Gephi,71
Google Data Studio,176
Highcharts,58
Illustrator,426
Java,29
Leaflet,134
Mapbox,167
kepler.gl,24
Observable,157
Plotly,223
Power BI,460
PowerPoint,681
Python,530
QGIS,193
Qlik,61
R,561
React,145
Tableau,852
Vega,48
Vue,51
Web Components,79
WebGL,65
Pen & paper,522
Physical materials,69
Canvas,121
P5/Processing,55
由于我们的数据集是一个 CSV 文件,我们可以使用函数 d3.csv() 将其加载到我们的项目中,将数据文件的路径作为第一个参数传递。知道我们的数据文件位于 /data 文件夹中,main.js 的相对路径是 “../数据/数据.csv“ .使用两个点(“..”),我们回到一个级别,这将我们带到/js文件夹之外,在项目的根目录。然后我们进入 /data 文件夹并到达文件数据.csv .
d3.csv("../data/data.csv");
好吧,这几乎完成了我们数据工作流程的第 2 步,但现在,我们需要了解如何访问数据以执行格式化和测量步骤。重要的是要知道加载数据是一个异步过程。异步操作是结果不可用的请求。我们需要等待 D3 获取数据,然后才能读取或操作它。我们可以安全地知道数据已经完成加载并准备好通过 d3.csv() 的回调函数和/或使用 JavaScript Promise 来访问它。
3.2.2 格式化数据集图 3.9 D3 数据工作流的第三步最初包括设置数据的格式,以便在开始构建可视化效果时可以使用数据。
d3.csv() 的回调函数,也称为行转换函数,允许逐行访问数据。在下面的代码片段中,d3.csv() 的第一个参数是数据的路径,第二个参数是回调函数,我们将数据记录到控制台中。将此代码段复制粘贴到 main.js 中并保存您的项目。
const svg = d3.select(".responsive-svg-container")
.append("svg")
.attr("viewBox", "0 0 1200 1600")
.style("border", "1px solid black");
d3.csv("../data/data.csv", d => {
console.log(d);
});
打开浏览器的检查器,然后转到控制台选项卡。您将看到,如图 3.10 所示,数据一次记录一行,每一行都是一个包含技术和计数的 JavaScript 对象。
图 3.10 从 d3.csv() 回调函数登录控制台的获取数据(部分)
请注意计数列中的值是如何作为字符串而不是数字提取的。这是导入数据时的常见问题,这是由于数据集的类型从 CSV 转换为 JSON。由于 d3.csv() 的回调函数使我们能够一次访问一行数据,因此它是将计数转换回数字的好地方。这样做将确保计数值已准备好用于以后生成可视化效果。
在下一个代码段中,我们返回一个包含技术和计数键值对的对象,而不是将每个数据行记录到控制台中。这些值通过点表示法与 d 参数一起提供。使用 d 参数,我们将遍历先前登录到控制台的对象(图 3.10)。然后我们可以通过以下方式访问该技术 d.技术 ,以及计数 d.count 。最后,我们使用 运算符将计数转换为数字。
d3.csv("../data/data.csv", d => {
return {
technology: d.technology,
count: d.count
};
});
重要的是要知道,回调函数中返回的键值对是数据集完全加载后您唯一可以访问的键值对。此策略可能是从原始数据集中删除不需要的列的有效方法。但是,如果数据集包含大量列,并且您确实需要保留所有列,则逐个返回键和值可能是多余的。在这种情况下,你可能希望跳过回调函数的工作,并在 D3 返回完整数据集后执行格式化。我们将在下一节中讨论如何访问它。
3.2.3 测量数据集图 3.11 在第三步的第二部分,我们可以测量和探索数据。
虽然逐行检索数据很有用,但我们还需要访问整个数据集。这就是JavaScript Promise发挥作用的地方。Promise 是异步操作的结果,存储为对象,如 d3.csv() 返回的对象。检索 Promise 的一种简单方法是使用 .then() 方法。在下面的代码片段中,我们将 .then() 方法链接到 d3.csv() 。一旦数据完全加载,承诺就实现了,完整的数据集在 .then() 方法的回调函数中可用。将完整的数据集记录到控制台中并保存您的项目。
d3.csv("../data/data.csv", d => {
return {
technology: d.technology,
count: d.count
};
}).then(data => {
console.log(data);
});
在控制台中,您将看到数据集已转换为对象数组,每个对象都是原始 CSV 数据集中的一行。这样,D3 使数据可迭代,这对于开发可视化非常有用。我们还可以确认计数值是否已正确转换为数字。如果查看数据数组中的最后一项,将会看到 D3 公开了 CSV 数据集中的列标题。虽然我们不需要它来构建我们的条形图,但这个数组有时可以派上用场。
图 3.12 完整数据集表示为登录到控制台的对象数组
在数据工作流的步骤 3.a 中,我们已经完成了数据格式化部分,但我们仍然可以使用 D3 探索和测量数据。测量数据的特定方面有助于在深入研究数据可视化的实际制作之前确定位置。
虽然没有关于在哪里继续的严格规定,但我们的数据承诺的 then() 方法是对数据集进行初始探索的好地方。我们可能想知道的第一件事是它包含多少技术。为此,我们可以直接查看数据数组的长度属性。如果我们在控制台中记录 length 属性,我们将获得 33,这意味着我们的条形图将有 33 个矩形。
d3.csv("../data/data.csv", d => {
...
}).then(data => {
console.log(data.length); // => 33 #A
});
我们可能还想知道哪种技术在我们的调查数据中最受欢迎,以及有多少数据从业者表示他们经常使用它。对于最不受欢迎的人也是如此。您可以使用方法 d3.max() 和 d3.min() 获取这些值。如以下代码片段所示,它们采用两个参数。第一个是迭代对象,我们想知道最大值或最小值,因此 Promise 返回的数据。第二个参数是一个访问器函数,我们在其中指定要比较数据对象中的哪个键的值,这里是 计数 .
如果我们将最大值和最小值记录到控制台中,仍然在 Promise 的 then() 方法中,我们得到 1078 和 20 .请注意,我们也可以使用方法 d3.extent() ,它采用相同的参数并返回一个包含最小值和最大值的数组。
d3.max(data, d => d.count) // => 1078
d3.min(data, d => d.count) // => 20
d3.extent(data, d => d.count) // => [20, 1078]
了解数据中的最大值和最小值有助于我们粗略地想象我们的条形需要在图表中存在多长时间,以及最高值和最低值之间的差异是否容易在屏幕上表示。
条形图通常按降序显示数据。它使它们更易于阅读,并允许观众一目了然地知道哪些技术比其他技术使用得更多或更少。JavaScript sort() 方法允许我们非常轻松地做到这一点。它将比较函数作为参数,如您在下一个代码片段中看到的那样,它比较了两种技术的计数值,表示为 a 和 b 参数。如果 b 的计数大于 a 的计数,则 b 应该出现在排序数组中的 a 之前,依此类推。
data.sort((a, b) => b.count - a.count);
您可以在 then() 方法中对数据进行排序。如果您将其登录到控制台,您将看到 Excel 位于技术列表的顶部,计数为 1078,其次是 Tableau,计数为 852。条形图中的最后一个技术将是 Angular,计数为 20。
图 3.13 按降序排序的技术数据集
模块 d3-array (https://github.com/d3/d3-array) 包含许多其他测量和转换数据的方法,其中一些我们将在本书中探讨。但是 d3.max() 、d3.min() 和 d3.extent() 可能是你最常使用的。
完成数据加载、转换和测量后,通常的做法是将数据集传递给另一个负责构建可视化的函数。在示例 3.2 中,您可以看到 main.js 在这个阶段的状态,以及在 then() 方法结束时我们如何调用数据并将其传递给函数 createViz()。我们将从下一节开始使用此函数。
示例 3.2 加载、转换和测量数据 - main.js
const svg = d3.select(".responsive-svg-container")
.append("svg") #A
.attr("viewBox", "0 0 1200 1600") #A
.style("border", "1px solid black"); #A
d3.csv("../data/data.csv", d => { #B
return { #C
technology: d.technology, #C
count: d.count #C
}; #C
}).then(data => {
console.log(data.length); // => 33 #D
console.log(d3.max(data, d => d.count)); // => 1078 #D
console.log(d3.min(data, d => d.count)); // => 20 #D
console.log(d3.extent(data, d => d.count)); // => [20, 1078] #D
data.sort((a, b) => b.count - a.count); #E
createViz(data); #F
});
const createViz = (data) => {}; #G
在结束本节之前,您可以在图 3.14 中找到到目前为止讨论的数据加载、行转换和 Promise 概念的概述。策略如下:
图 3.14 在 D3 中如何以及在何处加载、转换和测量数据
3.3 将数据绑定到 DOM 元素现在,我们准备介绍 D3 最令人兴奋的功能之一:数据绑定。通过数据绑定,我们可以将数据集中的对象耦合到 DOM 元素。例如,条形图中的每个矩形元素都将与一项技术及其相应的计数值相结合。在数据工作流的数据绑定步骤中,可视化真正开始栩栩如生。对于可视化开发人员来说,这总是一个快乐的时刻!
图 3.15 D3 数据工作流的第四步包括创建数据并将其绑定到 DOM 元素,这些元素将成为可视化的核心。
要绑定数据,您只需要使用下一个代码片段中显示的模式,该模式由链接到一个选择的三个方法(selectAll() 、data() 和 join() )组成。
selection
.selectAll("selector")
.data(myData)
.join("element to add");
让我们使用条形图练习来解释数据绑定模式。在我们的条形图中,数据集中的每一行都需要一个矩形元素,也称为基准面。使用数据绑定模式,我们告诉 D3 每个矩形元素都应对应于一个基准面。
回到main.js并在函数createViz()中调用与SVG容器对应的选择并保存在名为svg的常量中。选择是将添加矩形的位置。现在,将 selectAll() 方法链接到选择,并将我们要添加的元素类型作为参数传递,即 SVG 矩形元素。您可以将任何 CSS 选择器传递给 selectAll() 方法,但使用元素类型是常见的。
const createViz => (data) {
svg
.selectAll("rect")
};
您可能想知道为什么我们要选择甚至还不存在的元素!这就是我们所说的空选择。它告诉 D3:“准备好将矩形元素添加到 DOM”。但是 D3 还不知道需要添加多少个矩形。这就是为什么我们链接 data() 方法并将我们的数据集作为参数传递的原因。现在 D3 知道它需要为数据中的每一行创建一个矩形元素。
svg
.selectAll("rect")
.data(data)
最后,矩形使用 join() 方法进入 DOM。
svg
.selectAll("rect")
.data(data)
.join("rect")
保存您的文件并查看检查器中的 DOM。SVG 容器现在包含 33 个矩形,数据集中的每种技术对应一个矩形。
图 3.16 添加到 DOM 的数据绑定矩形
图 3.17 说明了数据绑定过程。我们从一个选择开始,这里是 SVG 容器。然后我们通过告诉 D3 我们将使用 selectAll() 方法添加矩形来创建一个空选择。我们将数据集传递给 data() 方法。最后,D3 通过 join() 方法为每个基准面附加一个矩形。数据绑定完成后,选择将成为元素和数据的组合。每当我们重用或操作此选择中的元素时,我们都可以访问它们的相应数据!
图3.17 数据绑定过程
另一种数据绑定模式如果您查看 Web 上的 D3 示例,无疑会遇到略有不同的数据绑定模式,其中使用 .enter().append() 而不是 .join() 。
selection
.selectAll("selector")
.data(myData)
.enter().append("element type");
虽然 .enter().append() 方法仍然有效,但从 D3 v6 开始,它主要被 .join() 取代。
在后台,join() 方法不仅根据数据计算要添加到选择中的元素数量。它实际上考虑了有多少新元素正在进入 DOM,有多少正在退出以及有多少正在更新。这种更复杂的模式在交互式可视化中特别强大,其中可视化中显示的数据正在不断发展。通过处理数据绑定的所有这些方面,join() 比以前的方法更易于使用。
我们将在第7章讨论这种更复杂的方法。现在,重要的是您知道以前版本的 D3 使用的数据绑定模式略有不同,并且您可能会满足这些示例。
3.3.1 使用数据动态设置 DOM 属性我们之前提到过,在将 CSV 文件加载到我们的项目中后,D3 将其转换为可迭代的数据结构,因此是一个对象数组。然后,我们将可迭代数据结构中的每个对象绑定到一个矩形元素。这个绑定数据不仅向 DOM 添加了正确数量的矩形元素,而且当我们使用内联或访问器函数操作矩形时,也可以访问它。
让我们看看它在我们的条形图上的应用。在数据绑定模式之后,将 attr() 方法链接到矩形选择。我们将使用它向每个矩形添加一个类属性,但不是简单地将值作为第二个参数传递,而是输入访问器函数。
如以下代码片段所示,访问器函数的结构与任何 JavaScript 函数一样,并返回类的值,即“bar”或要赋予矩形的任何类名。
svg
.selectAll("rect")
.data(data)
.join("rect")
.attr("class", d => {
console.log(d);
return `bar bar-${d.technology}`;
})
访问器函数公开参数 d ,表示基准面,这是绑定到每个矩形的数据。如果将 d 登录到控制台,您将看到每个包含技术和计数的基准对象一个接一个地记录,就像我们在矩形及其数据中循环一样。
模板文本与串联字符串在前面的代码片段中,我们使用了模板文字,也称为模板字符串,用反引号 ( '' ) 分隔。它们用于将传统的JavaScript字符串与表达式组合在一起,表达式前面有一个美元符号,并用大括号(${expression})括起来。
使用模板文本与串联字符串
您可能更熟悉串联字符串,这是一种将表达式与字符串组合的较旧但正确的方法。如上图所示,在串联字符串中,字符串用引号 ( “” ) 括起来,并使用加号 ( ) 与表达式连接。这两种方法都是可以接受的,但由于模板文本的可读性增强,它们正在成为常态。
这种访问绑定数据的方式有利于设置每个矩形的位置和大小。我们知道我们希望在条形图中垂直堆叠矩形,如图 3.18 所示。每个矩形的 width 属性表示使用工具的从业者数,存储在绑定数据的计数键中。矩形越长,使用的技术越多,反之亦然。另一方面,height 属性是恒定的,每个矩形之间有一点垂直空间。
图 3.18 查找每个矩形左上角位置的公式如果我们将条形的高度存储到一个名为 barHeight 的常量中,我们可以按如下方式设置矩形选择的宽度和高度属性。请注意 width 属性如何使用数据访问器函数来获取绑定到每个矩形的计数值。
const barHeight = 20;
svg
.selectAll("rect")
.data(data)
.join("rect")
.attr("class", d => {
console.log(d);
return "bar";
})
.attr("width", d => d.count)
.attr("height", barHeight)
然后,我们需要通过计算矩形的 x 和 y 属性来设置矩形的位置,这些属性表示它们在 SVG 容器坐标系中的左上角的位置。如果您回顾图 3.18,您将看到矩形与 SVG 父项的左边框对齐,这意味着它们的 x 属性始终为零。
对于 y 属性,我们需要执行一个小的计算。第一个矩形的左上角位于 SVG 容器的顶部,其中 y 等于零。第二个矩形位于第一个矩形的下方,其距离对应于条形的高度加上一点间距。请记住,SVG 元素的 y 坐标是从上到下的!第三个矩形再次较低,位于 y 位置,对应于两个矩形的高度加上这些矩形之间垂直间距的两倍。在图3.18中,我们可以看到一个模式正在形成。每个矩形的 y 位置对应于它之前的矩形数乘以条形的高度和垂直间距的组合。
要在 y 属性的访问器函数中进行此计算,我们可以访问第二个参数,通常名为 i ,用于索引。我们已经说过,在访问器函数中,就好像我们在循环访问绑定元素的数据一样。在 JavaScript 循环中,我们通常可以访问每个项目的索引,对应于它们在循环数组中的位置减去 5(数组在 JavaScript 中索引为零)。在下面的代码片段中,我们使用索引来计算每个矩形的垂直位置,并在每个矩形之间留出 <>px 的空白空间。
const barHeight = 20;
svg
.selectAll("rect")
.data(data)
.join("rect")
.attr("class", d => {
console.log(d);
return "bar";
})
.attr("width", d => d.count)
.attr("height", barHeight)
.attr("x", 0)
.attr("y", (d, i) => (barHeight 5) * i)
在访问器函数中,我们使用 JavaScript 箭头函数(ES6 语法)。当仅使用一个参数(例如类和 width 属性)时,它不需要括号。当使用多个参数时,它们需要括在括号中,例如 y 属性的 (d,i)。此外,分布在多行上的访问器函数需要正文大括号 ({}) 和 return 语句,如 class 属性,而简单的单行函数不需要它们,如 width 属性。图3.19总结了这些规则。
图3.19 格式化箭头函数
保存您的项目并查看矩形的位置,如图 3.20 所示。这开始看起来像一个条形图!
图 3.20 使用数据定位和调整大小的矩形
提示在下一节中,我们将学习波段刻度如何为我们计算每个柱线的垂直位置。但是知道如何自己确定柱线的位置是一项有价值的练习。当我们构建 D3 项目时,我们经常需要对屏幕上元素的位置进行如此小的计算。适应它很重要。一开始可能并不容易,但通过练习,你会掌握它的窍门!进行此类计算的最佳方法之一是在一张纸上从可视化中绘制一些元素,并找到它们在 SVG 父项坐标系中的位置,如图 3.18 所示。本练习将帮助您更好地了解可视化是如何构建的,这在处理复杂的“开箱即用”可视化时特别方便。
现在,我们将通过使用柱线的填充属性为条形提供蓝色,从而使我们的图形更加愉快。在下面的代码片段中,我们为他们提供了 CSS 颜色名称“skyblue”。如果您愿意,可以随意使用另一种颜色。
svg
.selectAll("rect")
.data(data)
.join("rect")
...
.attr("fill", "skyblue");
作为最后一步,让我们询问绑定到矩形的数据,以识别与 D3.js 对应的矩形。为此,我们使用 JavaScript 三元运算符来检查绑定到当前矩形的技术是否为 “D3.js” 。如果是这种情况,则 CSS 颜色“黄绿色”被赋予 fill 属性,否则使用“天蓝色”。
...
.attr("fill", d => d.technology === "D3.js" ? "yellowgreen" : "skyblue");
图 3.21 D3.js对应的条形图为绿色,其他条形图为蓝色。
我们的条形图正在真正形成。目前,我们直接使用数据来设置每个矩形的宽度。但这种方法并不总是实用的。想象一下,如果数据中的数字是数百万的数量级,我们将无法直接使用这些值。在下一节中,我们将介绍比例,这是我们将数据值映射到 D3 项目中的视觉属性的方式。
3.4 调整屏幕数据当我们创建数据可视化时,我们将数据转换为可视变量,例如元素的大小、颜色或它在屏幕上的位置。在 D3 项目中,此转换使用比例处理。
图 3.22 在 D3 数据工作流的最后一步中,我们使用比例将数据值转换为屏幕属性,如长度、位置和颜色。
3.4.1 比例scales 是从数据中获取值作为输入,并返回可直接用于设置数据可视化元素的大小、位置或颜色的输出值的函数。更具体地说,输入数据是域的一部分,域是可能的数据值的范围。在屏幕上,该域映射到一个范围,即可能的输出值的频谱。
D3 具有许多缩放函数,可接受不同类型的域和范围。让我们以线性尺度函数 d3.scaleLinear() 为例。要初始化 scale 函数,我们需要链接 domain() 和 range() 方法。domain() 方法将可能的数据值范围作为参数,从数组中指定的最小值到最大值。range() 方法将相应输出值的数组作为参数。
const myScale = d3.scaleLinear()
.domain([0, 200])
.range([0, 20]);
然后可以像任何JavaScript函数一样调用scale。我们从域中传递一个值作为参数,scale从域返回相应的值。
myScale(100) => 10
刻度的输入和输出值可以是连续的,也可以是离散的。连续值可以存在于预定频谱中的任何位置,例如 0 到 100 之间的浮点数或 2020 年 2021 月至 <> 年 <> 月之间的日期。您可以将定量数据视为值的滑动比例。另一方面,离散输入和输出具有一组预定的值,例如 XS、S、M、L 和 XL 尺寸的 T 恤集合或一组颜色,如“蓝色”、“绿色”、“黄色”和“红色”。处理定性数据就像将物品扔进不同的盒子里。这些物品只能放在一个盒子里。
在D3中,定量数据通常耦合到具有连续域的尺度上。相反,定性数据意味着一个离散域,通常是可能值的数组。同样,具有连续输出的刻度允许指定范围内的任何值,而离散输出将从预定义列表中返回值。
基于连续与离散输入和输出的概念,我们可以将D3量表分为四个系列:
假设我们在 Google 上搜索 2021 年发布的系列,并将前十个结果分组到数据集中。然后,我们检索有关每个系列的类型、它们在烂番茄上的热门评分、专业评论家给出的平均分数以及它们可用的平台的信息。
表3.2 谷歌推荐的2021年上映的电视剧标题 | 类型 | 观众评分 (%) | 评论家评分 (%) | 平台 |
九个完美的陌生人 | 戏剧 | 59 | 62 | 主要 |
女仆 | 戏剧 | 88 | 94 | 网飞 |
卡特拉 | 戏剧 | 78 | 100 | 网飞 |
木星的遗产 | 行动 | 73 | 40 | 网飞 |
肇事逃逸 | 行动 | 72 | 82 | 网飞 |
非正规军 | 犯罪 | 54 | 80 | 网飞 |
影与骨 | 行动 | 89 | 88 | 网飞 |
点击诱饵 | 犯罪 | 64 | 56 | 网飞 |
性/生活 | 喜剧 | 34 | 23 | 网飞 |
时间之轮 | 行动 | 64 | 82 | 主要 |
资料来源:rottentomatoes.com |
该数据集如表3.2所示,既包含连续值(如观众和评论家的分数),也包含离散值(如流派和平台)。要创建图 3.23 中的条形图,我们需要使用前面列出的每个系列的 D3 刻度。您无需构建此条形图,它仅说明了不同类型的刻度以及何时使用它们。
图 3.23 2021年电视剧的评论家和热门评论 - 每个家庭的尺度可视化
在条形图上,每个条形对应于一个系列,条形的长度与评论家给出的平均分数成正比。为了计算柱线的长度,我们使用第一个系列的 D3 刻度,该标度将连续值作为输入,评论家的分数,并返回连续值作为输出,即相应柱线的长度。
此量表将具有介于 0 和 100% 之间的域(评论家分数中的潜在值范围)和相应的条形长度范围(以像素为单位)。
domain => [0, 100] possible min and max values of the input, in %
range => [0, 500] related min and max values of the output, in pixels
条形的颜色代表每个系列的类型。为了给条形提供适当的颜色,我们需要第二个系列中的刻度,该刻度将离散值作为输入,流派,并返回离散值作为输出,相应的颜色。此比例的域是流派的数组,范围是相关颜色的数组。
domain => ["Drama", "Action", "Crime", "Comedy"] possible inputs
range => ["purple", "blue", "green", "yellow"] corresponding outputs
在每个条形的顶端,表情符号代表热门评论的分数。得分高于 80% 的系列会得到一个心眼表情符号,得分在 70% 到 80% 之间的系列有一个笑脸,得分在 60% 到 70% 之间的系列有一个中性的脸,分数低于 60% 的系列有一个鬼脸。在这里,我们有一个连续的输入,流行的评论,和一个离散的输出,相关的表情符号。
domain => [60, 70, 80] thresholds of the input values
range => ["⊙﹏⊙", "●_●", "◠‿◠", "♥‿♥"] corresponding output
最后,条形沿图形的垂直轴分布。为了计算每根柱线的位置,我们需要第四个系列的刻度,该刻度采用离散输入、序列标题,并返回连续输出,即沿垂直轴的位置。
domain => ["Nine Perfect Strangers", "Maid", "Katla", ...] list of inputs
range => [0, 500] related min and max values of the output, in pixels
每个秤系列都包含多个秤功能。在撰写本书时,d20-scale模块(https://github.com/d3/d3-scale)中有超过3个比例函数可用。在本章中,我们将介绍函数 d3.scaleLinear() 和 d3.scaleBand(),因为它们通常用于 D3 项目,并且我们需要它们来完成我们的条形图。在整本书中,我们将涵盖许多其他尺度。有关所有可用比例函数的概述以及帮助您为项目选择正确功能的决策树,请参阅附录 B(即将推出)。
3.4.2 线性刻度毫无疑问,我们在开发 D3 项目时最常使用的尺度类型是线性尺度 ( d3.scaleLinear() )。此刻度将连续域作为输入,并返回连续范围的输出。
const myLinearScale = d3.scaleLinear()
.domain([0, 250])
.range([0, 25]);
线性刻度的输出与其输入成正比,如图3.24所示。在前面的代码段中,域涵盖 0 到 250 之间的任何值,而相应的输出范围包含 0 到 25 之间的值。如果我们调用这个参数为 100 的 scale 函数,它返回 10。同样,如果我们传递值 150,则返回 15。
myLinearScale(100) => 10
myLinearScale(150) => 15
图 3.24 线性刻度的输出与输入成正比
让我们回到条形图练习。在上一节中,我们使用数据中的计数值来设置每个矩形的宽度属性。它工作正常,因为计数是相对较小的数字,但使用刻度将数据中的值转换为 SVG 属性通常更实用。
为了说明这一点,假设可视化的 SVG 容器的大小为 600 x 700 像素,而不是 1200 x 1600 像素。在 main.js 中更改 SVG 容器的视图框属性以反映此新的宽高比。
const svg = d3.select(".responsive-svg-container")
.append("svg")
.attr("viewBox", "0 0 600 700")
...
还要修改 div 的最大宽度属性的值,在 main.css 中使用一类响应式 svg 容器。
.responsive-svg-container {
...
max-width: 600px;
...
}
如果您保存项目并转到浏览器,您将看到图形的前三个条形大于 SVG 容器,并且它们的提示是隐藏的。我们将使用线性刻度来解决此问题,该刻度会将计数值映射到 SVG 容器中的可用空间,同时为标签留出可用空间。
我们首先声明一个名为 xScale 的常量,因为刻度将负责沿 x 轴调整元素的大小和定位。然后我们调用函数 d3.scaleLinear() 并链接 domain() 和 range() 方法。
数据集中可能的计数值从零(理论最小值)扩展到 1078(对应于 Excel 作为数据可视化技术的最高计数)。请注意,我们使用零而不是数据集中的实际最小计数。像在大多数图形中一样,我们希望我们的 x 轴从零开始。我们将最小值和最大值作为数组从我们的域传递给 domain() 方法([0, 1078])。
现在,我们需要评估可用的水平空间,从而评估尺度的范围。图 3.25 显示了图形的前五个条形。您应该还看不到项目图表左侧和右侧的标签。我们已将它们添加到此插图中,以说明为什么我们需要额外的空间。
我们已经知道 SVG 容器的总宽度为 600px。我们希望在左侧为技术标签留出 100px 的可用空间,在右侧为计数标签留出 50px 的可用空间。这意味着条形的长度可以在 0 到 450px 之间。
图 3.25 评估条形的可用水平空间
我们现在可以声明 xScale ,域在 0 到 1078 之间变化,范围在 0 到 450 之间。在函数 createViz() 中的数据绑定代码之前添加线性刻度。
const createViz = (data) => {
const xScale = d3.scaleLinear()
.domain([0, 1078])
.range([0, 450]);
// Data-binding
...
}
我们已经提到,我们可以像任何其他JavaScript函数一样调用D3规模。我们将域中的值作为参数传递,该函数从范围内返回相应的值。例如,如果我们将值 1078 传递给 xScale ,对应于 Excel 的计数值,该比例将返回 450 .如果我们通过 414 ,即使用 D3 的从业者数量,则刻度返回 172.82 ,对应于 D3.js 的条形的宽度(以像素为单位)。
xScale(1078) // => 450
xScale(414) // => 172.82
通过登录控制台,将秤返回的输出记录数据集中的几个值,亲自尝试一下。
图 3.26 由线性刻度映射到条形宽度的数据中的计数值
现在我们的比例已经声明,我们可以开始使用它来计算条形图中每个矩形的宽度。查找设置矩形宽度属性的代码行。就像在下一个代码片段中一样,不要直接使用计数值,而是调用 xScale() 并将计数值作为参数传递。还将 x 属性的值更改为 100,以将矩形向右平移,并为图 3.25 中所示的技术标签留出空间。
svg
.selectAll("rect")
.data(data)
.join("rect")
...
.attr("width", d => xScale(d.count))
...
.attr("x", 100)
...
保存项目并注意条形图在 SVG 容器中的适应方式,以及如何为条形图两侧的标签保留空白区域。
您现在知道如何使用 D3 秤了!尽管 D3 中提供了许多不同类型的刻度,但如何声明和使用它们的原理仍然相似,从一个刻度切换到另一个刻度只需要知道域接受的数据类型和范围。
3.4.3 带音阶条形图需要的第二种刻度是波段刻度。乐队音阶来自第四科。它们接受离散输入并提供连续输出,对于在可用空间内分布条形图的矩形特别有用。
要声明一个带刻度,我们调用函数 d3.scaleBand() 。在下面的代码片段中,我们将比例保存到一个名为 yScale 的常量中,因为这个刻度负责沿 y 轴分布元素。我们的频段尺度域是一个包含数据集中所有技术的数组。我们使用 JavaScript map() 函数生成这个数组。(如果您需要复习我们何时以及如何使用 map() 函数,请返回第 1.2.5 节。然后,我们的范围涵盖了所有可用的垂直空间,从 SVG 容器顶部的零到 SVG 容器底部的 700px。
const yScale = d3.scaleBand()
.domain(data.map(d => d.technology))
.range([0, 700]);
在数据绑定之前,在 createViz() 中添加带刻度。当使用数据集中的技术调用时,波段刻度返回一个与条形图上的垂直位置相对应的数字。例如,如果我们将字符串“Excel”传递给 yScale ,它返回零。这是有道理的,因为对应于 Excel 的条形是图形顶部的第一个条形图。类似地,如果我们调用 yScale 传递值 “D3.js” ,它返回 272.72 ,这是对应于 D3 的柱左上角的垂直位置。
yScale("Excel") // => 0
yScale("D3.js") // => 272.72
您还记得我们之前为设置矩形的 y 属性而必须执行的计算吗?多亏了带标尺,我们现在可以通过将绑定到每个矩形的技术名称传递给 yScale 来非常轻松地设置此属性。
svg
.selectAll("rect")
.data(data)
.join("rect")
...
.attr("y", d => yScale(d.technology))
...
带刻度还有一个非常方便的方法, bandwidth() ,它返回条形的粗细,它与条形的数量和可用空间成正比。在我们的条形图中,此厚度对应于矩形的高度属性。您可以在下一个代码片段中看到如何在带标度上调用 bandwidth() 直接返回 height 属性。
svg
.selectAll("rect")
.data(data)
.join("rect")
...
.attr("height", yScale.bandwidth())
...
保存您的项目并在浏览器中查看它。如图 3.27 所示,条形覆盖了 SVG 容器中可用的所有垂直空间,但它们之间没有填充使图形看起来局促且难以阅读。
图 3.27 使用带刻度分布且不带填充的条形图
我们可以通过设置带刻度的 paddingInner() 属性来修复它,该属性指定每个波段之间的填充量并接受 0 到 1 之间的值。在这里,我们给它一个值 0.2,表示波段高度的 20%。
const yScale = d3.scaleBand()
.domain(data.map(d => d.technology))
.range([0, 1000])
.paddingInner(0.2);
完成后,我们的条形图布局会呼吸更多。那就好多了!
图 3.28 使用带刻度分布的条形图,带填充
图3.29概述了频段刻度的工作原理。首先,它从我们的数据集中获取一个域,即技术列表,并将其分布在SVG容器中可用的垂直空间范围内。每个矩形左上角的垂直位置可以通过调用 scale 函数并将技术作为参数传递( yScale(“PowerPoint”)) 来检索。类似地,我们可以通过调用刻度的带宽方法(yScale.bandwidth() )来获取条形的高度。最后,默认情况下,条形之间的填充为零。我们可以通过设置带刻度的 paddingInner() 属性并为其指定一个介于 3 和 0 之间的值来告诉 D1 每个波段之间所需的填充量。
图3.29 波段刻度如何在可用垂直空间内分布技术列表
3.5 为图表添加标签我们的条形图几乎是完整的,但目前无法知道哪个矩形对应于哪种技术以及条形长度代表哪些值。我们将通过向图表添加两组标签来纠正此问题。第一组标签将是条形左侧列出的技术名称。第二个将是与每个条形相关联的计数,并位于矩形的末尾。
在基于 SVG 的可视化中,我们使用 SVG 文本元素制作标签。我们将有两个文本元素与每个矩形组合在一起,并将每个矩形及其相关标签嵌套到 SVG 组中。如果您还记得我们在第 1 章(第 1.2.2 节)中关于 SVG 组的讨论,我们使用组将多个元素作为一个元素移动。它们也可以方便地将绑定数据传递给它们的后代,正如我们将在这里观察到的那样。
让我们从重构我们的代码开始。首先,注释掉与矩形元素属性相关的所有行。我们将在几分钟内重用它们。在 JavaScript 中,单行注释以两个正斜杠 ( // ) 开头,而多行注释以 /* 开头并以 */ 结尾。
现在返回到数据绑定代码段。不要将数据绑定到矩形上,而是使用 SVG 组 ( g )。我们还将选择保存在一个常量命名的栏和标签中。
const barAndLabel = svg
.selectAll("g")
.data(data)
.join("g");
为了使矩形及其标签一起移动,我们将使用 transform 属性对每个组应用垂直平移。transform 属性的 translate 属性采用两个参数:水平平移(我们将其设置为零)和垂直平移(对应于每个柱线的垂直位置)。请注意我们如何调用 yScale 函数来查找此位置,就像我们之前对矩形所做的那样。
const barAndLabel = svg
.selectAll("g")
.data(data)
.join("g")
.attr("transform", d => `translate(0, ${yScale(d.technology)})`);
尽管 SVG 组没有图形表示,也不作为有界空间存在,但我们可以将它们想象成封装其所有子元素的框。由于 transform 属性,这些组分布在 SVG 容器的垂直高度上。矩形和标签的位置将相对于其父组。
图 3.30 位于 SVG 容器内并封装其后代矩形和标签的组现在我们的组已经准备就绪,让我们重新添加矩形。调用条形常量并将矩形元素附加到其中。
const barAndLabel = svg
.selectAll("g")
.data(data)
.join("g")
.attr("transform", d => `translate(0, ${yScale(d.technology)})`);
bar
.append("rect");
由于条形图选择包含多个组元素,每个基准面一个,因此 D3 了解它需要向每个组添加一个矩形元素。保存您的项目并使用检查器工具查看标记。确认组和矩形已添加到 DOM 中。
图 3.31 附加到组中的矩形元素
现在,您可以取消注释矩形的属性方法,并将其应用于新添加的矩形元素。数据绑定的巧妙之处在于将绑定的数据传递给组的后代元素。我们仍然可以像以前一样访问数据。唯一的区别是,由于垂直平移已应用于组,因此矩形的 y 属性可以设置为零。
barAndLabel
.append("rect")
.attr("width", d => xScale(d.count))
.attr("height", yScale.bandwidth())
.attr("x", 100)
.attr("y", 0) #A
.attr("fill", d => d.technology === "D3.js" ? "yellowgreen":"skyblue");
您的矩形现在应该在条形图上可见,并且看起来与以前完全相同(参见图 3.28)。
我们已准备好添加标签!再次调用条形选择,并将文本元素追加到其中。这将向每个组添加一个文本元素。我们希望标签显示与每个矩形相关的技术名称。为此,请将 text() 方法链接到所选内容。此方法接受一个参数:要添加到 SVG 文本元素的文本。在这里,我们根据绑定到每个元素的数据动态设置文本。
barAndLabel
.append("text")
.text(d => d.technology);
然后,我们使用文本元素的 x 和 y 属性定位每个标签。在水平方向上,我们希望标签的末尾与每个矩形的开头对齐。由于矩形从 100px 开始,我们可以说标签应该在 96px 左右结束,在标签的末尾和相关矩形的开头之间留下 4px。我们还使用值为 end 的文本锚点属性来使标签右对齐。这意味着 x 属性表示每个标签末尾的位置,如图 3.32 所示。
图3.32 计算技术标签的位置
在垂直方向上,每个标签的位置相对于其父组。我们需要稍微向下移动它们,直到它们与条形居中,请记住文本元素是垂直定位的,而不是相对于它们的基线。在这里,我们应用12px的翻译。请注意,x 和 y 属性的值不是凭空而来的。我们通过粗略地了解要显示标签的位置并测试一些值直到找到正确的值来找到这些数字。浏览器的检查器是进行此类小调整的好地方。
barAndLabel
.append("text")
.text(d => d.technology)
.attr("x", 96)
.attr("y", 12)
.attr("text-anchor", "end");
最后,我们可以根据自己的偏好并使用 style() 方法设置标签的字体系列和字体大小属性。这里我们使用大小为 11px 的无衬线字体。您可以在图 3.33 中看到结果。
barAndLabel
.append("text")
.text(d => d.technology)
.attr("x", 96)
.attr("y", 12)
.attr("text-anchor", "end")
.style("font-family", "sans-serif")
.style("font-size", "11px");
图 3.33 带技术标签的条形图
现在,我们可以在矩形的尖端添加一个标签,表示在调查中选择技术的次数。该过程与用于技术标签的程序非常相似。首先,我们调用包含组选择的 bar 常量,并将另一个文本元素附加到每个组中。每个标签的文本通过 text() 方法设置为每种技术的计数值。
barAndLabel
.append("text")
.text(d => d.count)
由于计数标签位于每个矩形的尖端,我们可以通过调用 xScale() 来计算它们的水平位置,它返回条形的长度。我们还在条形图(4px)的末尾添加了一些填充,并考虑到矩形左侧有100px的空间。在垂直方向上,计数标签也以 12px 向下推。
图3.34 计算计数标签的位置
barAndLabel
.append("text")
.text(d => d.count)
.attr("x", d => 100 xScale(d.count) 4)
.attr("y", 12)
最后,我们使用 style() 方法设置字体系列和字体大小属性。请注意,计数标签 (9px) 的字体大小如何小于其中一个技术标签 (11px)。我们以这种方式继续维护两种类型的标签之间的视觉层次结构。较大的标签将首先引起注意,观众将了解计数标签是次要的,而不是技术标签。
barAndLabel
.append("text")
.text(d => d.count)
.attr("x", d => 100 xScale(d.count) 4)
.attr("y", 12)
.style("font-family", "sans-serif")
.style("font-size", "9px");
作为最后一步,让我们在条形的左侧添加一条垂直线,以在视觉上将图形固定在一起。在下面的代码片段中,我们将该行附加到 SVG 容器中。行 (x1, y1) 的起始位置在 (100, 0) , SVG 容器的顶部,其结束位置 (x2, y2) 在 (100, 700) 的容器底部。我们还需要指定笔触的颜色,以使线条可见。
svg
.append("line")
.attr("x1", 100)
.attr("y1", 0)
.attr("x2", 100)
.attr("y2", 700)
.attr("stroke", "black");
如果从 SVG 容器中删除边框,则条形图应类似于图 3.36 和 Github 托管项目 (https://d3js-in-action-third-edition.github.io/most-popular-data-visualization-technologies/) 中的条形图。在我们的图表中,垂直线和技术标签充当轴。为了给轴和标签腾出空间,我们通常使用 D3 边距约定,我们将在下一章中介绍这个概念,并在本书的其余部分使用。
图 3.35 完成条形图(https://d3js-in-action-third-edition.github.io/most-popular-data-visualization-technologies)
恭喜你读到本章的结尾,这是一个密集的章节!如果您还没有掌握我们讨论过的所有概念,请不要太担心。我们将继续在不同的环境中使用它们,它们很快就会成为第二天性。
3.6 小结Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved