D3.js入门指南
本章涵盖以下内容:
◆ 搭建简易的D3开发环境
◆ 搭建基于NPM(Node Packaged Modules是Node.js的套件管理工具)的开发环境
◆ 理解D3风格的JavaScript
1.1 简介
本章旨在帮助读者初步认识并且运行D3.js。其中包含一些基本知识,比如什么是D3.js,如何搭建一个典型的D3.js数据可视化(data visualization)环境。还有一个专门的章节,解释了一些JavaScript中鲜为人知而D3.js又甚为倚重的特性。
什么是D3?D3是指数据驱动文档(Data-Driven Documents),根据D3的官方定义:
D3.js是一个JavaScript库,它可以通过数据来操作文档。D3可以通过使用HTML、SVG和CSS把数据鲜活形象地展现出来。D3严格遵循Web标准,因而可以让你的程序轻松兼容现代主流浏览器并避免对特定框架的依赖。同时,它提供了强大的可视化组件,可以让使用者以数据驱动的方式去操作DOM。
D3维基(2013年8月)
总的来说,D3是这样一个特殊的JavaScript库,它利用现有的Web标准,通过更简单的(数据驱动)方式来制作炫目的可视化效果。D3.js由Mike Bostock
[http://bost.ocks. org/mike/](http://bost.ocks. org/mike/)
制作。之前他制作过一个叫Protovis的数据可视化JavaScript库,如今它已经被D3.js取代。如果想了解更多诸如D3.js制作过程、影响Protovis和D3.js的相关理论这类的信息,可以看看下面的链接。而本书将着眼于如何使用D3.js来增强可视化。D3使用JavaScript实现数据可视化的方式比较特别,因此刚开始时可能会让人觉得有些难懂。我希望通过本书中的大量实例,其中有基础的,也有高级的话题,能够帮助大家更好更高效地使用D3。一旦理解了原理,使用D3就可以让数据可视化的效率和丰富程度产生指数化的增长。
更多有关制作D3的创意,可以参考MikeBostock于2010年在IEEEInfoVis发表的论文Declarative Language Design for Interactive Visualization,链接:
[http://vis.stanford.edu/papers/protovis-design] (http://vis.stanford.edu/papers/protovis-design)。
如果对于D3是如何制作的感兴趣,建议看看Mike Bostock于2011年在IEEE InfoVis发表的论文D3: Data-Driven Document,链接:http://vis.stanford.edu/papers/d3。
Protovis,D3.js的前辈,是Mike Bostock和斯坦福可视化组的Jeff Heer制做的,相关链接:[http:// mbostock.github.io/protovis/](http:// mbostock.github.io/protovis/)
1.2 搭建一个简易的D3开发环境
在开始使用D3之前,我们要做的第一件事是搭建一个开发环境。这节里,我们将告诉你如何在几分钟内搭建一个简单的D3开发环境。
1.2.1 准备阶段
在我们开始前,请确保你已经安装好一个文本编辑器。
1.2.2 搭建环境
我们先要下载D3.js。
1.我们可以在http://d3js.org/下载最新版本的D3.js,也可以在[https://github. com/mbostock/d3/tags](https://github. com/mbostock/d3/tags)下载之前的版本。另外,如果你对开发中的最新D3版本感兴趣,可以fork以下的代码库https://github.com/mbostock/d3。
2.下载并且解压缩后,我们得到了3个文件:d3.v3.js、d3.v3.min.js和许可证文件。在开发过程中,建议使用d3.v3.js,它可以帮你深入到D3库中跟踪调试JavaScript代码。把d3.v3.js和新建的index.html放在同一个文件夹里,index.html应该包含下面的代码。
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8](
<title>Simple D3 Dev Env</title>
<script type="text/javascript" src="d3.v3.js](</script>
</head>
<body>
</body>
</html>
如果是直接下载源代码或者tagged 版本,JavaScript文件的名字会略微不同,叫做d3.js,而非d3.v3.js。
我们已经搭建了一个最简单的D3数据可视化开发环境。现在可以用我们最喜欢的文本编辑器编辑那个html文件,还可以用浏览器打开它来检查可视化的效果。
可以在这里下载这个例子的源码:
[https://github.com/NickQiZhu/d3-cookbook/tree/master/ src/chapter1/simple-dev-env](https://github. com/NickQiZhu/d3-cookbook/tree/master/src/chapter1/simple-dev-env)。
1.2.3 工作原理
D3是个相当独立的程序库。它不依赖于特定浏览器提供的功能以及其他JavaScript库。实际上,你甚至可以通过简单的配置,让D3脱离浏览器而在诸如Node.js这样的环境中运行起来(后面的章节会更详细地介绍这点)。
如果你的浏览器是IE9,建议使用Aight兼容库和Sizzle selector engine。其中前者可以在这里下载:https://github.com/shawnbot/aight,而后者可以在这里下载:http://sizzlejs.com/。
html文件的头部信息中必须包含如下的编码格式声明。
<meta charset="utf-8](
上述声明会告诉浏览器和验证器在渲染页面的时候使用哪个字符集,否则浏览器就不会正确加载D3了,因为D3使用了一些utf-8里面的特殊字符,比如π。
D3是完全开源的。它使用了它的作者Michael Bostock自己定制的开源许可证。该许可证跟流行的MIT许可证十分类似,仅有一点不同,就是其明确声明了Michael Bostock的名字未经允许不可用来标记此软件的派生品,或者用以扩大此软件的派生品的影响。
1.2.4 更多内容
本书提供了大量的代码示例。所有的源码均可在GitHub上下载到([https:// github.com](https:// github.com))。
如何获取源码
最简单的方式就是直接克隆Git上的代码库([https://github.com.NickQiZhu/ d3-cookbook](https://github.com.NickQiZhu/ d3-cookbook)
)。如果你不打算构建开发环境,跳过这步即可。
如果你不熟悉Git,克隆(clone)很类似于其他的版本控制软件中的签出(checking out)。不过克隆所做的并非简单的签出。它把所有分支和历史拷贝到了你的本地机器,也就是把整个代码库都拷贝到了本地机器中,所以你完全可以使用这个拷贝过来的代码库,在本地环境中离线工作。
如果是首次安装Git,可以在这里找到Git客户端下载列表:[http://git-scm.com/ downloads](http://git-scm.com/ downloads)
,在这里还有针对不同操作系统的安装向导:[http://git-scm.com/book/ en/Getting-StartedInstalling-Git](http://git-scm.com/book/ en/Getting-StartedInstalling-Git)。
另一个使用Git和GitHub的方式是安装GitHub客户端,它提供了比Git更多样的功能。在作者编写本书时,GitHub仅仅提供了Windows版([http://windows.github. com/](http://windows.github. com/)
)和Mac OS版(http://mac.github.com/)。
安装完Git客户端后,运行下面的命令就可以把本书中所有的代码都下载到你的机器里。
> git clone git://github.com/NickQiZhu/d3-cookbook.git
如果你打算使用GitHub客户端(目前还没有中文版),在https://github.com/NickQiZhu/d3-cookbook页面点击Fork按钮(在页面的右上角),之后你就可以在GitHub客户端中看到这个代码库了。
1.3 搭建一个基于NPM的开发环境
如果你所在的项目是一个略复杂的数据展示项目,并且使用了为数不少的JavaScript库,那我们之前讨论的那个简单的解决方案可能就显得有些褚小杯大,不能胜任了。在这一节当中,我们将展示一个使用了NPM(Node Packaged Modules,实际上就是JavaScript库的代码库管理系统)的更加强大的系统。如果你像我一样没有耐心,想更快地尝试本书最带劲儿的部分,想学点秘传招式,完全可以跳过这部分,如果想搭建一个产品开发环境,再回来看看这部分也不迟。
1.3.1 准备阶段
我们开始前要先确保NPM已经安装好了。在安装Node.js时,NPM作为其中一部分也被安装了。可以在http://nodejs.org/download/
下载Node.js。选择适合你操作系统的Node.js,安装完毕后,在终端窗口运行如下npm命令。
> npm -v
1.2.14
如果前面的命令输出了NPM客户端的版本号,就说明安装成功了。
1.3.2 搭建环境
安装完NPM后,让我们创建一个包描述符文件,以便自动化一些手动安装的过程。
1.首先,在工程文件夹下,创建名为package.json的文件,包含下面的代码。
{
"name": "d3-project-template",
"version": "0.1.0",
"description": "Ready to go d3 data visualization project template",
"keywords": [
"data visualization",
"d3"
],
"homepage": "<project home page>",
"author": {
"name": "<your name>",
"url": "<your url>"
},
"repository": {
"type": "git",
"url": "<source repo url>"
},
"dependencies": {
"d3":"3.x"
},
"devDependencies": {
"uglify-js": "2.x"
}
}
2.定义package.json文件后,运行下面的命令。
> npm install
1.3.3 工作原理
package.json文件中绝大部分字段仅仅用于提供信息,比如name、description、homepage、author和repository。如果你打算把自己的库发布到NPM的代码库中,就要用到name和version字段。现在,我们在意的是dependencies和devDependencies字段。
◆ dependencies字段描述了你的工程所用到的库在运行时的依赖,它们可以使你的工程在浏览器中正常运行。在这个简单的例子中,对于d3我们只有一个依赖。d3是D3在NPM库中发布时使用的名字。版本号3.x标明该工程可以兼容任意大版本为3的发行版,并且NPM应该获取大版本为3的最新的稳定发布版本来满足依赖。
D3是个自满足的库,运行时对外部是零依赖。然而这并不意味着它不能跟其他JavaScript库一起运行。作者平时也使用一些其他的库以便让自己的工作容易些,比如JQuery、Zepto.js、Underscore.js和Backbone.js。
◆ devDependencies字段描述了库在开发时(编译时)的依赖。就是说,这个字段内罗列出来的库文件仅仅在构建工程时会用到,运行JavaScript工程时用不到它们。
关于NPM包JSON文件更详细的文档,可以参考这里https://npmjs.org/doc/json.html。
执行npminstall可以自动触发NPM去下载你工程中所引用的所有依赖,包括递归的下载依赖的依赖。所有的依赖库文件会被下载到node_modules文件夹中,该文件夹位于工程文件夹中的根目录里。这些完成以后,就可以创建一个HTML文件(跟我们之前创建的那个一样),HTML文件直接从node_modules/d3/d3.js来引用D3的JavaScript库。
本节中的代码可以在此处下载,里面包含了自动构建脚本。
[https://github.com/NickQiZhu/d3-cookbook/tree/master/src/chapter1/ npm-dev-env](https://github.com/NickQiZhu/d3-cookbook/tree/master/src/chapter1/ npm-dev-env)
工程中会有一些麻烦的地方,比如手动下载JavaScript库以时刻保持它们的版本更新。为了避免这些麻烦,使用NPM是个行之有效的方式,可以拯救你于水火之中。当然,一些聪明的读者可能已经发现了,使用这个方法可以把我们的“搭建环境”过程直接提升一个档次。想象一下,你正在构建一个巨大的可视化工程,其中包含了上千行的JavaScript代码,很明显我们这里所描述的简单的搭建方式满足不了这种情形。因为“模块化的JavaScript开发”这个话题足够写本书了,所以这里我们就不再讨论这方面的话题了,我们将把注意力放在数据可视化和D3上。如果你对这方面感兴趣,可以看看这一节的代码,里面演示了如果实现一个模块化的工程,当然也包含了自动构建脚本。在后面单元测试相关的章节中,我们会针对这个话题多讲一些,演示一下,可以在某些方面增强环境,以便运行自动化单元测试。
1.3.4 更多内容
前面提到过,你可以通过浏览器直接打开HTML文件来查看可视化的结果,不过这种方式有一些局限性。一但我们需要从其他文件中加载数据(后面的章节中我们就要这么做了,这也很像你平时工作中的情形),由于浏览器内建的安全机制,这种方式就不好用了。为了绕开这个安全限制,强烈建议搭建一个本地的HTTP服务器,用该服务器来维护HTML页面和数据文件,而非直接从文件系统加载。
搭建本地HTTP服务器
由于使用的操作系统不同,HTTP服务器的软件包不同,搭建HTTP服务器的方法也很不同。这里说几种比较流行的搭建方式。
Python简易HTTP服务器
这是我最喜欢的方式。如果你的机器上已经安装了Python,通常UNIX/Linux/Mac OS发行版上都有,直接运行下面的命令即可。
> python –m SimpleHTTPServer 8888
如果你拥有更新版本的Python,那么你也可以运行如下命令。
> python –m http.server
这个python小程序可以启动一个HTTP服务器,然后你就可以访问该程序所在文件夹中的所有文件。这是目前所有操作系统中运行HTTP服务器最简单的方式。
如果你的机器没有安装python,可以在这里下载http://www.python.org/getit/。现在所有的操作系统,诸如Windows、Linux,还有Mac,都支持它。
Node.js HTTP服务器
安装Node.js之后(前面的小节中所做的搭建开发环境练习,包含了相应的内容),就可以搭建http-server模块了。与Python简易HTTP服务器类似,通过该模块,你可以从任意的文件夹快速启动一个轻量级的HTTP服务器。
安装http-server模块。
> npm install http-server –g
命令中的-g参数会把http-server模块设置为全局的,这样你就可以在命令行里直接使用http-server命令。完成此步后,可以通过下面的命令,在任意文件夹内启动一个服务器。
> http-server .
该命令可以启动一个Node.js驱动的HTTP服务器,默认端口号是8080,也可以用 -p 参数指定一个端口号。
如果是在Linux/UNIX/Mac的操作系统中运行该命令,需要用sudo模式或者root权限来运行。
1.4 理解D3风格的JavaScript
对于那些习惯了过程式或者面向对象式的JavaScript风格的人来说,他们会感觉D3使用函数式的JavaScript编程风格有一些奇怪。本节会涵盖一些JavaScript中函数式编程最根本的功能性概念,以便对D3有个基本的理解,将来可以用D3的风格来编写可视化工程的代码。
1.4.1 准备阶段
在浏览器中打开下面文件的本地副本。
[https://github.com/NickQiZhu/d3-cookbook/blob/master/src/ chapter1/functional-js.html](https://github.com/NickQiZhu/d3-cookbook/blob/master/src/ chapter1/functional-js.html)
1.4.2 开始编程
现在让我们尝试更深入一点,了解一下JavaScript函数式方面的内容。请看下面的代码段,
function SimpleWidget(spec) {
var instance = {}; // <-- A
var headline, description; // <-- B
instance.render = function () {
var div = d3.select('body').append("div");
div.append("h3").text(headline); // <-- C
div.attr("class", "box")
.attr("style", "color:" + spec.color) // <-- D
.append("p")
.text(description); // <-- E
return instance; // <-- F
};
instance.headline = function (h) {
if (!arguments.length) h; // <-- G
headline = h;
return instance; // <-- H
};
instance.description = function (d) {
if (!arguments.length) d;
description = d;
return instance;
};
return instance; // <-- I
}
var widget = SimpleWidget({color: "#6495ed"})
.headline("Simple Widget")
.description("This is a simple widget demonstrating functional javascript.");
widget.render();
这段代码在页面上生成了下面图片中的示例。
函数式JavaScript简单示例
1.4.3 工作原理
尽管非常简单,但是不可否认,这段示例中的代码跟D3风格的JavaScript非常相似。这不是巧合,在JavaScript编程范型中,这叫做函数对象。跟很多有趣的话题一样,这个话题也能写一本书。不过在本节中,我会尝试尽量多讲一些这种特殊范型的东西,好让不理解D3语法的读者也能创建这种风格的库文件。正如D3的维基页面上所讲的那样,这种函数式编程风格给D3带来了很大的便利性。
D3的函数风格,使得多种组件插件之间的代码重用成为现实。
{--:}D3维基(2013年8月)
函数即对象
JavaScript中的函数是对象。跟其他的对象一样,函数只是键值对的一个集合。函数对象跟普通对象的区别就是,函数可以执行,函数还带有两个隐藏的属性,即函数上下文和函数代码。这两个隐藏属性有时候会给你一个大大的“意外惊喜”,如果你有着很深的过程式编程背景,这点可能更明显。不过这也是我们最需要注意的关键点了,要了解一下D3使用函数的奇怪方式。
JavaScript的大部分特性都显得有些不够“面向对象”,不过在函数对象这方面,JavaScript跟其他语言相比较应该更胜一筹。
现在我们心里有了这样的概念,那就再看一遍这段代码。
var instance = {}; // <-- A
var headline, description; // <-- B
instance.render = function () {
var div = d3.select('body').append("div");
div.append("h3").text(headline); // <-- C
div.attr("class", "box")
.attr("style", "color:" + spec.color) // <-- D
.append("p")
.text(description); // <-- E
return instance; // <-- F
};
在A、B和C行,我们可以看到instance、headline和description都是SimpleWidget这个函数对象的内部私有变量。可是render函数却是instance对象的一个方法,并且被定义为对象字面量。函数本身也是对象,它可以存储在对象/函数、其他变量、数组里,也可以作为函数参数。运行SimpleWidget的结果就是I行所写的,返回一个instance对象。
function SimpleWidget(spec) {
...
return instance; // <-- I
}
render函数中用到了一些我们还没讲过的D3中的函数,不过我们现在先不管它们,后面的章节中我们会详细讲解的。它们也只是渲染了一些可视化的东西,跟我们目前的话题没有太多的关系。
下载示例代码
可以从http://www.packtpub.com下载你购买的所有Packt图书的示例代码。如果是在其他地方购买的话,可以在http://www.packtpub.com/support注册一下,代码会通过邮件发送给你。
静态变量作用域
好奇的读者可能会问,这个示例中的变量作用域到底是怎样的啊?看上去好奇怪,render函数不仅访问了instance、headline和description,还访问了从SimpleWidget传进来的spec变量。这个怪异的变量作用域其实是由一个简单的静态作用域规则来决定的。可以把这个规则想象成这样:当查找一个变量引用时,该变量先被当成是一个本地变量。如果没有找到变量声明(比如C行中的headline),就继续在父对象里找(本例中,SimpleWidget函数就是静态的父对象,headline变量的声明在B行)。如果还是没有找到,就不断地重复这个过程,递归地去父对象里查找,一直到全局变量的定义那层。如果最后还是没有找到,就针对该变量生成一个引用错误。这样的作用域行为与大多数流行语言(诸如Java、C#)中的变量处理方式大不相同,可能需要一段时间适应,要是你觉得不习惯的话,也不用担心,练得多了,就习惯了。
对于有Java和C#背景的人,需要再提醒一下,JavaScript没有实现块作用域(block scoping)。我们这里描述的静态作用域规则,仅仅适用于函数/对象级别,不适用于块级别。
for(var i = 0; i < 10; i++){
for(var i = 0; i < 2; i++){
console.log(i);
}
}
对于上面这段代码,你可能觉得它会打印20个数字。其实在JavaScript里,这段代码会陷入到无限循环。因为JavaScript没有实现块级别的作用域,所以里面那层循环的i跟外面那层循环的i是同一个变量。于是里面的循环改变了i的值,导致外面的循环永远不会结束。
跟流行的原型编程中的伪类模式相比,这样的模式通常被称作函数模式。函数模式的优点是提供了更好的信息隐藏和封装。因为只能通过静态作用域规则里限定的那些嵌套定义的函数来访问私有变量(我们示例中的headline和description),所以SimpleWidget函数返回的对象就更加灵活,也更加健壮。
如果我们用函数式风格创建对象,并且该对象所有的方法都没有用this,那这个对象就是持久(durable)的 {![译者注:如果一个对象不包含外部可见的成员数据,并且其方法不会使用this,那么这个对象就是持久的.]}。持久对象就是许多功能行为的集合了。
{--:}(Crockfort D. 2008年)
可变参数函数
在G行有些奇怪的东西。
instance.headline = function (h) {
if (!arguments.length) h; // <-- G
headline = h;
return instance; // <-- H
};
你可能会问,G行的这个arguments从哪里来的?这个例子中从来没有定义过它。其实这个arguments是内建的隐藏参数,并可在函数执行时直接使用。arguments是一个数组,它包含了所在函数的全部参数。
实际上,arguments本身并不是JavaScript的数组对象。虽然它有length 属性,并可以用索引下标访问每个元素,但是它没有JavaScript中数组对象的那么多方法(比如slice、concat)。如果你想在arguments上使用JavaScript数组对象的方法,需要这样:
var newArgs = Array.prototype.slice.apply (arguments);
把这个隐藏的参数和JavaScript可以在函数声明时省略参数的功能结合起来,就可以写出instance.headline这种不需要指定参数个数的函数。在本例中,我们可以传一个参数h,也可以不传。因为如果没有传进来参数,arguments.length就返回0,headline函数就返回h,如果h有值,就变成了一个赋值操作。为了说清楚,我们看看下面这段代码。
var widget = SimpleWidget({color: "#6495ed"})
.headline("Simple Widget"); // 给headline赋值
console.log(widget.headline()); // 输出"Simple Widget"
这里你可以看到headline在参数不同的情况下,可以分别作为setter和getter(赋值操作和取值操作)。
函数级联调用
这个例子另一个有趣的地方是函数的级联调用。这也是D3库提供的一个主要的函数调用方式,因为D3库中的大多数函数都设计成了这种链式的结构,以便能提供简洁的、上下文连贯的编程接口。如果你理解可变参数函数的概念,就很好理解这个了。可变参数函数(比如headline函数)能同时作为setter和getter,当其作为setter时,返回instance对象,这就使得你可以在返回的instance上立即执行另一个函数,此即链式调用。
看下面这段代码。
var widget = SimpleWidget({color: "#6495ed"})
.headline("Simple Widget")
.description("This is ...")
.render();
这个例子中,SimpleWidget函数返回了instance对象(如I行所示)。那么,headline函数在这里是一个setter,同时也返回一个instance对象(如H行所示)。description函数执行后也返回instance对象。最后调用了render函数。
现在我们已经大概了解了JavaScript的函数式风格,有了一个可工作的D3开发环境,也准备好了使用D3提供的丰富功能来一试身手。在开始前,我还想讲几个比较重点的事情,即如何寻找、分享代码以及有困难时如何获取帮助。
1.4.4 更多内容
先让我们看几个有用的东西。
寻找、分享代码
在D3众多值得称赞的亮点中,有一个是比其他可视化工具提供了更加丰富的示例和教程,你可以从中汲取灵感。当我创建自己的开源可视化图表项目,还有写作这本书的时候,我在那些资源中获得了大量的灵感。我会在那些最棒的例子里,挑出一些列个清单出来。这个清单虽然不是百科全书,但却是个不错的入门参考。
◆ D3 gallery(https://github.com/mbostock/d3/wiki/Gallery),这里有不少有趣的例子,可以帮你在线查找D3的使用方法,有各种各样的图表、特定的技术,还有一些跟其他工具一起实现的示例。
◆ BioVisualize(http://biovisualize.github.io/d3visualization),算是一个分门别类的D3 gallery,可以帮你快速地在线查找你想要的例子。
◆ D3教程(https://github.com/mbostock/d3/wiki/Tutorials),有很多朋友不断更新提供的教程、讨论和文档,给你细致地演示了D3的使用方法。
◆ D3插件(https://github.com/d3/d3-plugins),可能有些你需要的功能是D3没有的。在你自己实践之前,先查查D3的插件库,它提供了各种常用的、不常用的功能。
◆ D3 API(https://github.com/mbostock/d3/wiki/API-Reference)是很不错的文档。这里你能找到D3所提供的全部功能、属性的详细说明。
◆ Mike Bostok's Blocks(http://bl.ocks.org/mbostock)是个D3示例站点,作者是Mike Bostock,这个站点里有很多有趣的例子。
◆ JS Bin(http://jsbin.com/ugacud/1/edit)是个在线的D3测试、实验环境。你可以很容易地通过该工具跟其他人分享一些简单的代码。
◆ JS Fiddle(http://jsfiddle.net/qAHC2/)跟JS Bin差不多,也是一个在线JavaScript代码分享平台。
如何获取帮助
即便有了这些例子、教程,还有书,你在实践的过程里仍然会遇到问题。不过D3有数目庞大并且活跃度不错的社区。一般情况,简单地“google”一下,就能找到满意的答案。要是没有也不用担心,D3还有强大的社区支持。
◆StackOverFlow上的D3.js([http://stackoverflow.com/questions/ tagged/d3.j](http://stackoverflow.com/questions/ tagged/d3.j)s):StackOverflow是最著名的免费技术主题问答社区站点,D3在StackOverflow上有专门的页面,可以帮你找到专家,快速地解决你的问题。
◆ D3 Google讨论组([https://groups.google.com/forum/?fromgroups#! forum/d3-js](https://groups.google.com/forum/?fromgroups#! forum/d3-js)):这是个官方的用户组,不单单有D3,还有一些其他相关的库。
本文摘自:《D3.js数据可视化实战手册》
QQ截图20150206132646.png