[译]如何学习 D3.js 之入门(上)
in 数据可视化 with 0 comment

[译]如何学习 D3.js 之入门(上)

in 数据可视化 with 0 comment

原文地址:How to learn D3.js

简介

所以,你想在 web 上创建经验的数据可视化,你总是听到 D3.js。那么 D3 是什么呢? 你是怎么学的? 让我们从这个问题开始:D3 是什么?

D3.js 看起来是一个包罗万象的框架,但实际上它只是一些小模块的集合。所以学习D3,就是学习D3中的模块。下面是所有的模块: 每个模块都被可视化为一个圆,面积越大,体积越大。

D3.js Modules

数据抓取

在可视化数据时,我们首先需要做的是什么?

当然是获取数据!

有许多方法可以访问 web 页面上的数据: 从在 Javascript 文件中硬编码数据到查询数据库。

最简单的方法之一是将其存储在一个静态文件中并获取(fetch)它。

虽然我们可以使用原生 Javascript api 从文件中获取文本,但是我们需要自己解析它。

grabbing data

d3-fetch

d3-fetch 提供了一些实用的方法,可以从文件中获取数据并将其解析为 Javascript 对象。

d3-fetch 能够解析文件与几种不同的数据格式:

d3-fetch 中,这些方法只是文件格式的名称,并且它们使用一个参数:文件的 URL。当你执行这些方法,他们会返回携带resolve 解析后数据的Promise 🙌

如果你不熟悉 Promise,可以看看这个很棒的视频解释The Coding TrainMDN 文档

例如,要获取挪威奥斯陆的当前天气,我们可以查询 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 有一个非常方便的模块:

manipulating data

d3-array

让我们先谈谈 d3-array 没有涵盖的内容:

那么我们可以用 d3-array 做什么呢?

基本数据统计

d3-array 中有 11 种方法可以帮助解决关于数据集的基本问题。

可能最常用的方法是找到最大值和最小值的方法(d3.max() / d3.min()),以及返回极值的方法(d3.extent())。这些工具在创建 scale 时非常有用(稍后介绍 scale)。

还有一些用于查找其他统计信息的方法,如 d3.mean()d3.median()d3.quantile()d3.variance()d3.deviation()

要使用这些方法中的任何一个,我们需要使用两个参数来调用它:

  1. 数据集合 dataset
    第一个参数需要是一个包含我们感兴趣的值的数组
  2. 访问器函数(可选)
    第二个参数将告诉 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 还可以通过两种主要方式帮助查找数组中的特定项:

例如,我们可以找到数字 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 包中还不可用:
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

Manipulating the DOM

文档对象模型(DOM)是 web 页面上的元素树。

假设您想为数据集中的每一项在 DOM 中添加一个元素。我们可以只使用 for 循环,这样做工作量很大,但不会太难。

现在,假设我们的数据发生了变化,我们希望保持 DOM 中的元素与数据同步。嗯,如果只使用本地浏览器 api 和普通的 Javascript,就会有很多工作要做。

这时就需要 d3-selection 了。

d3-selection

d3-selection 有一个替代 document.querySelector() 的方法: d3.select()

这个方法创建了一个具有许多辅助方法的 d3 selection 对象

我们来创建一个!

让我们改变一个 idparagraph 的元素样式,并从一个数组添加多个 <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 指导的基本模式