(原文地址:https://bost.ocks.org/mike/bar/,原文作者:Mike Bostock)
简介:出于个人学习目的,计划将 D3 的学习资料逐步翻译为系列文章,鉴于本人水平有限,如有不到或错误之处,欢迎大家指正,谢谢!
概述
如果你有一小拨数据,一个数组:
var data = [4, 8, 15, 16, 23, 42];
简而来说,一个柱状图就是比例上来正确呈现该数据的恰当图表。本篇教程将介绍如何使用 D3 Javascript 函数库来创建柱状图。首先,我们将介绍 HTML 版的原始数据法,然后是更复杂的 SVG 方法,最后提供一个带动态过渡的版本。
本篇教程假设您具备网页开发的基础知识:了解如何编辑页面并用浏览器查看、如何加载 D3 程序库等。你也会发现简单的从 CodePen 模板修改是最简单的开始方式。
选择一个元素
在一般的 JavaScript 编程中,通常一次只能处理一个元素,比如要创建一个 div
元素、设置它的内容,然后将其添加到body
:
var div = document.createElement("div");
div.innerHTML = "Hello, world!";
document.body.appendChild(div);
有了 D3 (当然 jQuery 等类似函数库也一样),您可以通过选择器来实现同样的功能,通过大批的处理选择结果并赋予它们丰富的操作功能,您可以任意处理器中想要的内容而不用重复数写代码。虽然这看起来像一个小小的挑战,消灭循环和其他程序流控制可以让您的代码更加整洁。
选择器可以通过很多种方式实现,最常见的是通过请求一个通过特殊符号构建的选择符
字符串(比如 div
或 .foo
等),比如创建一个单一元素的选择符:
var body = d3.select("body");
var div = body.append("div");
div.html("Hello, world!");
您也可以同样的处理多种元素:
var section = d3.selectAll("section");
var div = section.append("div");
div.html("Hello, world!");
修改操作的串接
另一种方便的选择法是通过对选择器的串接:选择函数,比如 selection.attr
返回当前的选择。这样,您可以继续对该元素进行操作,比如设置 body
的背景色和文字颜色:
var body = d3.select("body");
body.style("color", "black");
body.style("background-color", "white");
“body
,body
,body
!”反复的呼叫 body
法可以通过如下的操作串接消除:
d3.select("body")
.style("color", "black")
.style("background-color", "white");
注意,我们再也没有使用一个 var
类型声明,在进行毕要的操作之后,选择结果可以被丢弃。操作串接让您可以缩短代码,减少过多用于变量命名的时间消耗。
在学到方法串接的好处之后我们要留意,大多数操作都返回同样的选择内容,然而有些方法却不这样!比如,selection.append
返回仅包含新元素的选择,从而让您可以对新元素进行串接操作:
d3.selectAll("section")
.attr("class", "special")
.append("div")
.html("Hello, world!"); // 注意:本教程中刻意使用缩进来呈现结构关系。
方法串接只能让选择内容不断沉入文档结构内,此时使用 var
可以保留中间过程从而返回原状态:
var section = d3.selectAll("section");
section.append("div")
.html("First!");
section.append("div")
.html("Second.");
手动创建图表
现在,假设您需要抛弃 JavaScript 而写一个柱状图,反正在数据表中只有六个数据,所以手工写几个 div
并不是复杂的事情,根据数据设置好它们的宽度,不就完成了:
<!DOCTYPE html>
<html>
<head>
<style>
.chart div {
font: 10px sans-serif;
background-color: steelblue;
text-align: right;
padding: 3px;
margin: 1px;
color: white;
}
</style>
</head>
<body>
<div class="chart">
<div style="width: 40px;">4</div>
<div style="width: 80px;">8</div>
<div style="width: 150px;">15</div>
<div style="width: 160px;">16</div>
<div style="width: 230px;">23</div>
<div style="width: 420px;">42</div>
</div>
</body>
</html>
结果显示如下:
本图用一个 div
做容器,每个柱子采用一个子节点 div
,子 div
拥有蓝色背景色与白色前景色,呈现出了向右对齐的文字标签。当然您可以移掉容器 div
而让实现更简洁,但一般来说,您的页面在图标之外还有其他内容,表格容器可以让您不影响其他页面的情况下布局和样式化图表。
自动创建图表
手工绘制并不适合现实中绝大多数数据源,本文的目的正是教您如何自动通过数据创建图表,所以让我们从一个仅包含一个类别为 chart 的 div
的空页面开始,用 D3 创建同样的图表:
d3.select(".chart")
.selectAll("div")
.data(data)
.enter().append("div")
.style("width", function (d) { return d * 10 + "px"; })
.text(function(d) {return d;});
虽然部分代码看起来熟悉,我们引入了一个新概念:数据接合。下面让我们将它拆开,将以上简洁代码写成原本的加长格式,从而展现它的工作原理。
首先,我们使用类别选择器得到图表容器:
var chart = d3.select(".chart");
下一步,我们通过定义需要接合的部位来接合数据:
var bar = chart.selectAll("div");
数据接合,便是在数据改变时,使用标准模式来创建、更新和销毁元素。这感觉上有些奇怪,好处是您可以只学此单一的模式来改变页面,所以无论您构建静态图表还是动态渐变图表时都可以保持代码一致,就像您选择希望存在的元素时的初始选择器。(见“使用接合思考”)。
下一步,我们使用 selection.data
将数据接合到选择结果上:
var barUpdate = bar.data(data);
因为我们知道选择结果为空,其返回的 update
和 exit
选择结果同样为空,我们只需要处理包含了不存在的新数据的 enter
选择结果。
var barEnter = bar.Update.enter().append("div");
下面我们根据关联的数据为每个新柱子设置宽度 d
:
barEnter.style("width", function (d) {
return d * 10 + "px";
});
因为这些元素是通过数据接合创建的,每个柱子已经绑定了数据,我们通过传递一个计算宽度风格的函数来恰当设置柱子的尺寸。
最后,我们使用一个函数来设置每个柱子的文字标签:
barEnter.text(function (d) {
return d;
});
D3 的选择器操作符,比如 attr
,style
,property
,允许您将数值设置为常量(每个选择元素相同)和函数(每个单独计算)两种方式。如果某个属性必须基于元素绑定的数据计算,那么就使用函数来实现,如果每个元素都相同,那么就使用字符串或数值来赋值。
用缩放匹配画幅
以上代码的软肋,是我们使用固定数字 10
来转换每个柱子和数据的关系。该数字应当取决于数据集内容的分布区域(0到42)和图表所定制的宽度(420),当然,针对以上图表,我们选择 10
是没有问题的。
我们可以使用 线性缩放 功能让以上数量关系更加明确,并消灭魔术数字 10
,D3 的缩放功能输入为数据范围及显示尺寸:
var x = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, 420]);
虽然,在这里 x
看起来像个对象,它也是一个包含根据输入数值转换原始数据的函数。比如,传入 4 可以返回 40, 输入 16 返回 160。为了使用新的缩放,我们将手写的乘法转换代码变为呼叫该函数:
d3.select(".chart")
.selectAll("div")
.data(data)
.enter().append("div")
.style("width", function (d) {
return x(d) + "px";
})
.text(function (d) {
return d;
});
下一篇:创建柱状图,第二部分
本篇原始柱状图的呈现很容易实现,但很显然也是有限的。柱状图应当有格线来辅助数据对比,您也可能更喜欢垂直的柱状图,或者您想要其他不同的图表类型,比如饼状图和流状图等。为了更好的视觉表达,您需要下一篇我们将呈现的 SVG 技术。
个人笔记
本篇教程之中,D3 的原创作者通过“非图表”技术展示了 D3 的选择器与修改器等功能,然而,选择器与修改器仅仅是 D3 框架之中最基础部分的功能,作者书写本教程的目的应该是为读者打下开发的基础,而实际并未深入 D3 作为图形绘制系统的实质核心内容。
使用 div
绘制图表,可能是十年前 Web 技术没有发展到 HTML5 时,使用网站进行数据呈现的“权宜之计”。然而,当今 HTML5 技术将 Web 技术提高到了一定的高度,通过 svg
和 canvas
标签,如今的浏览器已经成为了强大的矢量绘图工具(当然,还有不可忽略的 webgl
图形接口)。
D3 在 v3 版本及之前,还是一个一体化开发的普通插件,在 v4 到来之时,D3 进行了技术上的革新,将原本一体的 Javascript 代码拆分成了几十个可以独立使用的模块,并已经完整提供了对 svg
绘图组件的全面支持。在 v5 到来之后,D3 完整支持了 canvas
标签,并且提供了两者可以随意选择的技术方案。
我们作为 D3 框架的学习者,特别是掌握了 jQuery 插件使用方法的读者们肯定感觉到了内容的枯燥。但是,作为未来的 D3 应用者,我们会在未来的开发使用中越来越发现这些基础知识的重要性,因为每一幅通过 D3 绘制的 Web 图表都必须使用以上所描述的内容。
在下一篇,作者将深入 D3 的 svg 支持功能之中,带领大家进入真正的 D3 领域,来体会高效的绘图技术和接口。