快学熟用D3(txt+pdf+epub+mobi电子书下载)


发布时间:2021-02-25 21:31:30

点击下载

作者:(德)菲利普·K.贾纳特(Philipp K.Janert)

出版社:机械工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

快学熟用D3

快学熟用D3试读:

前言

本书排版约定。

本书使用以下排版约定。

斜体(Italic)

表示新术语、URL、电子邮件地址、文件名和文件扩展名。

等宽字体(Constant width)

表示代码段以及段落中的程序元素,比如变量、函数名、数据库、数据类型、环境变量、语句以及关键字。

等宽粗体(Constant width bold)

显示应由用户按字面输入的命令或其他文本。

等宽斜体(Constant width italic)

表示将由用户提供的值(或由上下文确定的值)替换的文本。这个图标表示提示或建议。这个图标表示重要说明。这个图标表示警告或提醒。示例代码

可以从https://github.com/janert/d3-for-the-impatient下载补充材料(示例代码、练习等)。

这里的代码是为了帮助你更好地理解本书的内容。通常,可以在程序或文档中使用本书中的代码,而不需要联系O’Reilly获得许可,除非需要大段地复制代码。例如,使用本书中所提供的几个代码片段来编写一个程序不需要得到我们的许可,但销售或发布O’Reilly的配套CD-ROM则需要O’Reilly出版社的许可。引用本书的示例代码来回答一个问题也不需要许可,将本书中的示例代码的很大一部分放到自己的产品文档中则需要获得许可。

非常欢迎读者使用本书中的代码,希望(但不强制)你注明出处。注明出处的形式包含标题、作者、出版社和ISBN,例如

D3 for the Impatient,作者为Philipp K.Janert,由O’Reilly出版,书号为978-1-492-04677-6

如果读者觉得对示例代码的使用超出了上面所给出的许可范围,欢迎通过permission@oreilly.com联系我们。O’Reilly在线学习平台(O’Reilly Online Learning)近40年来,O’Reilly Media致力于提供技术和商业培训、知识和卓越见解,来帮助众多公司取得成功。

我们拥有独一无二的专家和革新者组成的庞大网络,他们通过图书、文章、会议和我们的在线学习平台分享他们的知识和经验。O’Reilly的在线学习平台允许你按需访问现场培训课程、深入的学习路径、交互式编程环境,以及O’Reilly和200多家其他出版商提供的大量文本和视频资源。有关的更多信息请访问http://oreilly.com。如何联系我们

对于本书,如果有任何意见或疑问,请按照以下地址联系本书出版商。

美国:

O’Reilly Media,Inc.

1005 Gravenstein Highway North

Sebastopol,CA 95472

中国:

北京市西城区西直门南大街2号成铭大厦C座807室(100035)

奥莱利技术咨询(北京)有限公司

要询问技术问题或对本书提出建议,请发送电子邮件至:bookquestions@oreilly.com

本书配套网站https://oreil.ly/D3-for-the-Impatient上列出了勘误表、示例以及其他信息。

要了解更多O’Reilly图书、培训课程、会议和新闻的信息,请访问:http://www.oreilly.com

我们在Facebook上的地址:http://facebook.com/oreilly

我们在Twitter上的地址:http://twitter.com/oreillymedia

我们在YouTube上的地址:http://www.youtube.com/oreillymedia致谢

我要感谢从一开始就对这个项目热情提供支持的Mike Loukides和Scott Murray。Giuseppe Verni、Jane Pong、Matt Kirk、Noah Iliinsky、Richard Kreckel、Sankar Rao Bhogi、Scott Murray和Sebastien Martel阅读了部分或全部手稿,测试了示例,并提出了许多重要的建议。Matt、Scott和Sebastien回答了许多问题并通过大量的通信分享了他们的独到见解。特别感谢Giuseppe Verni,他带着极大的兴趣和奉献精神阅读了整个手稿,提供了许多有用的建议。

本书取名D3 for the Impatient是为了致敬由Paul W.Abrahams和Bruce R.Larson合著的图书Unix for the Impatient(由Addison-Wesley出版)。第1章 引言

D3.js(或称D3,指Data-Driven Document,数据驱动文档)是一个JavaScript库,以操作文档对象模型(Document Object Model,DOM)树的方式向用户直观地展示数据信息,现已成为网络中信息图形的事实标准。

尽管它的受欢迎程度很高,但它的学习曲线并不平坦。当然,我本人并不认为D3很复杂,也并不认为其庞大的API阻塞了开发者的学习道路(虽然说确实不少,但它的API结构和设计都很棒)。相反,我认为新用户遇到的许多困难都来自于他们想得太多。因为人们经常看到由D3开发的令人惊艳的图表,所以开发者们自然而然地将其视为一个“图形库”,他们从内心深处认为D3安排了图基,从而为常见的绘图类型提供了高级支持。正是这种先入为主的期望让用户在设置元素的颜色时面对冗长的指令感到不爽,这个“选择集”(selection)功能是做什么的?我直接用canvas画不行么?

