原文地址:How to learn D3.js
简介
所以,你想在 web 上创建经验的数据可视化,你总是听到 D3.js。那么 D3 是什么呢? 你是怎么学的? 让我们从这个问题开始:D3 是什么?
D3.js 看起来是一个包罗万象的框架,但实际上它只是一些小模块的集合。所以学习D3,就是学习D3中的模块。下面是所有的模块: 每个模块都被可视化为一个圆,面积越大,体积越大。
数据抓取
在可视化数据时,我们首先需要做的是什么?
当然是获取数据!
有许多方法可以访问 web 页面上的数据: 从在 Javascript 文件中硬编码数据到查询数据库。
最简单的方法之一是将其存储在一个静态文件中并获取(fetch
)它。
虽然我们可以使用原生 Javascript api 从文件中获取文本,但是我们需要自己解析它。
d3-fetch
d3-fetch
提供了一些实用的方法,可以从文件中获取数据并将其解析为 Javascript 对象。
d3-fetch
能够解析文件与几种不同的数据格式:
-
JSON (JavaScript Object Notation)是一种看起来非常像典型 JavaScript 对象的文件格式。
{ "items": [ { "date": "2018-10-10", "weather": "cloudy" }, { "date": "2018-10-11", "weather": "sunny" } ] }
JSON 文件很容易阅读,因为它们读起来有点像正常的语音(今天的天气很奈斯)。它们还非常灵活,能够表示嵌套的数据结构。
-
DSV (delimiter-separated values) 是一种模仿经典表的文件格式。文本的第一行表示列是什么,其余的行是数据行。
date, weather 2018-10-10, cloudy 2018-10-11, sunny
每个列由特定的分隔符(delimiter)分隔。
D3 允许你指定分隔符,但也有针对两种最常见类型的特定方法:
虽然 dsv 文件可能有点难以阅读,但它们比 json 文件小得多,因为它们不重复列名——只需比较上面两个小示例即可。
在过去,我需要在这两种格式之间转换数据 —— 有一个很好的工具叫 CSV2JSON,它可能会派上用场。
-
misc
在 d3-fetch
中,这些方法只是文件格式的名称,并且它们使用一个参数:文件的 URL。当你执行这些方法,他们会返回携带resolve
解析后数据的Promise
🙌
如果你不熟悉
Promise
,可以看看这个很棒的视频解释The Coding Train或 MDN 文档。
例如,要获取挪威奥斯陆的当前天气,我们可以查询 MetaWeather API 并从解析后的响应中获取温度。
const url = "https://cors-anywhere.herokuapp.com/https://www.metaweather.com/api/location/862592/";
d3.json(url)
.then((res) => {
alert(`Current temperature: ${res.consolidated_weather[0].the_temp}°C`);
})
.catch(() => {
alert("Oh no, something horrible happened!");
});
d3-fetch
是一个非常小的库 — 只有 8 个文件,每个文件大约有 8 行代码。真正重要的事情发生在另一个模块:
在文档中了解更多关于
d3-fetch
的信息。
d3-dsv
d3 API 的优点之一是它非常模块化。这允许我们经常使用它的内在逻辑,如果我们想的话。
d3-dsv
有很多方法在 Javascript 对象和 dsv 格式之间转换。它还有一些命令行实用程序,用于在 JSON、dsv 和 dsv 之间使用不同的分隔符进行转换。
在文档中了解更多关于
d3-dsv
的信息。
操作数据
现在我们已经获取了数据,我们需要将其转换为一种我们可以使用的格式。在转换和查询数据时,d3 有一个非常方便的模块:
d3-array
让我们先谈谈 d3-array 没有涵盖的内容:
-
昂贵的数据操作
这应该在我们访问网站数据之前就发生,因为长时间运行的任务会降低用户设备的速度(特别是如果他们使用的是旧手机) -
原生 Javascript 方法
目前,普通的 Javascript 非常强大 —— 浏览器中已经有很多方法可以帮助转换数据。这是数组方法的完整 列表 。这些方法中有一些在 d3-collection 中已经包含,现在已经不推荐使用了,因为我们不再需要一个特殊的库了!
那么我们可以用 d3-array 做什么呢?
基本数据统计
在 d3-array 中有 11 种方法可以帮助解决关于数据集的基本问题。
可能最常用的方法是找到最大值和最小值的方法(d3.max()
/ d3.min()
),以及返回极值的方法(d3.extent()
)。这些工具在创建 scale
时非常有用(稍后介绍 scale
)。
还有一些用于查找其他统计信息的方法,如 d3.mean()
、d3.median()
、d3.quantile()
、d3.variance()
和 d3.deviation()
。
要使用这些方法中的任何一个,我们需要使用两个参数来调用它:
- 数据集合
dataset
第一个参数需要是一个包含我们感兴趣的值的数组 - 访问器函数(可选)
第二个参数将告诉 d3 如何在一个数据点内找到一个值。默认情况下,这是一个标识函数(d => d
),这意味着如果dataset
是一个值数组,那么这个参数是不必要的。
例如,让我们求出这一系列温度的平均值:
const dataset = [
{
date: "2019-10-10",
temp: 10,
},
{
date: "2019-10-11",
temp: 12,
},
];
const mean = d3.mean(dataset, (d) => d.temp);
alert(mean); // is 11
关于顺序
d3-array
还可以通过两种主要方式帮助查找数组中的特定项:
-
查找值最小的项。
d3.least()
将返回item
,而d3.leastIndex()
将返回item
的索引index
。 -
查找最接近特定值的项。
d3.bisect()
将获取一个排序的数组和一个值,并返回该值适合的索引。
例如,我们可以找到数字 12
在这个数组中的位置:
const array = [10, 20, 30, 40, 50, 60];
const nearestValueIndex = d3.bisect(array, 12);
alert(nearestValueIndex); // is 1
// compiled array: [10, 12, 20, 30...]
有一些类似的 bisect
函数,用于使用访问器或比较器函数(d3.bisector()
),或者指定索引是低于(d3.bisectLeft()
)还是高于(d3.bisectRight()
)现有匹配值。
这些 bisector 函数在创建工具提示时非常方便 —— 找到离用户光标最近的点是使图表易于交互的好方法。
还有两个方便的比较器函数可以使用: d3.ascending()
和 d3.descending()
Transformations
有一些 d3-array
函数看起来像是在 Lodash
这样的实用函数库中。像:
- 通过特定的键对一组对象进行分组: d3.group() 返回分组的对象,而 d3.rollup() 返回分组的索引
- 创建两个数组的排列列表: d3.cross()
- 通过拉取特定索引或键来创建一个新数组: d3.permute()
- 数组乱序: d3.shuffle()
- 创建两个数字之间的数组计数: d3.range()
- Zip 两个数组: d3.zip()
注意,下列方法在核心 d3 包中还不可用:
d3.quickselect(), d3.group(), d3.rollup(), d3.bin(), d3.count(), d3.minIndex(), d3.maxIndex(), d3.least(), d3.leastIndex(), d3.groups(), d3.rollups()
可以在 packages.json 和 Changelog 中检查。
为了使用这些新方法,请确保导入的是 2+ 版本的d3-array
。
装箱
还有一组 d3-array 方法,可以将数据集装箱。这对于创建柱状图表非常有帮助。
d3-random
有时数据可视化需要随机数 —— 比如当你需要生成测试数据时。我还使用随机数添加了抖动,以帮助在图表上间隔开点 -- 当抖动不代表任何数据的时候很有用!
我们的浏览器有一个内置的 Math.random()
函数,它适用于简单的用例。但是,如果希望随机数据具有结构呢?
让你创建具有特定分布的随机数。例如,正态分布(d3.randomNormal()
)有助于生成围绕特定值正态分布的数字。
例如,这个被抛弃的原型使用了 d3.randomNormal()
来分隔移动的破折号,它们大部分围绕单个向量分组,但有些破折号被进一步分隔开来,以使流量更容易解析。
其它
这些模块显式地专门用于创建/操作数据,但其他模块中也有专门的方法来满足特定类型的可视化。
例如,d3-hierarchy
有一个方法(d3.hierarchy()
),它将数据转换为特定的嵌套结构,然后可以对其进行可视化。
在具体的可视化部分,我们将在模块中更多地讨论这些用例驱动的数据操作函数。
操作 DOM
文档对象模型(DOM)是 web 页面上的元素树。
假设您想为数据集中的每一项在 DOM 中添加一个元素。我们可以只使用 for 循环,这样做工作量很大,但不会太难。
现在,假设我们的数据发生了变化,我们希望保持 DOM 中的元素与数据同步。嗯,如果只使用本地浏览器 api 和普通的 Javascript,就会有很多工作要做。
这时就需要 d3-selection 了。
d3-selection
d3-selection 有一个替代 document.querySelector()
的方法: d3.select()
。
这个方法创建了一个具有许多辅助方法的 d3 selection 对象。
我们来创建一个!
让我们改变一个 id
为 paragraph
的元素样式,并从一个数组添加多个 <div>
如下:
d3.select("#this-other-paragraph")
.style("color", "cornflowerblue")
.style("font-style", "italic")
.style("font-weight", "bold")
.selectAll("div")
.data([1, 2, 3, 4, 5])
.enter()
.append("div")
.text((d) => d);
可以新建一个 html 文件进行测试,引入 d3.js
,可以是本地文件或者 cdn:
<script src="https://d3js.org/d3.v4.min.js"></script>
然后复制代码在控制台 console 中操作,可以查看返回结构:
看到我们如何为数组中的每一项添加新元素了吗?想想看,在可视化数据时,这个功能有多么强大。
您还会注意到,我们使用了一个 .selectAll()
方法来选择多个元素。
现在让我们同步我们的新元素,当数据改变两秒后:
const paragraph = d3.select("#this-new-paragraph");
let array = [1, 2, 3, 4, 5];
paragraph
.style("color", "cornflowerblue")
.style("font-style", "italic")
.style("font-weight", "bold")
.selectAll("div")
.data(array)
.enter()
.append("div")
.text((d) => d);
setTimeout(() => {
array = ["this", "is", "new", "content"];
paragraph.style("color", "tomato");
paragraph
.selectAll("div")
.data(array)
.text((d) => d)
.exit()
.remove();
}, 2000);
漂亮!现在我们可以看到我们的 "data elements" 更新了!
您可能已经注意到,在第二次运行相同的代码时,内容的更新是不同的。🤔
在学习 d3 时,d3-selection 更新模式是一个常见的问题。在使用它之前,对它进行充分的理解是很重要的,所以请仔细阅读 Mike Bostock 指导的基本模式。
本文由 咻咻 创作,采用 知识共享署名4.0
国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Sep 11,2023