这里的错误就是D3不是图形库。相反,它是一个操作DOM树的JavaScript库。它的基本构建单元不是圆形或矩形,而是节点和DOM元素。它并不涉及在canvas上绘制图形形状,而是通过属性来设置元素的样式。元素的“当前位置”和canvas上的xy坐标没有相似性,而是从DOM树节点的选择集中获取。

接下来是让新用户望而却步的第二道坎。D3作为和网页相关的技术,同样也依赖着其他的网页技术,比如DOM API和事件模型、CSS选择器和属性、JavaScript对象模型,当然还有可缩放矢量图形(Scalable Vector Graphics,SVG)。在大多数情况下,D3没有很深入地使用这些技术,且其自身的设计也处处反映了底层API。这就让环境一下子变得复杂了起来。如果你已经对HTML5等现代Web技术栈很熟悉了,那么你就会觉得毫无压力,否则的话,没有特定的抽象思维可能会让你感到困惑。

值得庆幸的是,你无须深入研究所有这些基础技术。D3为了让用户易于理解提供了大量的统一封装。唯一一个需要了解的技术是SVG。你必须花时间来充分了解它,不仅要了解指代的元素,还要了解图形中控制信息组织的结构元素。本书的附录B中列出了必要的知识点。如果你不熟悉SVG,那么我真诚地建议你在阅读本书之前先看看附录——这真的很有用。1.1 谁适合读这本书

本书面向那些希望掌握D3的程序员和科研人员。这里假设你是一名不错的程序员并对数据和图形工作信手拈来。不过你无须对现代专业的网页开发有很深的了解。

以下是你应该了解的:

·至少了解一到两种编程语言(但不一定是JavaScript)并且有信心学习新语言的语法。

·熟悉现代编程概念,不仅是循环、条件和常用数据结构,还包括闭包和高阶函数。

·基本理解XML和文档结构。我希望你知道DOM以及它是如何将网页元素构建为树中节点的,但并不强求你一定要熟悉原始的DOM API或任何一个现代框架(比如jQuery)。

·熟悉简单的HTML和CSS(至少你应该能够识别和使用和

标签等),并且熟悉CSS的语法和机制。

但是如果你并没有什么耐心。比方说你对语言得心应手,但对于学习D3却很头大的话,那这本书就是你需要的!1.2 为什么选择D3

为什么D3能够获得程序员和科研人员——甚至是非Web开发人员的青睐呢?

·D3提供了一种通过Web构建图形的便捷方式。如果你从事的是数据和可视化工作,通常来说你会在绘图程序中输入数据,然后将结果保存成PNG或PDF,接下来创建一个带有标签的网页,以让他人看到你的工作。如若可以一键完成上面这些步骤,何乐而不为呢?

·更重要的是,D3简化了创建动画和交互式图形的方式。这一点也许不应该过分强调:同其他领域一样,科学可视化也可以从动画和交互当中受益——虽然众所周知这个目标在过去很难实现。它经常需要加入一些复杂或不匹配的技术(听过Xlib编程吗?),抑或是添加一些专业但昂贵的商业软件包。D3让你把上面的一切都抛诸脑后,将最现代的可视化需求呈现在你的面前。

·除了图形之外,D3是一个易学易用的框架,擅长做通用的DOM处理。如果你偶尔需要操作DOM,那么D3将满足你的需求,并且无须掌握其他框架和API来编写网页。该库的设计也很巧妙,作为一个模型,它提供了“开箱即用”的功能来处理常见的数据操作和可视化任务。

说了这么多,其中最无可比拟的是D3是一种开放技术,用户通过它可以创造无限的可能性。最棒的D3应用永远在路上,你就是它的缔造者。1.3 通过本书你会学到什么

本书将对D3进行全面而简洁的介绍,涵盖其大部分主要功能。

·本书致力于成为一站式资源整合点,为学习该框架的人员提供便利。本书涵盖了API文档及其他背景信息(如SVG、JavaScript、DOM,以及HTML canvas元素等),以飨读者。

·本书强调的是机制和设计概念,绝非照本宣科。我们希望读者深入地学习D3是为了把它实际应用到自己的项目中或是实现一些其他的新奇想法。

这本书就是你所需要的,拿起D3,做想而未做之事。

那学不到什么呢?

本书有意将内容限制在介绍D3的能力和机制方面。这意味着不包含如下内容:

·没有广泛的案例研究或实例演示。

·不介绍数据分析、统计或可视化设计。

·没有提到任何D3以外的JavaScript框架。

·基本不讨论当代Web研发。

最后两项稍微强调一下。这本书严格地只针对D3,没有任何参考或依赖于其他JavaScript框架或库。没错,这是有意为之的。我想让那些不熟悉或不习惯JavaScript但在其他方面颇有建树的读者也能了解D3。出于同样的原因,本书不讨论当代Web开发中的其他话题,尤其不会存在有关浏览器兼容性和相关话题的讨论。我们假设你[1]经常使用的是一个支持JavaScript且能够呈现SVG的现代浏览器。

其他不包括的内容就是D3对地理和地理空间信息的支持。尽管很重要,但是这个主题其实属于进阶内容,一旦清楚地掌握了D3的基础,那么从D3参考文档中学习也就水到渠成了。[1] 这就是D3的精神。正如D3官网所言:“D3并不会去做兼容,如果你的浏览器连标准都不支持,我们爱莫能助。”(https://github.com/d3/d3/wiki)1.4 如何阅读本书

本书采用循序渐进的方式,每一章都系统地介绍了新的知识。也就是说,在通读本书前半部分打下必要的基础之后,后面的章节可以按任何顺序阅读。以下是一些建议:

1. 除非你已经有了扎实的SVG基础知识,否则我强烈建议你先阅读附录B。这些是学习D3必备的知识。

2. 每个读者都应该从阅读第2章起手,并好好期待接下来要探讨的话题。

3. 第3章是必读部分,不要偷懒。选择集是D3中的主要组织概念。它不仅表示DOM树上的句柄,而且还管理DOM元素和数据集之间的关联。几乎每个D3程序都是从选择集开始的,当使用D3时,请务必理解它们的功能。

4. 严格来说,第4章所讲的事件处理、交互性和动画是选读内容。不过这些都是D3中最令人兴奋的功能,跳过它们也挺遗憾的。

5. 第5章很重要,因为它解释了一些基本的D3设计概念(如组件和布局),并介绍了一些常用的技术(SVG变形和自定义组件)。

6. 剩下的章节大部分可以按任何顺序阅读。特别指出第7章中详述的比例尺对象和第10章中关于数组处理的各种函数,它们虽然不太显眼,用途却极其广泛。1.5 术语

本节将解释本书其余部分中使用的一些特定术语。

1.5.1 D3 API中的术语

D3 API中采用了一些统一的术语,方便了开发人员。其中一些是常见的JavaScript习惯用法,不仅是D3的,不过非JavaScript程序员可能不太熟悉。我们把这些信息统一地放在一起,这样后面的内容就不会被干扰。

·D3主要是DOM树的访问层。其设计规则是不封装底层技术,着力于提供方便且通用的处理。比方说,D3并未引入抽象性的“圆形”或是“矩形”,而是让程序员直接通过SVG创建图形。这种方式的优点就是D3拥有了很强的适应性,不会依赖于某个特定的技术或是版本。缺点是,除了D3之外,程序员还要了解底层技术,因为D3并未提供完整的抽象层。

·由于JavaScript并不会强制规定函数格式,因此所有的参数都是可选参数。D3中的大多数方法都遵循下面这条用法:如果有参数,那么该方法充当setter(将相应的属性设置为传递过去的值);如果没有传参,则充当getter(返回属性的当前值)。若想完全删除属性,请在调用方法时传递null。

·作为setter调用时,函数通常返回对当前对象的引用,从而可以进行方法的链式调用(记住这个术语)。

·除了传递值以外,很多D3的setter方法还能够接受访问函数作为参数,这些方法可以返回一个值,用以设置相关属性。D3中访问函数的期望参数各有不同,但一组相关D3函数总是以统一的方式调用访问函数。它们的参数细节都分别由D3本身进行了整理记录。

·一些至关重要的D3工具都以函数方式实现。当作为函数调用时,它们实现自身的功能,但同时它们也是对象,有其自身的内部方法和内部状态(如第7章的比例尺对象,第5章的生成器和组件)。先将它们实例化,然后通过内部方法配置参数,最后再调用之,这是很常见的模式。通常来说,最后一步调用并不会使用显式的函数调用语法,而是采用JavaScript的一种方法来“整合”函数调用。函数对象被传递给另一个函数(如call()),后者提供所需的参数并最终计算函数对象本身。

1.5.2 API参考文档中的术语

本书中的D3 API参考文档将以表格的形式呈现。各个条目按照相关性进行排序。

·D3函数要么在全局的d3对象上调用,要么作为某个D3对象的内部方法调用,还有一些两种情况均可。如果一个函数是通过一个对象调用的,那么这个对象就被称为调用方法的接收者,函数内部的this就指向它。

·所有API的参考表格都在表头中指明了接收者的类型。表格并未显式地引用对象的原型。

·列表中的函数都尽量指示出每个参数的类型,许多函数可以接受不同类型的参数,因而并无真正统一的表示法。请尽量阅读文档来获得详细信息,如函数调用时,方括号[]就表示一个数组。可选函数参数在表格中并没有明显地表示出来。

1.5.3 代码示例术语

代码示例旨在演示D3的特性和机制。为了清楚地展示每部分的作用,我们尽可能地简化了它们。我们省去了大多数的“花样”,比如丰富的颜色和有吸引力的数据集。颜色只使用初始颜色,数据集也尽量简练。

另一方面,每个示例本身也都是完整的,可以在浏览器中运行并创建相应的图表。除了少数例子外,大部分示例都是完整的。因为我发现完整地显示简单的例子比只显示较长的例子中的“有趣的部分”要好,这样就不会有丢失整个上下文的危险。书中的代码很可靠,可以随意对它们进行扩展和更改。

1.命名术语

本书的示例变量命名遵循以下规定。

·用首字母代表单个对象:c代表圆形(circle),p表示点(point),等等。后面如果加上字母“s”则代表集合:cs代表圆形集合,ps则代表许多点的数组。

·一些常见的名字则按照常规显示:像素用px表示,比例尺用sc表示。生成器和组件用来“生成”某些东西的函数对象,因而以mkr表示。

·字母d通常用以表示匿名函数中的“当前内容”。在D3选择器中,d通常表示绑定到DOM元素的单个数据点。处理数组时,代表数组中的元素(ds.map(d=>+d))。

·数据集以data或ds表示。

·表示元素的选择器很常见,作为变量时用svg或g表示。

2.源文件组织

从第3章开始,我采用了一个习惯用法,即对于每个代码块,页面都应该已经包含了一个元素,该元素具有唯一的id,并且正确设置了width和height。然后,示例代码通过它的id属性选择这个SVG元素,并经常将其赋值给一个变量以供使用:var svg = d3.select( "#fig" );

这样就避免了使用通用选择器(如d3.select("svg"))所造成的模糊性,从而可以很容易地在一个HTML页面中包含几个示例。

每个图形都对应一个JavaScript函数,该函数创建动态组成图形的SVG元素。本书的构建函数名都以make...开头,后面使用目标SVG元素的id属性值。

除了第2章中的示例外,每章都有一个HTML页面和一个JavaScript文件。(除极个别情况外,不会直接在HTML页面中包含JavaScript代码。)

3.平台、JavaScript和浏览器

要运行示例,你需要运行本地或托管的Web服务器(参见附录A)。这些示例应该适用于任何现代支持JavaScript的浏览器。目前JavaScript有几个版本,除了3个例外,本书的代码示例只使用“经典”JavaScript(ES5,2009/2011年发布),没有其他任何框架或库。需要更新版本的JavaScript(ES6,2015年发布)的三个特性是:

·用于匿名函数的箭头表示法(参见附录C),适用于整个示例。

·解构赋值,如[a,b]=[b,a],在一些地方使用。

·若干使用D3包装器的Fetch API(参见第6章)访问远程资源的示例,它们依赖JavaScript的Promise对象。第2章 让我们开始画图吧

让我们通过一些示例来演示D3的部分功能,以使你能够立即着手处理实际的问题。本章的前两个示例将向你展示如何依靠数据文件创建标准的散点图和xy坐标轴。过程可能比较枯燥,但麻雀虽小五脏俱全,你可以很容易理解它们并举一反三到其他数据集。第三个例子的完成度略有欠缺,因为它的主要目的是向你展示在文档中引入事件处理和动画是多么易如反掌。2.1 第一个例子:单数据集图表

在开始研究D3之前,我们来看看示例2-1这个短小精干的数据集。使用D3将它绘制成图表可以让我们接触到许多基本概念。

示例2-1:一个简单的数据集(examples-simple.tsv)x y100 50200 100300 150400 200500 250

正如我在第1章中就已经提到过的,D3是一个JavaScript库,用以操作DOM树来可视化地表示信息。这表明任何D3图表至少包括2到3个组成部分:

·一个包括DOM树的HTML文件或者文档。

·一个JavaScript文件或内嵌的script标签,用以定义操作DOM树的命令。

·包含数据集的文件或其他资源。

示例2-2完整展示了HTML文件。

示例2-2:在HTML页面中定义SVG元素 ② ③

没错,HTML文件基本是空的!所有的操作都在JavaScript中进行。让我们快速浏览一下文档中发生了什么。

① 首先,文档加载了d3.js库。

② 然后,文档加载我们自己的JavaScript文件。这个文件包含了我们准备定义图形的所有指令。

③ 标签定义了onload事件处理程序,当浏览器完全加载了元素时,将触发该事件的处理程序。我们在JavaScript文[1]件examples-demo1.js中定义了makeDemo1()事件处理函数。

④ 最后,文档包含了一个600×300像素的SVG元素。我们能看到一个浅灰色背景的SVG元素,除此之外一无所有。

第三部分也是最后一部分是JavaScript文件,如示例2-3所示。

示例2-3:图2-1的命令function makeDemo1() { ① d3.tsv( "examples-simple.tsv" ) ② .then( function( data ) { ③ ④ d3.select( "svg" ) ⑤ .selectAll( "circle" ) ⑥ .data( data ) ⑦ .enter() ⑧ .append( "circle" ) ⑨ .attr( "r", 5 ).attr( "fill", "red" ) ⑩ .attr( "cx", function(d) { return d["x"] } ) ⑪ .attr( "cy", function(d) { return d["y"] } ); } );}

如果将这三个文件(数据文件、HTML页面和JavaScript文件)以及d3.js库文件放到一个公共目录中,然后再将页面加载到浏览器,这[2]时浏览器中应该呈现一个如图2-1所示的图。

让我们一步步了解这些JavaScript指令并讨论一下:

① 这段代码仅仅定义了一个函数makeDemo1(),它将在HTML页面完全加载完成后调用。

② 函数通过tsv()方法加载(或“获取”)数据文件。D3定义了数个函数来根据分隔符读取文件格式。tsv()函数适用于由制表符分隔的数据文件。

③ 同JavaScript Fetch API中的所有函数一样,tsv()函数返回一个Promise对象。Promise封装了数据结果和回调,并在数据加载完成准备处理时调用回调。Promise提供then()函数来注册需要调用的回调函数。(JavaScript Promise的内容请查看6.1节的内容页。)

④ 文件加载成功后调用的函数是一个匿名函数,它的参数就是数据文件的内容。tsv()函数的作用是将制表分隔符的文件内容作为JavaScript对象的数组返回。文件中的每一行都是一个对象,以输入文件的首行内容作为属性名。

⑤ 我们选择将图表渲染到DOM树中的元素位置。select()和selectAll()函数接受一个CSS选择器字符串(请参阅3.1节),并返回匹配的节点:select()只匹配第一个,selectAll()则返回匹配所有节点的集合。

⑥ 我们选择节点内的所有元素。这听起来有点荒谬,毕竟SVG中根本没有什么元素。不过selectAll("circle")执行起来也只返回一个(元素的)空的集合,所以这也就不算问题了。通过一种奇怪的方式调用selectAll()的目的是创建占位符(空集合),我们会在之后的操作中填充这个占位符。每当在图中添加新元素时都会采用这种方式,这在D3的应用过程中已是司空见惯。

⑦ 我们通过调用data(data)将元素集和数据集关联起来。这里有一个必须注意的关键点,这两个集合(一个是DOM元素,另一个是数据点)并非是彼此“批量”关联的。D3试图在DOM元素和数据点之间建立一对一的对应关系,每个数据点都通过一个单独的DOM元素表示,而DOM元素又从数据点的信息中获取其属性(如位置、颜色和外观)。事实上,在单个数据点及其关联的DOM元素之间建立并管理这种一对一的协作关系正是D3的一个基本特性。(我们会在第3章更详细地研究这个过程。)

⑧ data()函数返回和单个数据点关联的元素集合。在当前情况下,D3尚不能将每个数据点与元素相关联,因为还没有任何circle元素,因而返回的集合是空的。但是,D3对无法通过enter()函数与DOM元素相匹配的剩余数据点提供了访问方法。接下来的代码就是处理它们的。

⑨ 我们将一个元素放入第6行代码所选择的SVG元素集合中。

⑩ 设置一些固定的(即不依赖于数据的)属性和样式:半径(r属性)和填充色。

⑪ 根据每个圆关联的数据点的值获得它们的位置。每个元素的cx和cy属性是依照数据文件中的条目指定的。我们不提供固定的值,提供的是访问函数,根据数据文件的条目(即单行数据)返回该数据点的对应值。图2-1:一个简单的数据集图表

老实说,对于这样一个简单图表来说,这个过程其实挺痛苦的。或许你已经发现了这其中的端倪:D3不是一个图形库,更不是一个绘图工具。相反,它是一个DOM树的操作库,以此来可视化地表示信息。你会发现自己一直都在(通过选择器)操作DOM树的某些部分。你还会注意到D3在击键方面并不节俭,它需要用户操作属性值并逐一编写访问函数。与此同时,我还可以公平地说,代码虽然冗长,但干净整洁,可读性一点不差。

如果你跟着教程试验这些例子,可能会有一些新的发现。比方说,tsv()函数很挑剔:数据列必须使用制表符分割,也不会忽略空格,必须要有表示属性的表头行等。最后,在仔仔细细地检查了一遍数据集和图表之后,你就会意识到图表其实是错的——数据完全颠倒啦!究其原因,是因为SVG使用了“图形坐标”,水平轴按常规从左到右指示数据,但垂直轴的指示方向则是从上到下的。

在继续探索第二个示例前,我们把这些牢牢地记在心里。[1] 通过onload标签来定义事件处理程序有时会引起争议,因为它在HTML中嵌入了JavaScript代码。参见附录A和附录C了解现代替代方法。[2] 你应该能够通过将浏览器指向本地目录来加载页面和相关的JavaScript文件。但是浏览器可能拒绝以这种方式加载数据文件,因此在使用D3时通常需要运行Web服务器。有关这方面的建议请参阅附录A。2.2 第二个例子:双数据集图表

在第二个示例中,我们将使用示例2-4中的数据集。它看起来与前一个相差不大,若深入研究,就会发现其中的一些难点。这个文件不仅包含两个数据集(列y1和列y2)而且需要注意数据的取值范围。在前面的示例中,数据值可以直接作为像素坐标,而新数据集中的值需要通过比例尺缩放才能转换为有意义的屏幕坐标。我们得再加把劲了。

示例2-4:复杂一点的数据集(examples-multiple.tsv)x y1 y21.0 0.001 0.633.0 0.003 0.844.0 0.024 0.564.5 0.054 0.224.6 0.062 0.155.0 0.100 0.086.0 0.176 0.208.0 0.198 0.719.0 0.199 0.65

2.2.1 绘制符号和线

我们使用的页面和之前的示例2-2基本相同,除了把下面这行

换成

script的地址换了,那么onload事件处理程序也得给个新名字了:

script内容见示例2-5,所生成的图见图2-2。图2-2:示例2-4中的数据集基本图(见示例2-5)

示例2-5:图2-2中的代码内容function makeDemo2() { d3.tsv( "examples-multiple.tsv" ) .then( function( data ) { var pxX = 600, pxY = 300; ① var scX = d3.scaleLinear() ② .domain( d3.extent(data, d => d["x"] ) ) ③ .range( [0, pxX] ); var scY1 = d3.scaleLinear() ④ .domain(d3.extent(data, d => d["y1"] ) ) .range( [pxY, 0] ); ⑤ var scY2 = d3.scaleLinear() .domain( d3.extent(data, d => d["y2"] ) ) .range( [pxY, 0] ); d3.select( "svg" ) ⑥ .append( "g" ).attr( "id", "ds1" ) ⑦ .selectAll( "circle" ) ⑧ .data(data).enter().append("circle") .attr( "r", 5 ).attr( "fill", "green" ) ⑨ .attr( "cx", d => scX(d["x"]) ) ⑩ .attr( "cy", d => scY1(d["y1"]) ); ⑪ d3.select( "svg" ) ⑫ .append( "g" ).attr( "id", "ds2" ) .attr( "fill", "blue" ) ⑬ .selectAll( "circle" ) ⑭ .data(data).enter().append("circle") .attr( "r", 5 ) .attr( "cx", d => scX(d["x"]) ) .attr( "cy", d => scY2(d["y2"]) ); ⑮ var lineMaker = d3.line() ⑯ .x( d => scX( d["x"] ) ) ⑰ .y( d => scY1( d["y1"] ) ); d3.select( "#ds1" ) ⑱ .append( "path" ) ⑲ .attr( "fill", "none" ).attr( "stroke", "red" ) .attr( "d", lineMaker(data) ); ⑳ lineMaker.y( d => scY2( d["y2"] ) ); ㉑ d3.select( "#ds2" ) ㉒ .append( "path" ) .attr( "fill", "none" ).attr( "stroke", "cyan" ) .attr( "d", lineMaker(data) );// d3.select( "#ds2" ).attr( "fill", "red" ); ㉓ } );}

① 为了便于后面引用,我们将页面SVG区域的长度和宽度赋值给变量(px表示像素)。当然了,将尺寸属性完全放到HTML文件外,通过JavaScript去设置它也没问题(试试看)。

② D3提供了比例尺对象,用以将输入映射到输出范围。这里我们使用线性比例尺将数据值从它们的自然定义域按比例缩放到图表的像素范围内,其实D3库还提供了对数比例尺和幂比例尺,甚至还能够将数值范围映射到颜色从而制作伪色图或热力图,想要进一步了解的话请再往后看吧。比例尺是函数对象,你可以将数值传入而获得缩放后的值。

③ domin()方法和range()方法所规定的参数都是一个双元素数组。d3.extend()方法很方便,它接收一个对象数组,返回由数组中最大值和最小值组成的双元素数组(参见第10章)。为了从输入对象数组中提取所需的值,我们必须提供一个访问函数,这和示例2-4中的最后一步所做的类似。我们这里使用了箭头函数(“=>”——参见附录C)。

④ 数据集有三列且值域各不相同,因此我们需要分别与之对应的三个比例尺对象。

⑤ 对于垂直方向的轴,我们在定义比例尺对象时反转输出范围,从而符合SVG坐标系在垂直方向的反转规则。

⑥ 为第一个数据集添加符号并放入元素。

⑦ 新内容出现了:在添加任何图形元素之前,我们先放一个元素再设置一个id。这个元素应该是这样的:...

SVG的元素提供了一个逻辑分组,方便我们能够一起引用第一个数据集的所有符号,且区别于第二个数据集的符号(参见附录B和第5章的“元素是你的亲密战友”)。

⑧ 同前面一样,我们使用selectAll("circles")创建一个空的占位符集合。元素将作为元素的子元素被创建出来。

⑨ 为每个元素添加固定样式。

⑩ 访问函数将合适的列分配到水平轴。在返回数据之前想想比例尺操作符是怎样对应数据的。

⑪ 访问函数为第一个数据集选择了合适的列,并进行恰当的缩放。

⑫ 上面的流程已是轻车熟路,我们来为第二个数据集添加元素——但是请注意它们之间的区别。

⑬ 对于第二个数据集,我们将填充颜色作为fill属性设置到元素上,这样该属性就会被其子元素继承。定义父类的外观有助于我们稍后一次性更改所有子类的外观。

⑭ 同样,我们创建一个空集合来保存新添加的元素。这里就体现出元素的好处了:如果在元素上调用selectAll("circle")那么我们就不会得到一个空集合,而是第一个数据集的元素,从而避免了添加新元素,而是修改现有的元素,用第二个数据集覆盖第一个。元素允许我们将元素和对数据集的映射区分开来。(当我们在第3章系统地学习了D3的选择抽象之后,这一点就会豁然开朗了。)

⑮ 访问函数为第二个数据集选择合适的列。

⑯为了更清楚地区分这两个数据集,我们需要用直线将各个数据集符号连接起来。直线比符号更复杂,因为每条线都依赖于两个连续的数据点。D3内置了创建方法,d3.line()工厂函数返回一个函数对象,它通过一个给定的数据集来生成一个能够适配SVG元素中d属性的字符串。(有关元素及其语法的更多信息,请参见附录B。)

⑰直线生成器函数需要访问函数来挑出每个数据点的水平和垂直坐标。

⑱根据元素的id属性来选择第一个数据集。ID选择器字符串由“#”和属性值组成。

⑲添加一个元素作为第一个数据集组的子元素。

⑳其d属性是通过调用数据集上的直线生成器函数来设置的。

㉑我们并非从头开始创建一个新的直线生成器函数,而是通过设置新的访问函数复用现有的直线生成器。

㉒向SVG树中的合适位置添加第二个数据集的元素并填充颜色。

㉓由于第二个数据集符号的填充样式是在其父元素上定义的(而并非单个元素本身上),因此可以在单个操作中进行修改。取消这一行的注释,那么第二个数据集的所有圆圈就都会变为红色。因为只有外观选项继承自父类,所以不可能以这种方式更改所有圆的半径或是将圆改为矩形。若要这么做需要单独修改每个元素。

至此,你可能开始对D3的机制有了一些感觉。当然,虽然有一些复杂,但它的大部分工作都是组装类型的,你只需要将预制组件组装在一起即可。尤其是方法的链式调用和Unix的管道构造类似(或者说就像玩乐高玩具一样)。组件自身更倾向于机制而非策略,这使得它们有了更广阔的复用空间,同时也给程序员或设计师带来了压力,他们需要创建更有意义的语义化图形程序集。我想强调的最后一点是,D3倾向于通过“延后绑定”的方式来推迟图表的绘制。例如,它通常将访问函数作为参数来渲染到之前渲染过的架构,而非从原始数据集中提取数据列。

2.2.2 使用可复用组件添加图形元素

图2-2是最基本的图表,它只显示数据,没有其他内容。甚至连比例尺都没有显示——这一点至关重要,因为两个数据集的数值范围是不同的。没有比例尺或坐标轴,就不可能从图(甚至任何图表)中读取定量的信息。因此,我们需要将坐标轴添加到现有的图表当中。

坐标轴算是比较复杂的图形元素了,我们需要妥善安排其刻度标记和刻度值。幸运的是,D3提供了一个坐标轴生成工具,可以通过给定的比例尺对象(已经定义好了定义域、值域以及它们之间的映射)来生成并绘制所有必需的图形元素。可视的坐标轴组件包含了许多独立的SVG元素(作为刻度标记和标签),因而我们应该始终在其自身的容器中进行创建。应用于此父元素的样式和变形转换都会由坐标轴的所有部分继承。这一点很重要,因为所有轴最初都是位于原点(左上角),并且必须使用tranform属性将其移动到需要的位置(有关坐标轴的更多细节,请参见第7章)。

除了添加坐标轴和生成曲线的功能外,示例2-6还演示了D3编程的另一种风格。示例2-5中的代码非常简单,但也很冗长,并且包含了大量重复代码。例如,创建三个不同比例尺对象的代码几乎是相同的。与之类似,用于为第二个数据集创建符号和直线的代码中大部分是重复的。这种风格的优点是简单和线性逻辑流,代价就是更加冗长了。

在示例2-6中,我们将重复的代码封装到了本地函数中,因而精简了大量代码。由于这些函数均定义在makeDemo3()方法内部,所以访问其作用域内的变量也就顺理成章了。这有助于减少本地辅助函数的参数。该示例还将组件作为封装和可复用的单元来引入,并演示了“整合”方法的调用——这都是使用D3时的诀窍。

示例2-6:图2-3的指令function makeDemo3() { d3.tsv( "examples-multiple.tsv" ) .then( function( data ) { var svg = d3.select( "svg" ); ① var pxX = svg.attr( "width" ); ② var pxY = svg.attr( "height" ); var makeScale = function( accessor, range ) { ③ return d3.scaleLinear() .domain( d3.extent( data, accessor ) ) .range( range ).nice(); } var scX = makeScale( d => d["x"], [0, pxX] ); var scY1 = makeScale( d => d["y1"], [pxY, 0] ); var scY2 = makeScale( d => d["y2"], [pxY, 0] ); var drawData = function( g, accessor, curve ) { ④ // draw circles g.selectAll( "circle" ).data(data).enter() .append("circle") .attr( "r", 5 ) .attr( "cx", d => scX(d["x"]) ) .attr( "cy", accessor ); // draw lines var lnMkr = d3.line().curve( curve ) ⑤ .x( d=>scX(d["x"]) ).y( accessor ); g.append( "path" ).attr( "fill", "none" ) .attr( "d", lnMkr( data ) ); } var g1 = svg.append( "g" ); ⑥ var g2 = svg.append( "g" ); drawData( g1, d => scY1(d["y1"]), d3.curveStep ); ⑦ drawData( g2, d => scY2(d["y2"]), d3.curveNatural ); g1.selectAll( "circle" ).attr( "fill", "green" ); ⑧ g1.selectAll( "path" ).attr( "stroke", "cyan" ); g2.selectAll( "circle" ).attr( "fill", "blue" ); g2.selectAll( "path" ).attr( "stroke", "red" ); var axMkr = d3.axisRight( scY1 ); ⑨ axMkr( svg.append("g") ); ⑩ axMkr = d3.axisLeft( scY2 ); svg.append( "g" ) .attr( "transform", "translate(" + pxX + ",0)" ) ⑪ .call( axMkr ); ⑫ svg.append( "g" ).call( d3.axisTop( scX ) ) .attr( "transform", "translate(0,"+pxY+")" ); ⑬ } );}

① 选择元素并赋值给一个变量以便其无须调用select()就能复用。

② 接下来,我们获取元素的尺寸。许多D3函数既可以作为设置属性值(setter)和也可以读取属性值(getter)。如果提供了第二个参数,那么就会把指定的属性设置为该值,否则会返回属性的当前值。我们在这里使用这个特性来获取的尺寸。

③ makeScale()函数把相对烦琐的D3函数调用进行了封装。比例尺对象在前面(示例2-5)已经介绍过。它上面的nice()函数的作用就是将数据范围扩展到最“相近”的值。

④ drawData()函数封装了绘制单个数据集所需的所有命令,为每个数据点都创建了对应的圆形并通过直线进行连接。drawData()的第一个参数必须是一个Selection实例。通常来说,元素是单一数据集中所有图形元素的容器。将Selection作为第一个参数然后向其中添加元素的函数就是组件,这是D3中封装和代码复用的重要机制。这是我们看到的第一个例子,本例后面的axis工具是另一个(详见第5章)。

⑤ 我们已经在示例2-5中熟悉过d3.line()工厂函数了。它可以接受一种算法,该算法定义了应该使用何种曲线来连接连续的点。它的默认值是直线,D3也定义了许多其他算法——你当然可以自定义算法(详见第5章)。

⑥ 为两个数据集各创建一个元素容器。

⑦ 然后将元素容器、描述数据集的访问函数和曲线形状作为实参传入drawData()函数。为了展示不同的方法,我们分别使用了阶梯曲线(d3.curveStep)和自然曲线(d3.curveNatural)。drawData()函数的作用就是将必要的元素添加到容器中。

⑧ 为每个容器的图形元素设置各自的颜色。设置颜色的逻辑并未包含在drawData()内,虽然说这样做毫无压力。这里反映了D3的一个习惯用法:将创建DOM元素和配置外观选项分离。

⑨ 在图的左侧绘制第一个数据集的坐标轴。还是要强调一下,默认情况下所有的轴都在原点(0,0)处开始渲染。axisRight对象是在坐标轴的右侧绘制刻度标签,因此,如果坐标轴位于图的右侧,那么刻度就会显示到图的外部。这里我们就放在左侧,以免刻度溢出。

⑩ 工厂函数d3.axisRight(scale)返回一个函数对象,用以生成包含所有组件的坐标轴。它需要一个SVG容器(通常是一个元素)作为参数,并在其内部创建坐标轴上的所有元素。换句话说,它就是之前所说的组件(详见第7章)。

⑪ 对于图右侧的坐标轴,我们会将其容器元素移动到适当的位置。这可以通过使用SVG的transform属性来实现。

⑫ 新知识点:这里不是将包含元素的axMkr方法作为参数来显式调用,而是将axMkr函数作为参数传递给call()函数。call()函数是Selection API(见第3章)的一部分,其功能和JavaScript的同名原生方法类似。它会调用传入的参数(必须是函数),同时将当前选择集作为参数传入。这种形式的“整合”函数调用在JavaScript中非常常见,请习惯这种用法。它的优点之一就是能够支持方法的链式调用,请往下看。

⑬ 我们在图的底部加上了横坐标。这里我们交换了函数的调用顺序:首先调用axis组件,然后再移动它的位置。这里我们还省去了[1]辅助变量axMkr。这可能是编写此类代码最惯用的写法。

结果如图2-3所示。它虽然并不漂亮,但是功能齐全,显示了两个数据集及各自的比例尺。想要让它美观一些,可以在SVG区域内的实际数据周围加入一些内边距,从而为坐标轴和刻度腾出空间(尝试一下)。图2-3:经过改进后的图,加入了坐标轴和不同类型的曲线(对比图2-2和示例2-6)[1] 事实上,drawData()函数通常的调用形式为g1.call(drawData,d=>scY1(d["y1"]),d3.curveStep)2.3 第三个例子:让列表项动起来

我们的第三个例子也是最后一个例子,与其他两个例子相比,可能有些脑洞大开,但它仍然说明了两个重要问题:

·D3并非局限于生成SVG。相反,它可以操作DOM树的任何部分。在本例中,我们将使用D3来操作普通的HTML列表元素。

·D3使创建响应式和动态图表成为可能。也就是说,图表可以响应用户事件(如鼠标单击)并随着时间改变其外观。这里我们仅提供一个简略的示例,更多细节将在第4章中进行讨论。

2.3.1 用D3创建HTML元素

这次因为代码量很少,因此就不将JavaScript命令单独拆出来,而是直接写到页面中了。示例2-7就是包括了D3命令的整个页面内容(也可见图2-4)。

示例2-7:将D3用于图形以外的HTML操作(见图2-4)

从结构上来说,本示例几乎可以等同于本章的第一个例子(示例2-3),细节上稍有不同。

① 数据集定义在代码内部,而非从外部文件加载。

② 将HTML元素作为最外层容器。

③ 插入一个

④ 与前面一样,数据集绑定到选择集,并且检索尚未匹配到的DOM元素。

⑤ 最后,根据数据集为每个数据点添加列表项和内容(本例中的列表项文本)。

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载