数据可视化实战


图灵程序设计丛书 人 民 邮 电 出 版 社 北  京 Interactive Data Visualization for the Web [美]Scott Murray 著 李松峰 译 Beijing • Cambridge • Farnham • Köln • Sebastopol • Tokyo O’Reilly Media, Inc.授权人民邮电出版社出版 数据可视化实战 使用D3设计交互式图表 内 容 提 要 数据可视化是展示数据的重要手段,广泛适用于数据分析、计量统计、演讲展示和各种网 站应用。而通过浏览器来呈现数据不受平台限制,任何计算机只要能上网就可以看到漂亮的交 互式图表。本书将带领读者学习当前最热门的基于浏览器的数据可视化库——D3。作者通过风 趣幽默的语言、简单易懂的示例,由浅入深地介绍了使用 D3 所需的基本技术,以及基于数据 绘图、比例尺、数轴、数据更新、过渡和动画等构建交互式在线图表的核心概念,最后还介绍 了 D3 中常用的布局方法和创建地图等流行应用的技巧。 本书需要读者具有一定的 Web 开发经验,特别要了解一些 DOM 编程。除此之外,只要对 数据可视化感兴趣,均可阅读学习。 定价:59.00元 读者服务热线:(010)51095186转604 印装质量热线:(010)67129223 反盗版热线:(010)67171154 广告经营许可证:京崇工商广字第 0021 号 著    [美] Scott Murray 译    李松峰 责任编辑 刘美英 责任印制 焦志炜 人民邮电出版社出版发行  北京市崇文区夕照寺街14号 邮编 100061  电子邮件 315@ptpress.com.cn 网址 http://www.ptpress.com.cn 北京      印刷 开本:787×1092 1/16 印张:15.5 字数:295千字 2013年 6 月第 1 版 印数:1 — 3 000册 2013年 6 月北京第 1次印刷 著作权合同登记号 图字:01-2013-3660号 ◆ ◆ ◆ III 版权声明 ©2013 by Scott Murray. Simplified Chinese Edition, jointly published by O’Reilly Media, Inc. and Posts & Telecom Press, 2013 Authorized translation of the English edition, 2013 O’Reilly Media, Inc., the owner of all rights to publish and sell the same. All rights reserved including the rights of reproduction in whole or in part in any form. 英文原版由 O’Reilly Media, Inc. 出版 2013。 简体中文版由人民邮电出版社出版, 2013。英文原版的翻译得到 O’Reilly Media, Inc. 的授权。此简体中文版的出版和销售得到出版权和销售权的所有者 —— O’Reilly Media,Inc. 的许可。 版权所有,未得书面许可,本书的任何部分和全部不得以任何形式重制。 O’Reilly Media 通过图书、杂志、在线服务、调查研究和会议等方式传播创新知识。 自 1978 年开始,O’Reilly 一直都是前沿发展的见证者和推动者。超级极客们正在开创 着未来,而我们关注真正重要的技术趋势——通过放大那些“细微的信号”来刺激社 会对新科技的应用。作为技术社区中活跃的参与者,O’Reilly 的发展充满了对创新的 倡导、创造和发扬光大。 O’Reilly 为软件开发人员带来革命性的“动物书”;创建第一个商业网站(GNN);组 织了影响深远的开放源代码峰会,以至于开源软件运动以此命名;创立了 Make 杂志, 从而成为 DIY 革命的主要先锋;公司一如既往地通过多种形式缔结信息与人的纽带。 O’Reilly 的会议和峰会集聚了众多超级极客和高瞻远瞩的商业领袖,共同描绘出开创 新产业的革命性思想。作为技术人士获取信息的选择,O’Reilly 现在还将先锋专家的 知识传递给普通的计算机用户。无论是通过书籍出版,在线服务或者面授课程,每一 项 O’Reilly 的产品都反映了公司不可动摇的理念——信息是激发创 新的力量。 业界评论 “O’Reilly Radar 博客有口皆碑。” ——Wired “O’Reilly 凭借一系列(真希望当初我也想到了)非凡想法建立了数百万美元的业务。” ——Business 2.0 “O’Reilly Conference 是聚集关键思想领袖的绝对典范。” ——CRN “一本 O’Reilly 的书就代表一个有用、有前途、需要学习的主题。” ——Irish Times “Tim 是位特立独行的商人,他不光放眼于最长远、最广阔的视野并且切实地按照 Yogi Berra 的建议去做了:‘如果你在路上遇到岔路口,走小路(岔路)。’回顾过去 Tim 似乎每一次都选择了小路,而且有几次都是一闪即逝的机会,尽管大路也不错。” ——Linux Journal O’Reilly Media, Inc.介绍 V 目录 前言 .........................................................................................................................................................XI 第 1 章 写在前面 ................................................................................................................................1 1.1 数据为什么要可视化 .................................................................................................................1 1.2 为什么要写代码 .........................................................................................................................2 1.3 为什么要交互 .............................................................................................................................2 1.4 为什么要在 Web 上 ....................................................................................................................3 1.5 这是一本什么书 .........................................................................................................................3 1.6 读者是谁 .....................................................................................................................................4 1.7 这不是什么书 .............................................................................................................................4 1.8 使用示例代码 .............................................................................................................................5 1.9 谢谢你 .........................................................................................................................................6 第 2 章 D3 简介 ..................................................................................................................................7 2.1 D3 能做什么 ...............................................................................................................................7 2.2 D3 不能做什么 ...........................................................................................................................8 2.3 起源与背景 .................................................................................................................................9 2.4 替代方案 ...................................................................................................................................10 2.4.1 简易图表 ......................................................................................................................10 2.4.2 图谱可视化 ..................................................................................................................12 2.4.3 地图映射 ......................................................................................................................12 2.4.4 较原始的方案 ..............................................................................................................13 2.4.5 三维图形 ......................................................................................................................13 2.4.6 基于 D3 的工具............................................................................................................14 VI | 目录 第 3 章 技术基础 ..............................................................................................................................15 3.1 Web(万维网) ..........................................................................................................................15 3.2 HTML........................................................................................................................................17 3.2.1 内容和结构 ..................................................................................................................18 3.2.2 通过元素来添加结构 ..................................................................................................19 3.2.3 常用元素 ......................................................................................................................20 3.2.4 属性 ..............................................................................................................................22 3.2.5 类和 ID .........................................................................................................................22 3.2.6 注释 ..............................................................................................................................23 3.3 DOM..........................................................................................................................................23 3.4 开发者工具 ...............................................................................................................................24 3.5 渲染与盒模型 ...........................................................................................................................26 3.6 CSS ............................................................................................................................................28 3.6.1 选择符 ..........................................................................................................................28 3.6.2 属性和值 ......................................................................................................................30 3.6.3 注释 ..............................................................................................................................30 3.6.4 引用样式 ......................................................................................................................30 3.6.5 继承、层叠和特指度 ..................................................................................................32 3.7 JavaScript ..................................................................................................................................34 3.7.1 Hello, Console ..............................................................................................................34 3.7.2 变量 ..............................................................................................................................34 3.7.3 其他数据类型 ..............................................................................................................35 3.7.4 数学运算符 ..................................................................................................................39 3.7.5 比较运算符 ..................................................................................................................39 3.7.6 控制结构 ......................................................................................................................40 3.7.7 函数 ..............................................................................................................................42 3.7.8 注释 ..............................................................................................................................43 3.7.9 引用脚本文件 ..............................................................................................................43 3.7.10 JavaScript 陷阱 ...........................................................................................................44 3.8 SVG ...........................................................................................................................................48 3.8.1 SVG 元素 .....................................................................................................................48 3.8.2 简单的图形 ..................................................................................................................49 3.8.3 为 SVG 元素添加样式 ................................................................................................51 3.8.4 分层与绘制顺序 ..........................................................................................................53 3.8.5 透明度 ..........................................................................................................................54 3.9 关于兼容性 ...............................................................................................................................56 目录 | VII 第 4 章 安装 D3 ................................................................................................................................59 4.1 下载 D3 .....................................................................................................................................59 4.2 引用 D3 .....................................................................................................................................60 4.3 配置 Web 服务器 ......................................................................................................................61 4.3.1 基于 Python 的文本终端方案 .....................................................................................61 4.3.2 MAMP、WAMP 和 LAMP .........................................................................................62 4.3.3 快开始吧 ......................................................................................................................62 第 5 章 数据 .......................................................................................................................................63 5.1 生成页面元素 ...........................................................................................................................63 5.1.1 连缀方法 ......................................................................................................................65 5.1.2 各个击破 ......................................................................................................................66 5.1.3 平稳交接 ......................................................................................................................66 5.1.4 不要连缀 ......................................................................................................................67 5.2 绑定数据 ...................................................................................................................................67 5.2.1 怎么绑定 ......................................................................................................................67 5.2.2 数据 ..............................................................................................................................68 5.2.3 作出你的选择 ..............................................................................................................71 5.2.4 绑定及确定 ..................................................................................................................72 5.3 使用自己的数据 .......................................................................................................................75 5.3.1 自定义函数 ..................................................................................................................75 5.3.2 数据需要拥抱 ..............................................................................................................76 5.2.3 添加样式 ......................................................................................................................77 第 6 章 基于数据绘图 .....................................................................................................................79 6.1 绘制 DIV ...................................................................................................................................79 6.1.1 设定属性 ......................................................................................................................80 6.1.2 关于类 ..........................................................................................................................81 6.1.3 言归正传 ......................................................................................................................81 6.1.4 设定样式 ......................................................................................................................82 6.2 data() 的魔力 ........................................................................................................................83 6.3 绘制 SVG ..................................................................................................................................86 6.3.1 创建 SVG .....................................................................................................................87 6.3.2 数据驱动的图形 ..........................................................................................................88 6.3.3 你好,色彩 ..................................................................................................................90 6.4 绘制条形图 ...............................................................................................................................90 6.4.1 老方法生成的条形图 ..................................................................................................90 VIII | 目录 6.4.2 用新方法改进条形图 ..................................................................................................91 6.4.3 上色 ..............................................................................................................................96 6.4.4 加标签 ..........................................................................................................................98 6.5 绘制散点图 .............................................................................................................................100 6.5.1 数据 ............................................................................................................................100 6.5.2 散点图 ........................................................................................................................101 6.5.3 散点大小 ....................................................................................................................102 6.5.4 标签 ............................................................................................................................103 6.6 更上一层楼 .............................................................................................................................105 第 7 章 比例尺 ................................................................................................................................107 7.1 苹果和像素 .............................................................................................................................107 7.2 值域和范围 .............................................................................................................................108 7.3 归一化 .....................................................................................................................................109 7.4 创建比例尺 .............................................................................................................................109 7.5 缩放散点图 .............................................................................................................................110 7.5.1 d3.min() 和 d3.max() ..........................................................................................110 7.5.2 设置动态缩放 ............................................................................................................112 7.5.3 整合缩放后的值 ........................................................................................................112 7.6 修饰图表 .................................................................................................................................113 7.7 其他方法 .................................................................................................................................117 7.8 其他比例尺 .............................................................................................................................117 第 8 章 数轴 .....................................................................................................................................119 8.1 数轴简介 .................................................................................................................................119 8.2 设定数轴 .................................................................................................................................120 8.3 修整数轴 .................................................................................................................................121 8.4 优化刻度 .................................................................................................................................124 8.5 垂直数轴 .................................................................................................................................125 8.6 最后的润色 .............................................................................................................................126 8.7 为刻度标签定义样式 .............................................................................................................128 第 9 章 更新、过渡和动画 ..........................................................................................................129 9.1 更新条形图 .............................................................................................................................129 9.1.1 序数比例尺 ................................................................................................................130 9.1.2 自动分档 ....................................................................................................................132 9.1.3 使用序数比例尺 ........................................................................................................132 9.1.4 其他更新 ....................................................................................................................133 9.2 更新数据 .................................................................................................................................133 目录 | IX 9.2.1 通过事件监听器实现交互 ........................................................................................134 9.2.2 改变数据 ....................................................................................................................135 9.2.3 更新视觉元素 ............................................................................................................135 9.3 过渡动画 .................................................................................................................................138 9.3.1 持续时间 ....................................................................................................................139 9.3.2 缓动函数 ....................................................................................................................140 9.3.3 延迟时间 ....................................................................................................................141 9.3.4 使用随机数据 ............................................................................................................143 9.3.5 更新比例尺 ................................................................................................................145 9.3.6 更新数轴 ....................................................................................................................147 9.3.7 在过渡开始和结束时执行操作 ................................................................................149 9.4 其他数据更新方式 .................................................................................................................156 9.4.1 添加值(和元素) ......................................................................................................156 9.4.2 删除值(和元素) ......................................................................................................161 9.4.3 通过键联结数据 ........................................................................................................164 9.4.4 添加和删除组合拳 ....................................................................................................169 9.4.5 简要回顾 ....................................................................................................................170 第 10 章 交互式图表 .....................................................................................................................171 10.1 绑定事件监听器 ...................................................................................................................171 10.2 什么是行为 ...........................................................................................................................172 10.3 分组 SVG 元素 .....................................................................................................................177 10.4 提示条 ...................................................................................................................................182 10.4.1 浏览器默认提示条 .................................................................................................182 10.4.2 SVG 元素提示条 ....................................................................................................184 10.4.3 HTML 的 div 提示条 ............................................................................................185 10.5 适应触摸设备 .......................................................................................................................188 10.6 更进一步 ...............................................................................................................................188 第 11 章 布局 ..................................................................................................................................189 11.1 饼图布局 ...............................................................................................................................190 11.2 堆叠布局 ...............................................................................................................................194 11.3 力导向布局 ...........................................................................................................................197 第 12 章 地图 ..................................................................................................................................203 12.1 JSON 与 GeoJSON ...............................................................................................................203 12.2 路径 .......................................................................................................................................205 12.3 投影 .......................................................................................................................................206 12.4 等值区域 ...............................................................................................................................208 X | 目录 12.5 添加定位点 ...........................................................................................................................212 12.6 取得和解析地图数据 ...........................................................................................................215 12.6.1 查找 shapefile 文件 .................................................................................................215 12.6.2 选择解析度 .............................................................................................................216 12.6.3 简化数据文件 .........................................................................................................217 12.6.4 转换为 GeoJSON ....................................................................................................218 第 13 章 导出文件 .........................................................................................................................221 13.1 导出位图 ...............................................................................................................................221 13.2 导出 PDF...............................................................................................................................222 13.3 导出 SVG ..............................................................................................................................223 附录 A 扩展阅读 ............................................................................................................................227 A.1 图书 ........................................................................................................................................228 A.2 网站 ........................................................................................................................................228 A.3 Twitter ....................................................................................................................................229 XI 前言 本书是关于数据可视化的,但非专业程序员也可以看懂。如果你是一位艺术家或者 拥有视觉表现经验的图形设计师,那么这本书就是为你写的。如果你是一位专栏作 者或者研究人员,但之前没有可视化或编程经验,那这本书也是写给你的。 本书介绍 JavaScript 的数据可视化库 D3(http://d3js.org/),它可以把数据加载到网 页中并基于数据生成各种图表。要看懂这本书,之前有没有编程经验不太重要。也 许你以前写过程序,也听说过关于 JavaScript 语言的各种传闻,那你可以从 D3 和数 据可视化入手,跟 JavaScript 第一次亲密接触。没错,JavaScript 是有那么一点点古 怪,但并没有你听说得那么坏,一切其实都很好。请坐,稍安毋躁。 本书脱胎于我在自己网站上发布的一系列文章。当时(2012 年 1 月),还很难找到 面向新手的 D3 学习资料。我的网站访问量很快就达到每天几百,甚至几千次,这 说明人们对这个领域(尤其是 D3)的关注度与日俱增。如果你看过那一系列教程, 那对本书内容会很熟悉。不过,我也补充了很多新内容,包括更多的示例、有用的 提示以及建议。此外,本书 78% 以上都是冷笑话。 数据可视化是一个跨学科的领域,因此一本书不可能涵盖所有技术。好在,随着这 个领域越来越热门,市面上也有很多这类书可以选择,能够起到相互补充的作用。 比如,有讨论设计流程的: Designing Data Visualizations: Intentional Communication from Data to Display• ,作者 是 Noah Iliinsky 和 Julie Steele(O’Reilly Media,2011); Data Visualization: A Successful Design Process• ,作者 Andy Kirk(Packt Publishing, 2012)。 XII | 前言 有关于视觉设计原理和技术的: The Functional Art: An Introduction to Information Graphics and Visualization• ,作者 Alberto Cairo(New Riders,2012); Information Dashboard Design: The Effective Visual Communication of Data• ,作者 Stephen Few(O’Reilly Media,2006)。 还有探讨数据实战的: Bad Data Handbook: Mapping the World of Data Problems• ,作者 Q. Ethan McCallum (O’Reilly Media,2012); Data Analysis with Open Source Tools: A Hands-On Guide for Programmers and Data • Scientists ,作者 Philipp K. Janert(O’Reilly Media,2010); Python for Data Analysis: Agile Tools for Real World Data• ,作者 Wes McKinney(O’Reilly Media,2012)。 排版约定 本书使用的排版约定如下。 楷体• 表示新的术语。 等宽字体• 表示程序片段,也用于在正文中表示程序中使用的变量、函数名、命令行代码、 环境变量、语句和关键词等代码文本。 加粗的等宽字体• 表示应该由用户逐字输入的命令或者其他文本。 倾斜的等宽字体• 表示应该由用户输入的值或根据上下文决定的值替换的文本。 这个图标代表小窍门、建议或说明。 这个图标代表警告信息。 前言 | XIII 使用代码 本书就是要帮读者解决实际问题的。也许你需要在自己的程序或文档中用到本书中 的代码。除非大段大段地使用,否则不必与我们联系取得授权。因此,用本书中的 几段代码写成一个程序不用向我们申请许可。但是销售或者分发 O’Reilly 图书随附 的代码光盘则必须事先获得授权。引用书中的代码来回答问题也无需我们授权。将 大段的示例代码整合到你自己的产品文档中则必须经过许可。 使用我们的代码时,希望你能标明它的出处。出处一般要包含书名、作者、出 版 商 和 ISBN, 例 如:Interactive Data Visualization for the Web by Scott Murray (O’Reilly). Copyright 2013 Scott Murray, 978-1-449-33973-9。 如果还有其他使用代码的情形需要与我们沟通,可以随时与我们联系:permissions@ oreilly.com。 Safari® Books Online Safari Books Online(www.safaribooksonline.com)是应 需而变的数字图书馆。它同时以图书和视频的形式出版 世界顶级技术和商务作家的专业作品。 Safari Books Online 是技术专家、软件开发人员、Web 设计师、商务人士和创意人 士开展调研、解决问题、学习和认证培训的第一手资料。 对于组织团体、政府机构和个人,Safari Books Online 提供各种产品组合和灵活 的定价策略。用户可通过一个功能完备的数据库检索系统访问 O’Reilly Media、 Prentice Hall Professional、Addison-Wesley Professional、Microsoft Press、Sams、 Que、Peachpit Press、Focal Press、Cisco Press、John Wiley & Sons、Syngress、 Morgan Kaufmann、IBM Redbooks、Packt、Adobe Press、FT Press、Apress、 Manning、New Riders、McGraw-Hill、Jones & Bartlett、Course Technology 以及其他 几十家出版社的上千种图书、培训视频和正式出版之前的书稿。要了解 Safari Books Online 的更多信息,我们网上见。 联系我们 请把对本书的评价和问题发给出版社。 XIV | 前言 美国: O’Reilly Media, Inc. 1005 Gravenstein Highway North Sebastopol, CA 95472 800-998-9938 (in the United States or Canada) 707-829-0515 (international or local) 707-829-0104 (fax) 中国: 北京市西城区西直门南大街 2 号成铭大厦 C 座 807 室(100035) 奥莱利技术咨询(北京)有限公司 O’Reilly 的每一本书都有专属网页,你可以在那儿找到本书的相关信息,包括勘误 表、示例代码以及其他信息。本书的网站地址是: http://oreil.ly/interactive_data_visualization_web 中文版地址: http://www.oreilly.com.cn/index.php?func=book&isbn=978-7-115-32011-7 对于本书的评论和技术性问题,请发送电子邮件到: bookquestions@oreilly.com 要了解更多 O’Reilly 图书、培训课程、会议和新闻的信息,请访问以下网站: http://www.oreilly.com 我们在 Facebook 的地址如下: http://facebook.com/oreilly 请关注我们的 Twitter 动态: http://twitter.com/oreillymedia 我们的 YouTube 视频地址如下: http://www.youtube.com/oreillymedia 致谢 我的名字虽然印在了封面上,但作为作者,我感觉自己只不过是一个“漏斗”。本书 每一页的内容其实是萃取了几百位杰出人物智慧的结晶。 前言 | XV 首先,我必须感谢我妻子。要不是她提醒我“嘿,你应该把那些教程整理成一本 书”,要不是她在背后支持和鼓励我,这本书不可能面世。 感谢 Rosten Woo,我的第一个 D3 项目就是跟他一起做的,是他带我结识了这个新 工具,最后欲罢不能。感谢 Joe Golike 回应我们早期关于 D3 调试的问题。还要感 谢 Jen Lowe 和 Sha Hwang 审校原来的教程和给出的意见。 非常感谢 Casey Reas、Dan Shiffman、Joshua Noble 和 Noah Iliinsky,不仅仅因为他 们对本书提出了宝贵建议,更重要的是感谢他们在艺术、设计、编码和数据领域突 破性的工作。他们的职业道路对我影响深远。 同样,我还要感谢 MassArt Dynamic Media Institute 的 Jan Kubasiewicz。2007 年, Jan 鼓励我接触了一个叫 Processing 的东西。从那时起,我的职业生涯就完全转向 了编程艺术设计、数据可视化。今天,又有了这本书。 我跟编辑 Meghan Blanchette 及 O’Reilly 的其他人合作非常愉快。感谢 Meghan 和她 的团队为出版这本书前前后后地忙碌了那么多天,让本来无法触及的思想变成了现 实当中看得见摸得着的一本书,而且还有很多字和奇形怪状的图表印在了里面。 特别感谢 Mike Bostock、Jen Lowe、Anna Powell-Smith 和 Daisy Vincent 答应做本 书的技术审校,并给出了很多特别有价值的反馈。最终内容质量大幅提升,主要源 于他们的反馈。换句话说,要是你在代码示例中发现了错误,那肯定是因为他们要 求我修改,而我坚持没改造成的。 Mike 当然是最应该感谢的人了,他开发了 D3。如果没有这个精美的程序,数据可 视化社区就不会像今天这样充满热情、活力四射,对标准的遵行也不会那么到位。 说到社区,还要感谢 Jérôme Cukier、Lynn Cherny、Jason Davies、Jeff Heer、Santiago Ortiz、Kim Rees、Moritz Stefaner、Jan Willem Tulp,还有其他没提到的 D3 邮件列 表中的人,以及我身边对我的思考和写作给予了直接和间接帮助的人。谢谢你们的 支持。我感到非常荣幸,能与那么多天才的人们交流学习。 1 第 1 章 写在前面 1.1 数据为什么要可视化 这个信息时代更多地让人觉得它是个信息过剩的时代。铺天盖地般的信息令人目不 暇接,很多未经加工的原始信息只有使用某种方法找出其中的规律才有价值。 谢天谢地,我们人类是对图形图像极为敏感的生物。虽然很少有人能从一堆数字中 发现趋势,但即使是小孩子也能看懂条形图,并且能从这些图形中明白数字的含义。 正因为如此,数据可视化成了一股潮流。可视化数据成为与人沟通的最便捷方式。 当然,数据可视化跟用语言描述一样,都可能“撒谎”、误导人,甚至扭曲事实。不 过,只要潜心学习,多加小心,把数据变成生动的图表就能帮我们从一个全新的角 度来看懂这个世界,从中揭示出原先隐藏的一些模式和趋势。运用得当,数据可视 化是可以开口讲故事的。 如果从字面上来理解,可视化就是把信息映射为可见图形的过程。我们必须总结出 一些规则,解读数据,同时把数据变成有形的东西。比如图 1-1 中这个最基本的条 形图吧,它就是根据一个最简单的规则生成的:较大的数值映射为较高的条形。 图 1-1:将数据值映射为条形 1 注 1: 本书部分彩图请在图灵社区本书页面下载:http://www.ituring.com.cn/book/1126。——编者注 2 | 第 1 章 图形越复杂,数据集越复杂,规则也就越复杂。 1.2 为什么要写代码 手工完成数据到图形的映射不是不行,但效率太低,也太乏味。所以,一般我们都 要利用计算机来提高工作效率。效率上来了,才能把时间放在研究更大的数据集上, 这样才能处理几千条数据,甚至几百万条数据。这么大的数据量,如果全靠手工得 几年,用计算机不过瞬间而已。还有一点同样重要,利用计算机可以快速验证不同 的映射思路,改一改规则然后就能即刻看到重新生成的输出结果。这个不断重复的 “写代码 - 呈现 - 评估”的过程,是快速迭代、设计出最优映射规则的关键。 映射规则发挥的是设计系统的作用,不用人手工描绘,计算机帮我们干。我们人呢, 应该把精力放在挖概念、找联系和写规则上面,其他统统让计算机帮我们搞定。 遗憾的是,计算机软件(通常是计算模型)很难精确表达人类的所思所想。(说句公 道话,很多人其实也不善于表达自己的想法。)因为计算机是二进制系统,所有一切 都是开关、是否、这个那个、这里或非这里。人类作为感性宽厚的生物,在计算机 不愿意迁就我们的时候,我们只能迁就它。于是不可避免地,我们就要学习写代码, 编软件。我们学着跟计算机沟通,使用非常有限但又很精确的语法,好让计算机能 够更好地理解我们。 看见自己做出来的可视化效果那么棒,我们的心都醉了,于是就会继续写代码。而 一旦目睹前所未见的可视化效果,我们又欲罢不能,于是就不断重复着这个过程。 恰似像神秘的数据魔瓶里跑出来一个天才的视觉魔法师。 1.3 为什么要交互 静态可视化展示的只是预先合成好的数据“视图”,而要展示相同信息的不同侧面, 往往需要多个静态视图。在静态视图中,数据的维度同样也是受限的,因为所有可 视化的要素必须同时展示在同一个表面上。要在静态视图中表现多维数据的难度势 比登天。固定不变的图像什么时候最合适?除非像印刷或打印时,不需要也没有必 要弄一堆视图。 然而,动态的响应式的图形可以激发人们探索数据的欲望。1996 年,马里兰大学的 Ben Shneiderman 率先在“Visual Information-Seeking Mantra”中提出“先给出一个 大小合适、筛选得当的概要,然后根据需要展示细节”,由此开始,大多数交互可视 化工具的基本功能就发生了变化。 写在前面 | 3 今天,许多交互式可视化作品中都有这种设计模式的影子。不同功能的组合是有效 的,因为无论你只想大概浏览或了解一下数据集,还是带着某个疑问想从可视化图 形中找到答案,这种模式都可以胜任。总之,展示数据概要同时又配有一系列“挖 掘”工具的交互式可视化作品,能够同时满足多种用户的需要,无论他是相关领域 的新人,还是已经非常熟稔于相关数据。 当然啦,交互性也能起到静态图像没办法起到的鼓励参与的作用。动态切换和制作 精美的界面,经常会让探索数据的人产生玩游戏的感觉。因此,交互可视化能够把 那些原本会对相关主题和数据视而不见的人吸引过来,你说它重要不重要? 1.4 为什么要在Web上 看不见的可视化不叫可视化。生成了可视化作品,把它展示给别人至关重要,而在 Web 上发布是向全世界展示的最快捷方式。采用标准的 Web 技术,意味着只要是使 用较新浏览器的人都能看到和体验你的成果,而跟他们使用的操作系统(Windows、 Mac、Linux)和设备(笔记本、台式机、智能手机、平板电脑)无关。 最关键的是,本书介绍的一切都可以通过免费工具来实现。因此,作为学习者,你 唯一的成本就是自己的时间。本书讨论的一切都基于开源的、符合 Web 标准的 技术。 避开了专有软件和插件,就能保证你的工作成果可以无障碍地送达各种设备,台式 计算机、平板电脑、智能手机,都没问题。作品越容易被人看到,你的受众就越多, 你的影响就越大。 1.5 这是一本什么书 本书基于 D3 这个强大的 Web 可视化展示工具,涉及数据可视化、交互设计和 Web 开发这三个主题。全书所有内容都是我在学习使用 D3 期间摸索和积累的经验教训 的结晶。可能很多读者(包括我自己)原来都有一些设计、绘图和数据可视化的经 验,但也许对编程和计算机知道的不多。 D3 被误解说很难学,我觉得这是不公允的。D3 没有那么复杂,而是它立足的 Web 有点复杂。不过,要想熟练地使用 D3,的确需要一些先行的 Web 知识,包括 HTML、CSS、JavaScript 和 SVG。很多人(包括我自己)都是自学的这些技术。自 学本身不是问题,因为门槛很低,但它存在一定问题,因为你不一定会从头学起, 所以有时候难免会采用一些偏门手法应付差事,得过且过。说实话,真要用好 D3, 4 | 第 1 章 就得老老实实地掌握一些基础知识。 因为 D3 是用 JavaScript 写出来的,所以学习它通常意味着要理解很多 JavaScript 代 码。对我们很多搞数据的人而言,D3 是他们学习 JavaScript(乃至通常所说 Web 开 发)的起点。单学一门语言已经够难的了,更何况是用这门语言写的新程序库呢。 但掌握了 JavaScript,你就可以利用 D3 尝试一些从未做过的事。难学归难学,我敢 保证你在这门语言和新工具上投入的时间会得到意想不到的回报。 我写这本书的目的就是缩短读者的学习时间,好让你尽早开始着手做出好东西来。 本书会采取由浅入深的思路,先从基本概念讲起,慢慢地过渡到复杂主题。我不想 过多地跟大家介绍具体的可视化效果,而主要把篇幅集中于全面深入地探讨 D3 的 工作原理,以便你将来可以随心所欲地利用它来生成符合自己需要的作品。 1.6 读者是谁 你完全可以是一位新手,不用了解数据可视化,不用了解 Web 开发,甚至两都不 需要了解。(放心吧!)也许你是一位专栏作者,想把采访过程中收集的数据以 可视化形式展示出来。也许你是一位设计师,绘制一幅静态的信息图对你来说只 是信手拈来的事,但你希望能更上一层楼,掌握制作 Web 交互作品的技术。也 许你是一位艺术家,醉心于根据数据来生成艺术作品。也许你是一位程序员,对 JavaScript 和 Web 并不陌生,但强烈希望掌握一个新工具,体验一番数据可视化的 乐趣。 好了,不管你是谁,我都希望你: 听过说“万维网”,或者知道什么叫“上网”;• 稍微懂点 HTML、DOM 和 CSS;• 甚至有点编程经验;• 听说过 jQuery,或者写过 JavaScript 代码;• 听到 CSV、SVG 或 JSON 这些新词汇,不会被吓懵;• 想做一些有价值的交互式可视化项目。• 即使上面提到的这些东西你没听过说或不太了解,也不用担心。只要花点时间看看 第 3 章就好了,那一章介绍了在学习 D3 之前真正需要了解的东西。 1.7 这不是什么书 这不是一本计算机专业人士才能看懂的书,不是一本教人深入学习错综复杂的 Web 写在前面 | 5 技术的书。 本着这个原则,我可能会在书中隐藏一些技术细节,粗略地讲解一些重要的基础概 念。至于讲解的方式,恐怕会让资深软件工程师不屑一顾。这没什么,因为这本书 是写给艺术家和设计师的,不是写给工程师大牛的。我们只会涉及基本概念,要想 深入某些技术细节,你要量力而行。 另外,我有意没有给出某些问题的全部解决方案,而是只向大家推荐我认为最简单 的方案,就算不是最简单,也应该是最好理解的。 我的目标是让大家理解 D3 的基本概念和方法。为此,本书就没有办法围绕特定的 示例项目来组织。而每个人的设计目标和数据集千差万别,所以只要掌握了基本概 念和方法,至于怎么用、用到哪,就完全悉听尊便了。 1.8 使用示例代码 如果你是一位天才,可能不用看示例文件就能学会 D3。如果是这样,本节下面的内 容可以不看。 如果你跟我一样,虽然很聪明,但还没有达到天才的地步,那恐怕必须借助本书随 附的示例代码,才能看懂这本书。在看书之前,请先到 GitHub 上下载完整的示例 代码文件(http://t.cn/zTI9BXG)。1 正常人通常是点击 ZIP 文件的链接来下载,而骨灰级玩家愿意使用 Git 来克隆代码。 如果你听不太明白后半句话是什么意思,就按照前半句话的意思做好了 。 在下载到的压缩文件中,每章的代码文件都放在以相应章名命名的文件夹里,比如: chapter_04 chapter_05 chapter_06 chapter_07 chapter_08 ... 文件按章组织,因此第 9 章在提到 01_bar_chart.html 时,你就知道这个文件的位置 是:d3-book/chapter_9/01_bar_chart.html。 只要不是出于商业目的,你可以随便复制、使用、修改和重用这些代码。 注 1: 也可以从图灵社区下载:http://www.ituring.com.cn/book/1126。——编者注 6 | 第 1 章 1.9 谢谢你 最后我想说,我已经尽最大努力把这本书写好,而且为了保证大家的学习效果反复 进行了修改。谢谢你看这本书,希望你能从中学习到知识,甚至感受到乐趣。 7 第 2 章 D3简介 D3(有时候也叫 D3 或 d3.js)是一个 JavaScript 库,用于创建数据可视化图形。但 这么说多少还是有点低估它了。 事实上,D3 是一个缩写,它的全称叫 Data-Driven Documents(数据驱动的文档)。 数据来源于你,而文档就是基于 Web 的文档(或者网页),代表可以在浏览器中展 现的一切,比如 HTML、SVG。D3 扮演的是一个驱动程序的角色,因为它联系着 数据和文档。 当然,这个名字也能让人直观地联想到 Web 开发背后的那个关键词:W3(即 World Wide Web,万维网),现在简称“Web”(也就是人们常说的“上网”的“网”)。 D3 的主要作者是才华横溢的 Mike Bostock,此外还有几位贡献者。这个项目完全是 开源的,托管在 GitHub 上(https://github.com/mbostock/d3/),任何人都可以自由 使用。 D3 的许可方式是 BSD,因此无论你出于商业还是非商业目的使用、修改和整合它, 都不用付出任何代价。 D3 官方网站是 http://d3js.org。 2.1 D3能做什么 简单地说,D3 是一个很不错的软件,它能帮你生成和操作带数据的文档。为此,要 8 | 第 2 章 经历以下几步: 把数据• 加载到浏览器的内存空间; 把数据• 绑定到文档中的元素,根据需要创建新元素; 解析每个元素的范围资料(bound datum)并为其设置相应的可视化属性,实现• 元素的变换(transforming); 响应用户输入实现元素状态的• 过渡(transitioning)。 学习 D3 的过程,就是学习那些告诉它如何加载、绑定数据,变换和过渡元素的语 法的过程。 其中,变换这一步最重要,因为映射关系在这一步起作用。D3 为应用不同的变换提 供了一个构造,不过映射规则还得由你来定。大数值应该映射为更长的条形,还是 颜色更浅的圆形?数据聚类(cluster)在 x 轴应该按年龄还是按类别排序?世界地 图中的国家应该用什么颜色来填充?诸如此类的设计决定完全是你的事。你来挖掘 概念、编写规则,D3 来执行——你不用管它怎么执行。(没错,跟 Excel 中那个爱 出风头的“图表向导”恰好相反。) 2.2 D3不能做什么 下面这些事 D3 都不能做。 D3 不能生成预定义的或“事先处理好”的视觉图形。是有意不这么做的。D3 主• 要用于生成那些解释型的,而非探索型的可视化图形。探索型工具可以帮你发现 数据中明显的、有价值的模型。Tableau(http://www.tableausoftware.com/)和 ggplot2(http://ggplot2.org/),这都是探索型工具,能帮你根据相同的数据集生成 多个视图。这一点很基础,但却不同于生成数据的解释型表现,即通过数据视图 表现出你已经发现的结论。解释型视图约束条件更多,限制也更多,同时也更容 易做专做精,而且主要用于传达最重要的信息。D3 擅长生成解释型视图,不擅 长探索型视图。(要了解与 D3 类似的其他工具,可以参考本章 2.4 节。) D3 不打算支持旧版本的浏览器。这样有助于保持 D3 代码库的干净,避免为支持• 诸如旧版 Internet Explorer 而加入太多的偏门代码。重点在于通过创建出更有吸 引力的工具,同时拒绝旧版浏览器,可以鼓励更多用户升级(而不是延缓这一进程, 让更多的人继续使用那些浏览器,然后再延缓……如此恶性循环)。D3 希望我们 大家向前看。 D3 的核心功能不处理像谷歌地图或 Cloudmade 等提供的那些位图格式的地图贴• 片。D3 最擅长处理矢量图形(SVG 图或 GeoJSON 数据),从一开始就没有打算 支持地图贴片。(位图由像素构成,很难在放大或缩小时做到不失真。矢量图则 D3简介 | 9 是由点、直线和曲线——实际上是数据方程式——定义的,因此可以随意放大 或缩小而不会失真。)不过,事情现在有了转机,但要配合使用 d3.geo.tile 插件 (http://t.cn/zTINvoy)。在这个插件问世之前,通过 D3 来实现地图映射时要么完 全依赖 SVG 而不用贴片,要么得通过 D3 在地图贴片基层上面创建 SVG 视觉效 果(这时要借助其他库,比如 Leaflet 或 Polymaps,具体请参见本章 2.4 节)。关 于如何集成位图贴片和矢量图形,一直是 D3 社区热议的话题。到今天,也没有 出现很简单很完美的方案。不过我相信,这一块肯定会有一些突破,或许未来某 一天,D3 核心会加入一些全新的贴片处理方法。 D3 不隐藏你的原始数据。D3 代码在客户端执行(也就是在用户浏览器,而不• 是 Web 服务器中执行),因此你想要可视化的数据必须发送到客户端。假如你的 数据不能共享,就不要使用 D3 了。替代方案是使用专有工具(如 Flash),或将 可视化结果预先渲染为静态图片,然后再发送到浏览器。(如果你不想共享数据, 那为什么还要把它们可视化呢?可视化的目的就是为了更好地表现数据,与其可 视化了之后担心得睡不着觉,还不如一开始就公开化和透明化。) 2.3 起源与背景 第一个浏览器只能渲染静态页面,所谓交互性仅限于单击链接。1996 年,Netscape 在浏览器中内置了 JavaScript 解释器,从而让浏览器在加载页面时,能够解释执行 这门脚本语言编写的代码。 这个举措并没有它后来引发的巨变那么惊心动魄,但却让浏览器从被动的显示,进 入了交互在线处理动态画面的新时代。这一历史性转变成就了我们今天的页面内交 互的 Web。如果没有 JavaScript,就不会有 D3,而基于 Web 的数据可视化也只能 局限于提前生成好的、不具备响应能力的 GIF 图。(噢……谢谢,Netscape !) 历史的车轮前进到了 2005 年,这一年 Jeffrey Heer、Stuart Card 和 James Landay 推出 prefuse(http://prefuse.org/),一个通过 Web 呈现的数据可视化工具包。prefuse(字母 全部小写)是用 Java 写的,那是一种编译型语言,而且可视化程序要在浏览器中通过 Java 插件运行。(注意,Java 和 JavaScript 是完全不一样的语言,尽管名字上类似。) prefuse 是当时一个突破性的应用,它首次让没有多少经验的编程人员,能够实现基于 Web 的可视化展示。有了 prefuse 之后,Web 上的数据可视化就成了小事一桩。 两年后,Jeff Heer 又推出了 Flare(http://flare.prefuse.org/)。这是一个类似的工具包, 编程语言是 ActionScript,就是说可以通过浏览器中的 Flash Player 来查看可视化结 果。与 prefuse 类似,Flare 也依赖浏览器插件。Flare 虽然是一个巨大的进步,但随 着浏览器的发展,可视化显然不通过插件(而只利用浏览器原生特性)也能实现了。 10 | 第 2 章 2009 年,Jeff Heer 搬到斯坦福。在那里,他说服一位刚毕业的学生 Mike Bostock, 共 同 在 斯 坦 福 的 Vis Group(http://vis.stanford.edu/) 开 发 了 Protovis(http:// mbostock.github.io/d3/tutorial/protovis.html),那是一个基于 JavaScript 的可视化工 具包,只依赖原生的浏览器技术。(如果你用过 Protovis,一定要参考 Mike 的这篇 “For Protovis Users”,网址:http://mbostock.github.com/d3/tutorial/protovis.html。) Protovis 简化了生成可视化图形的工作,即使是没有编程经验的人都可以上手。但 它要借助一个抽象的表现层,尽管设计师可以使用 Protovis 语法来控制这一层,可 调试很不方便,因为使用的不是标准方法。 2011 年,Mike Bostock、Vadim Ogievetsky 和 Jeff Heer 正 式 推 出 D3(http://vis. stanford.edu/papers/d3),作为下一代 Web 可视化工具。与 Protovis 不同的是,D3 直接操作网页文档。因此,调试就方便了,尝试不同的方案也更容易,而且展示视 觉效果的可能性也更多了。唯一的缺点是学习门槛有点高,不过本书会尽可能解决 这个问题。此外,你通过学习 D3 掌握的所有技术,即使在数据可视化这个领域之 外,也将是非常有用的。 无论你熟悉上面提到的任何一个突破性的工具,一定都会认可 D3 纯正的血统。 如果你对 D3 底层的设计思想感兴趣,强烈建议你看一看 Mike、Vadim 和 Jeff 在 InfoVis 上 发 表 的 论 文“D3 : Data-Driven Documents”(http://vis.stanford.edu/ files/2011-D3-InfoVis.pdf),其中清晰地分析了这种工具的必要性。这篇论文浓缩了 他们在学习和开发可视化工具几年间的心血。 2.4 替代方案 D3 也不是适合所有项目。有时候,可能你只想马上生成一张图表,没有时间自己编 写代码。或者,你想支持旧版本浏览器,因此不能依赖于 SVG 等较新的技术。 在这种情况下,最好是知道还有其他什么选择。以下我就来简单介绍一下 D3 的 部分替代方案,也许不全,但它们的共同特点是都采用了 Web 标准技术(主要是 JavaScript),而且可以免费下载使用。 2.4.1 简易图表 DataWrapper• 一个非常漂亮的在线服务,上传数据并快速生成图表后,就可以到处使用或将其 嵌入在自己的站点中。这个服务最初定位于专栏记者,而实际上任何人都可以使 用。DataWrapper 在新版本浏览器中可以显示动态图表,而在旧版本浏览器中则 D3简介 | 11 显示静态图片。(太聪明了!)你也可以下载代码在自己的服务器上运行。地址: http://datawrapper.de/。 Flot• 一个基于 jQuery 的绘图库,使用 HTML 的 canvas 元素,也支持旧版本浏览器 (甚至 IE6)。它支持有限的视觉形式(折线、散点、条形、面积),但使用很简 单。地址:http://www.flotcharts.org/。 Google Chart Tools• 由早期的 Image Charts API 发展而来的 Google Chart Tools,可以用来生成不少 标准的图表,也支持旧版本的 IE。地址:https://developers.google.com/chart/。 gRaphaël• 基于 Raphaël(参见本节后面)的一个图表库,支持旧版本浏览器(包括 IE6)。与 Flot 相比,它更灵活,而且据说还要更漂亮一些。地址:http://g.raphaeljs.com/。 Highcharts JS• JavaScript 图表库,包含一些预定义的主题和图表。它在最新浏览器中使用 SVG, 而在旧版本 IE(包括 IE6 及更新版本)中使用后备的 VML。这个工具只对非商 业用途免费。地址:http://www.highcharts.com/。 JavaScript InfoVis Toolkit• 简称 JIT,它提供了一些预设的样式可用于展示不同的数据,包括很多例子,而 文档的技术味道太浓。如果你喜欢它的预设样式,可以选择它,但浏览器支持情 况不太清楚。地址:http://philogb.github.com/jit/。 jqPlot• jQuery 绘图插件,只支持一些简单的图表,适合不需要自定义样式的情况。jqPlot 支持 IE7 及更新版本。地址:http://www.jqplot.com/。 jQuery Sparklines• 可生成波形图的 jQuery 插件,主要是那些可以嵌在字里行间的小条形图、折线 图、面积图。支持大多数浏览器,包括 IE6。地址:http://omnipotent.net/jquery. sparkline/#s-about。 Peity• jQuery 插件,可生成非常小的条形图、折线图和饼图,只支持较新版本的浏览 器。再强调一遍,它能生成非常小又非常精致的小型可视化图表,可爱程度加 10 分。地址:http://benpickles.github.com/peity/。 12 | 第 2 章 Timeline.js• 专门用于生成交互式时间线的一个库。不用编写代码,只用其代码生成器即可。 定制的空间不大,但时间线可不是那么容易做的。Timeline.js 只支持 IE8 及之后 的版本。地址:http://timeline.verite.co/。 YUI Charts• 雅虎 YUI(Yahoo! User Interface Library)的 Charts 模块,可用于创建简单的图 表,支持很多浏览器。地址:http://yuilibrary.com/yui/docs/charts/。 2.4.2 图谱可视化 所谓“图谱”,就是具有网络结构的数据(比如 B 连接到 A,A 连接到 C)。 Arbor.js• 基于 jQuery 的图谱可视化库。就算没用过它,也该看一看它的文档,连它的文 档都是用这个工具生成的(可见它有多纯粹、多 meta)。这个库使用了 HTML 的 canvas 元素,因此只支持 IE9 和其他较新的浏览器,当然也有一些针对旧版浏 览器的后备措施。地址:http://arborjs.org/。 Sigma.js• 一个非常轻量级的图谱可视化库。无论如何,你得看看它的网站,在页面上方的 大图上晃几下鼠标,然后再看看它的演示。Sigma.js 很漂亮,速度也快,同样使 用 canvas。地址:http://sigmajs.org/。 2.4.3 地图映射 我们要区分一下地图(全部内容都是地图)和地图映射(包括地理位置数据或地理 数据,比如传统的地图)。D3 本身也有很多地图映射功能,但下面这些工具最好你 也了解一下。 Kartograph• Gregor Aisch 开发的一个基于 JavaScript 和 Python 的非常炫的、完全使用矢量的 库,它的演示是必看的。最好现在就去看一看。保证你从来没见过这么漂亮的在 线地图。Kartograph 支持 IE7 及更新版本。地址:http://kartograph.org/。 Leaflet• 贴片地图的库,可以在桌面和移动设备上流畅地交互。它支持在地图贴片上显 示一些 SVG 数据层。(参见 Mike 的演示“Using D3 with Leaflet”:http://bost. ocks.org/mike/leaflet/。) Leaflet 支持 IE6(勉强)或 IE7(好得多),当然还有其 D3简介 | 13 他更新版本的浏览器。地址:http://leafletjs.com/。 Modest Maps• 作为贴片地图库中的老爷爷,Modest Maps 已经被 Polymaps 取代了,但很多人还是 喜欢它,因为它体积小巧,又支持 IE 和其他浏览器的老版本。Modest Maps 有很多 版 本, 包 括 ActionScript、Processing、Python、PHP、Cinder、openFrameworks…… 总之,它属于老当益壮那种。地址:http://modestmaps.com/。 Polymaps• 显示贴片地图的库,在贴片上可以叠加数据层。Polymaps 依赖于 SVG,因此在 较新的浏览器中表现很好。地址:http://polymaps.org/。 2.4.4 较原始的方案 以下工具跟 D3 有些类似,都提供了绘制图形的方法,但没有预定义的模板。如果 你愿意从头开始,希望得到更大的自由度,可能会对它们感兴趣。 Processing.js• Processing 的原生 JavaScript 实现,是新接触编程的艺术家和设计师的梦幻式 编程语言。Processing 是 Java 写的,因此 Processing 草图要在网页中显示通常 要靠 Java 小程序。有了 Processing.js,常规的 Processing 代码就可以在浏览器 中直接运行了。由于使用 canvas,所以只适合现代的浏览器。地址:http:// processingjs.org/。 Paper.js• 在 canavs 上渲染矢量图形的框架。同样,它的网站也堪称互联网上最漂亮的网 站之一,它们的演示做得让人难以置信。(现在就去欣赏一下吧。)地址:http:// paperjs.org/。 Raphaël• 也是一个绘制矢量图形的库,受欢迎的原因是语法具有亲和力,而且支持老版本 浏览器。地址:http://raphaeljs.com/。 2.4.5 三维图形 说来也怪,D3 不擅长 3D,因为浏览器从一开始就是二维的东西。但随着它对 WebGL 的支持越来越完善,在网页中显示 3D 图形也会渐渐成为一种趋势。 PhiloGL• 专注于 3D 可视化的一个 WebGL 框架。地址:http://www.senchalabs.org/philogl/。 14 | 第 2 章 Three.js• 能帮你生成任何 3D 场景的一个库,谷歌 Data Arts 团队出品。它的演示可以让人 整整一天都沉浸其中,兴奋不已。地址:http://mrdoob.github.com/three.js/。 2.4.6 基于D3的工具 如果你使用 D3,但又不想写代码,可以考虑下面这些基于 D3 的工具。 Crossfilter• 一个可以操作大型、多元数据集的库,主要作者是 Mike Bostock。非常适合 把 你 的“ 大 数 据 ” 塞 到 相 对 小 的 浏 览 器 里, 地 址:http://square.github.com/ crossfilter/。 Cubism• 时间序列数据可视化的 D3 插件,也是 Mike Bostock 写的。(我非常喜欢其中的 演示。)地址:http://square.github.com/cubism/。 Dashku• 用于实时更新在线控制板和小部件的在线工具,作者是 Paul Jensen。地址: https://dashku.com/。 dc.js• 这里的“dc”是 dimensional charting(维度图表)的简写,因为这个库是专门为 探索大型、多维数据集而进行优化的。地址:http://nickqizhu.github.com/dc.js/。 NVD3• 可重用的 D3 图表。NVD3 提供了很多漂亮的示例,不用像在 D3 里那样编写代 码就可以定制很多效果。地址:http://nvd3.org/。 Polychart.js• 更多可重用的图表,可选择的图表类型非常之多。Polychart.js 只对非商业用途免 费。地址:http://polychart.com/。 Rickshaw• 显示时间序列数据的一个工具包,提供了很多定制选项。地址:http://code. shutterstock.com/rickshaw/。 Tributary• 实时测试 D3 代码的一个好工具,作者是 Ian Johnson。地址:http://tributary.io/。 15 第 3 章 技术基础 本章将介绍一些相关的基本概念,熟悉这些概念对掌握 D3 大有裨益,当然也能减 少挫折感。嗯,就当本章是一个 Web 开发的入门教程吧。 请大家留意一下,本章的知识密度很高,浓缩了多年来形成的 Web 开发知 识,而且都不是专门针对 D3 的。建议大家只捡那些自己不知道的内容看, 其他的可以跳过。到后面再遇到什么问题时,可以再回来。 3.1 Web(万维网) 如果你从来没做过网页,那现在必须得想一想平常人大都不屑一顾的一个问题了: Web 的原理是什么? 我们一般把 Web 看成一堆内部相互链接的页面,而实际上它是服务器与客户端(浏 览器)之间你来我往的对话。 如果你单击了一个链接或者在浏览器地址栏输入了一个网站,那么就会出现下面这 个场景(这个场景每天都要上演无数次): 客户端:我想知道somewebsite.com上有什么,去登门造访一下吧,看看有什 么新消息。[互联网连接静静地建立起来] 服务器:你好,新来的Web客户端!我是托管somewebsite.com的服务器。你 想看哪个页面? 16 | 第 3 章 客户端:现在还早,我想看一看somewebsite.com/news/下面的页面中有什么新闻。 服务器:没问题,稍等。 (一串数字代码从服务器传输到浏览器。) 客户端:收到了。谢谢! 服务器:不客气!我非常愿意跟您在线多聊一会儿,不巧又来新请求了。再见! 客户端连接到服务器并发送请求,而服务器响应数据。可到底什么是服务器,什么 是客户端? 服务器是连在互联网上的计算机,运行着 Web 服务器软件。之所以称其为 Web 服 务器,正是因为它们会根据请求来提供网页,像个服务员似的。服务器一般 24 小时 运行,而且永远在线。有时候,Web 开发人员也会在本地计算机上运行 Web 服务 器软件,当然你也可以。本地是什么意思?就是你这台电脑啊,远程指就是其他电 脑,或者说除了你眼前这台电脑之外的其他电脑。 有很多现成的服务器软件,比如 Apache。Web 服务器软件可没那么好看,也没人愿 意看。 相对而言,Web 浏览器就赏心悦目多了,我们每天都盯着它看个没完。大家一般都 听说过 Firefox、Safari、Chrome,还有 Internet Explorer,这些都是浏览器,也就是 客户端。 理论上讲,每个网页都有一个唯一的 URL(Uniform Resource Locator,统一资源 定位符),也叫 URI((Uniform Resource Identifier,统一资源标识符)。大多数人不 知道 URL 是怎么回事,但看见就能认出来。URL 一般以 www 开头,比如 http:// www.calmingmanatee.com。但服务器如果做过配置,那么“www.”这几个字符完 全可以省掉不写。 完整的 URL 由以下 4 部分构成: 通信协议,如 HTTP 或 HTTPS;• 资源所在的域名,如 calmingmanatee.com;• 端口号,表示要连接到服务器的哪个端口上;• 其他定位信息,如所请求文件的路径或查询参数。• 于是,一个比较完整的 URL 就是这个样子的:http://alignedleft.com:80/tutorials/d3/。 平时一般不用指定端口号,因为浏览器默认会连接服务器的 80 端口。因此,前面的 URL 与下面这个功能完全一样:http://alignedleft.com/tutorials/d3/。 技术基础 | 17 URL 开头的协议名跟后面的域名之间是一个冒号和两个斜杠。为什么是两个斜杠?没 有为什么,这就么规定的。可以算个失误吧,Web 的发明人也为这个失误后悔不迭。 HTTP 代表 Hypertext Transfer Protocol(超文本传输协议),是服务器与客户端之 间传输 Web 内容最常用的协议。HTTPS 后面的“S”代表 Secure(安全)。因此, HTTPS 一般用于传输加密信息,比如在线交易或电子商务。 下面我们简单描述一个人访问某个站点时的过程。 1. 用户打开自己的浏览器,在地址栏中输入 URL,例如 alignedleft.com/tutorials/ d3/。因为没有指定协议,所以浏览器会使用默认的 HTTP 协议,在 URL 前面补 上“http://”。 2. 浏览器尝试通过互联网连接 alignedleft.com 所在的服务器,连接其 80 端口(这 是 HTTP 连接的默认端口)。 3. 与 alignedleft.com 关联的服务器同意连接,并准备接收浏览器的请求。(“我会等 你一晚上,不见不散。”) 4. 浏览器请求访问位于目录 /tutorials/d3/ 下的页面。 5. 服务器把那个页面的 HTML 内容发给浏览器。 6. 浏览器收到 HTML 后,根据其中引用的其他文件(包括 CSS 样式表和图片)再 聚合并显示出完整的页面。为此它还要再连接到同一台服务器,每次请求并取得 一个文件。 7. 服务器响应,根据请求发回每个文件。 8. 网页文档传输完毕。浏览器履行它最费劲的职责——渲染内容。首先通过解析 HTML 确定内容的结构,然后根据 CSS 选择符为匹配的元素应用样式,最后把 图片插入到页面中,并执行 JavaScript 代码。 或许你不敢相信,每单击一次链接都会经历上述步骤。这些步骤比大多数人想象的 要复杂,但却是理解客户端 / 服务器对话的基础,也是理解 Web 运行原理的基础。 3.2 HTML HTML(Hypertext Markup Language,超文本标记语言)用于向浏览器说明内容 的结构。HTML 保存在以 .html 为扩展名的纯文本文件中。下面就是一个简单的 HTML 文档。 Page Title 18 | 第 3 章

Page Title

This is a really interesting paragraph.

HTML 本身不算简单,历史也比较悠久。本节只介绍 HTML 的最新版本(正式称呼 叫 HTML5),而且只介绍那些与 D3 相关的内容。 3.2.1 内容和结构 HTML 的核心功能就是让你“标记”内容,进而给出结构。比如下面这段文本: Amazing Visualization Tool Cures All Ills A new open-source tool designed for visualization of data turns out to have an unexpected, positive side effect: it heals any ailments of the viewer. Leading scientists report that the tool, called D3000, can cure even the following symptoms: fevers chills general malaise It achieves this end with a patented, three-step process. Load in data. Generate a visual representation. Activate magic healing function. 只要读上两行,就能知道这是一个很激动人心的新闻报道。可由于内容没有分出结构 来,所以读起来很费劲。而添加了结构之后,就可以区分哪里是标题,哪里是正文。 Amazing Visualization Tool Cures All Ills A new open-source tool designed for visualization of data turns out to have an unexpected, positive side effect: it heals any ailments of the viewer. Leading scientists report that the tool, called D3000, can cure even the following symptoms: fevers• chills• general malaise• It achieves this end with a patented, three-step process. 1. Load in data. 2. Generate a visual representation. 3. Activate magic healing function. 文字还是那些文字,但加上结构之后,可读性就不一样了。 HTML 就是为内容添加语义结构(或者说层次、关系和含义)的这么一种手段。 (HTML 不考虑文档的外观,那是 CSS 的事。)如果把上面分出结构的内容用语义来 描述,就是下面这样。 技术基础 | 19 标题 段落文本 无序列表项• 无序列表项• 无序列表项• 段落文本 1. 有序列表项 2. 有序列表项 3. 有序列表项 这就是我们通过 HTML 标记给内容添加的结构。 3.2.2 通过元素来添加结构 所谓“标记”,就是通过给内容添加标签来创建元素的过程。HTML 标签以 < 开头, 以 > 结束,比如表示段落文本的

。标签一般都成对出现,一个开始标签和一个 结束标签就在文档中创建了一个元素。 结束标签用一个斜杠表示元素的关闭或结束,比如

。如果要标记一个段落元 素,那么可以像下面这样写:

This is a really interesting paragraph.

有些元素是可以嵌套的。比如,可以使用 em 元素为文本增加强调的语义。

This is a really interesting paragraph.

嵌套元素在文档中会形成层次。对上面的例子而言,em 就是 p 的子元素,因为它被 p 包含着。(相应地,p 是 em 的父元素。) 在元素相互嵌套的时候,子元素不能超出父元素之外,因为这样就会破坏层次,比如:

This could cause unexpected

results, and is best avoided.

有些标签永远不会成对出现,比如指向一张图片的 img。虽然 HTML5 不再强制要 求,但你经常也会看到这种标签的自关闭写法,也就是在末尾的 > 之前加一个斜杠: HTML5 之后,这种自关闭的标签就变成了可选的,因此下面的代码跟前面的代码 20 | 第 3 章 功能一样: 3.2.3 常用元素 HTML 有上百个元素,我们只介绍最常用的。后续章节还会介绍其他一些元素。 (要了解所有 HTML 元素,请参考全面的 Mozilla Developer Network,网址: https:// developer.mozilla.org/en/HTML/Element。) • 这是标准的文档类型声明,必须在文档的第一行。 html• 包含文档中的所有 HTML 内容。 head• 文档的头部,包含所有文档的元数据。比如标题和对外部样式表、脚本的引用。 title• 文档的标题。浏览器会把这个元素的内容显示在窗口标题栏中,并在收藏网页时 使用这个标题。 body• 所有不包含在 head 中的内容,都包含在 body 中。这里面的内容是可以在网页 中看到的。 h1• 、h2、h3、h4 用于标记不同级别的标题。h1 代表顶级标题,h2 代表二级标题,依此类推。 p• 段落! ul• 、ol、li ul 用于标记无序列表,也就是带项目符号的列表。ol 用于标记有序列表,也就 是带编号的列表。ul 和 ol 都包含 li 元素,用于标记列表项。 em• 表示强调,一般显示为斜体。 strong• 表示额外强调,一般显示为粗体。 技术基础 | 21 a• 链接。一般显示为带下划线的蓝色文本,可以另行设置。 span• 任意文本,一般都包含在 p 这样的大容器元素中。 div• 任意文本块。用于分组相关元素。 我们前面的例子就是使用这些元素来区分语义结构的:

Amazing Visualization Tool Cures All Ills

A new open-source tool designed for visualization of data turns out to have an unexpected, positive side effect: it heals any ailments of the viewer. Leading scientists report that the tool, called D3000, can cure even the following symptoms:

  • fevers
  • chills
  • general malaise

It achieves this end with a patented, three-step process.

  1. Load in data.
  2. Generate a visual representation.
  3. Activate magic healing function.
在浏览器中打开这个页面,可以看到如图 3-1 所示的结果。 图 3-1:简单的 HTML 在浏览器中默认的效果 我们到现在只介绍了给文档标记语义结构,还没有涉及视觉样式,比如颜色、字体 大小、缩进或行间距。如果没有这种指令,浏览器就会使用默认的样式。当然,默 22 | 第 3 章 认样式中规中矩,不会好看到哪里去。 3.2.4 属性 可以为任何 HTML 元素指定一些属性,形式如下(在开始标签中): < 标签名 属性 =" 值 "> 属性名后面是等于号,然后是放在双引号中的属性值。 不同的元素拥有不同的属性。比如,a 是一个链接元素,有 href 属性,这个属性的 值就是链接的 URL。(href 是 HTTP reference 的简写。) The D3 website 有些属性可以指定给任何元素,比如 class 和 id。 3.2.5 类和ID class 和 id 是最有用的两个属性,因为通过它们可以找到特定的内容。而且,CSS 和 JavaScript 代码也依赖它们定位元素。比如:

Brilliant paragraph

Insightful paragraph

Awe-inspiring paragraph

这里有三个段落,但只有一个真“不得了‘(awesome),因为我给它添加了 class= "awesome" 属性。这样,第三段就归到了“不得了”(awesome)的元素一类。通 过这个类,就可以选择并操作它。(稍后再介绍具体做法。) 可以给一个元素指定多个类,多个类名间以空格分隔;也可以给多个元素指定一个类:

Brilliant paragraph

Insightful paragraph

Awe-inspiring paragraph

这样,所有三个段落就都“令人振奋”(uplifting)了,但只有最后一个段落既 “令人振奋”又“不得了”。 ID 的使用方法类似,但每个元素只能有一个 ID,而且这个 ID 在整个页面中也只能 出现一次。例如:
技术基础 | 23 在某个元素比较特殊的情况下,使用 ID 比较合适。比如,想让一个 div 像按钮一 样,或者作为页面中其他内容的容器。 一般来说,如果页面中只有这么一个元素,就可以给它指定 id 属性。否则,就使 用 class 属性。 类和 ID 的值不能以数字开头,而必须以字母开头。比如,id="1" 不合规 则,而 id="item1" 就可以。写错了,浏览器也不报错,但你的代码就 运行不了了。为了找到问题所在,恐怕都得疯掉。 3.2.6 注释 随着代码越来越多,越来越复杂,最好的做法是添加注释。注释就是你描述代码用 途和实现方式的一些备注。如果你跟我一样,几个星期之后才会重新看一看代码, 那很可能早就忘了以前是怎么想的了。注释就能起到提醒的作用,在未来给你提供 一个向导。 HTML 中的注释是这样来写的: 位于 之间的内容都会被浏览器忽略掉。 3.3 DOM DOM(Document Object Model,文档对象模型)指的是 HTML 标签的层次结构。 每一对 HTML 标签(有时候则是一个标签)都是一个元素,对这些元素,我们一般 用拟人化的方法来称呼它们,比如:父元素、子元素、同胞元素、祖先元素和后代 元素。举个例子,对下面的 HTML 来说:

Breaking News

body 是其子元素 h1 和 p 的父元素,而 h1 和 p 则互为同胞元素。页面中的所有元 素都是 html 的后代元素。 浏览器通过解析 DOM 来操作页面内容。编码的人在实现可视化时,最关心的是 DOM。因为我们的代码必须在 DOM 层次中寻找元素,然后为它们应用样式和行 24 | 第 3 章 为。如果不想让所有 div 元素都显示成蓝色,那就必须想办法选择 class 为 sky 的 div,把它们设置成蓝色。 3.4 开发者工具 最早的时候,Web 开发的工作流程是这样的: 1. 在文本编辑器中写代码; 2. 保存文件; 3. 切换到浏览器; 4. 刷新页面,看代码是否有效。 5. 如果代码无效,猜一猜是哪出了问题,然后返回第 1 步。 浏览器是出了名的“密不透风”的黑盒子,其渲染引擎怎么工作很难预测,这就给 代码调试带来了无尽的麻烦。(说真的,1999 年年底 2000 年年初的时候,我就因此 烦透了。)好在,我们生活在一个文明的时代。现代的浏览器都内置了开发者工具, 能够把浏览器内部的工作过程展示出来,让我们看到后台的秘密。 说这些的意思就是告诉大家,开发者工具十分重要。你在写代码的时候,经常需要 用它来测试代码,而在遇到问题时,还要借助它找到原因所在。 下面我们就介绍开发者工具的一个最简单的用途:查看 HTML 页面的源代码(参见 图 3-2)。 图 3-2:在新窗口中查看源代码 技术基础 | 25 所有浏览器都支持这个功能,只不过它们可能会把这个选项放到不同的地方。在 Chrome 23.0 中,是“工具 > 查看源代码”。在 Firefox 17.0 中,是“工具 > Web 开 发者 > 页面源代码”。在 Safari 6.0 中,是“开发 > 显示源代码”(前提是必须在 “Safari > 偏好设置 > 高级”中,把“开发”菜单设置为可见)。接下来,无论你使 用什么浏览器,我都假设你使用的是它的最新版本。 如图 3-2 所示,其中显示了 HTML 源代码。如果有 D3 或 JavaScript 代码执行,那 当前 DOM 就会大不相同。 好在浏览器的开发者工具可以帮我们查看 DOM 的当前状态。同样,各个浏览器都 提供了自己的开发者工具。在 Chrome 中,可以在“工具 > 开发者工具” 中找到。 在 Firefox 中,可以试试“工具 > Web 开发者”。在 Safari 中,首先要启用开发者 工具(在“偏好设置 > 高级”下),然后,在“开发”菜单中选择“显示 Web 检查 器”。在任何浏览器中,都可以使用相应的键盘快捷键(就在前面提到的菜单项旁边 可以找到),或者通过右键单击并选择“检查元素”也能调出开发者工具。 直到最近,Safari 和 Chrome 使用的都是相同的开发者工具。从 Safari 6.0 开始,苹 果完全重新设计了自己的开发工具,但让粉丝们很失望。(新工具非常难用,而且有 这种感觉的不只我一人。)无论你使用什么浏览器,无论你的界面看起来跟我这个屏 幕截图有多不一样,其实它们的功能都非常相近。 图 3-3 显示了 Chrome 的 Web 检查器的 Elements(元素)选项卡。在这里,可以看 到 DOM 的当前状态。因为我们的代码会动态修改 DOM 元素,所以这个选项卡非 常有用。通过这个窗口,可以随时监控元素的变化。 仔细看一看,你就会发现 HTML 源代码和 DOM 层次的区别。而且,Chrome 还帮 我们生成了必要的 html、head 和 body 元素。(我总是很懒,不愿意在 HTML 中写 这些标签。) 还有一点,为什么我只讲 Chrome、Firefox 和 Safari 呢?为什么不提 IE、Opera 和 其他浏览器呢?首先,最好使用对 Web 标准支持得最完善的浏览器。IE 虽然从第 9 个版本到第 10 版本有了很大进步,但 Chrome、Firefox 和 Safari 对标准的支持仍然 是最好的,而且升级很频繁。 其次,我们得经常使用开发者工具,必须选择那些工具好用的浏览器。我个人很喜 欢 Safari 6.0 之前的开发者工具,之后的就不好用了。所以我就只能选择 Chrome 和 Firefox 的开发者工具。建议大家都试一试,选择一个最适合自己的。 26 | 第 3 章 图 3-3:Chrome 的元素检查窗口 3.5 渲染与盒模型 渲染,就是浏览器在解析 HTML 并生成 DOM 后,对 DOM 应用视觉规则并将像素 绘制到屏幕的过程。 要知道浏览器如何渲染内容,最重要的是记住一点:浏览器把一切都看成盒子 (box)。p、div、span,都是盒子,因为它们在屏幕这个二维空间里都是矩形,具 有任何矩形的特征,如宽度、高度、位置。就算有些元素看起来有圆角或者形状不 规则,放心,在浏览器眼里,它们也都是一个个的矩形盒子。 不相信?在 Web 检查器的帮助下,你可以看到这个盒子。只要把鼠标移动到任意元 素上,与之对应的盒子就会高亮显示出来,如图 3-4 所示。 技术基础 | 27 图 3-4:高亮元素盒子的检查器 图中的无序列表 ul 有很多方面的信息。首先,这个列表总的大小(宽度和高度) 显示在了左下角黄色的小提示框中。其次,这个列表在 DOM 中的位置,通过检查 器左下角也能看出来:html > body > ul。 另外,ul 盒子扩展到了与整个窗口同宽,因为它是一个块级元素。(可以看看检查 器右侧“Computed:Style”中,有一条 display::block。)与之相对的是行内元 素,顾名思义,就是构成文本行的元素,而不是像块级元素那样上下堆叠。常见的 行内元素包括 strong、em、a 和 span。 默认情况下,块级元素会扩展到填满父元素,从而把后面的同胞元素挤到自己脚下。 行内元素不会扩展,它们相互并肩排列,一行之中鳞次栉比。(思考一下:你想成为 什么元素?) 28 | 第 3 章 3.6 CSS CSS(Cascading Style Sheets,层叠样式表)控制 DOM 元素的视觉外观。下面就是 一条简单的 CSS 样式: body { background-color: white; color: black; } CSS 样式由选择符和属性组成。选择符后面跟着属性,但被一对花括号所包围。属 性和值由冒号分隔,每个属性声明以分号结尾。 选择符 { 属性 : 值 ; 属性 : 值 ; 属性 : 值 ; } 相同的属性可以应用给多个选择符,只要用逗号分隔选择符即可,比如: 选择符 A, 选择符 B, 选择符 C { 属性 : 值 ; 属性 : 值 ; 属性 : 值 ; } 举个例子,假设你想让段落 p 和列表项 li 拥有相同的文字大小、行高和颜色,可 以这样写: p, li { font-size: 12px; line-height: 14px; color: orange; } 所有这些放到一块(选择符和带花括号的属性)就叫做一条 CSS 规则。 3.6.1 选择符 D3 使用 CSS 式的选择符来标识要操作的元素,因此理解怎么使用选择符至关重要。 CSS 中的选择符用于标识要应用样式的元素,有很多种,我们这里只介绍其中最简 单的几种。 技术基础 | 29 1. 类型选择符 这种选择符最简单,就是匹配同名 DOM 元素的标签名: h1 /* 选择所有一级标题 */ p /* 选择所有段落 */ strong /* 选择所有 strong 元素 */ em /* 选择所有 em 元素 */ div /* 选择所有 div 元素 */ 2. 后代选择符 后代选择符匹配包含在(或“出身于”)另一个元素中的元素。在应用样式的时候, 后代选择符的使用率非常高: h1 em /* 选择包含在 h1 中的 em 元素 */ div p /* 选择包含在 div 中的 p 元素 */ 3. 类选择符 类选择符匹配具有指定类的所有元素。类名之前要加一个英文句点,看下面的例子: .caption /* 选择带 "caption" 类的元素 */ .label /* 选择带 "label" 类的元素 */ .axis /* 选择带 "axis" 类的元素 */ 有些元素可能包含多个类,为此可以把多个类名串起来选择它们,比如: .bar.highlight /* 选择高亮(hightlight)的条形(bar) */ .axis.x /* 选择 x 轴(axix) */ .axis.y /* 选择 y 轴(axix) */ .axis 可以为两个轴应用样式,而 .axis.x 则只能为 x 轴应用样式。 4. ID选择符 ID 选择符匹配具有给定 ID 的一个元素。(别忘了,一个 ID 只能在 DOM 中出现一 次。)ID 前面要带一个井字号。 #header /* 选择 ID 为 "header" 的元素 */ #nav /* 选择 ID 为 "nav" 的元素 */ #export /* 选择 ID 为 "export" 的元素 */ 选择符可以通过各种组合来达到选择特定元素的目的。比如,可以把两个选择符串 起来,选择一个更具体的元素: div.sidebar /* 只选择带有 "sidebar" 类的 div,而不选择带其他类的 div */ #button.on /* 只选择带有 "on" 类,且 ID 为 "button" 的元素 */ 提醒大家一下,DOM 是动态变化的,类和 ID 随时可能被加入,或者被删除,所以 你的 CSS 规则只能适用于某些特定的情况。 30 | 第 3 章 要了解更多有关选择符的信息,请参考 Mozilla Developer Network(http://mzl.la/ V27Mcr)。 3.6.2 属性和值 多个属性和值累积起来,就会构成特定的样式: margin: 10px; padding: 25px; background-color: yellow; color: pink; font-family: Helvetica, Arial, sans-serif; 虽然大家自己能看明白,但还是多说几句吧。每个属性都应匹配不同的信息,比如 color 要有一种颜色值,而 margin 需要一个长度值(这里单位是 px,即像素)。 顺便说一下,颜色可以使用以下几种格式。 颜色名:• orange。 十六进制值:• #3388aa 或 #38a。 RGB 值:• rgb(10, 150, 20)。 带透明通道的 RGB 值:• rgba(10, 150, 20, 0.5)。 至于所有的属性,在网上很容易找一个表格,因此这里就不再多说了。将来碰上的 时候,我们再逐个介绍。 3.6.3 注释 /* 噢,差点给忘了,这是 CSS 中注释的格式。 以斜杠和星号开头,以另一个星号和斜杠 结尾。中间的所有内容都会被浏览器忽略。 */ 3.6.4 引用样式 把 CSS 规则应用给 HTML 文档的方式有三种。 1. 在HTML中嵌入CSS 把 CSS 规则嵌入到 HTML 文档,可以把所有东西都放在一个文件中。在文档的头 部,可以把 CSS 代码放在 style 元素中。

If I were to ask you, as a mere paragraph, would you say that I have style?

这个 HTML 页面和 CSS 规则渲染后的结果如图 3-5 所示。 图 3-5:渲染嵌入 CSS 规则后的效果 嵌入 CSS 规则是最简单的办法。但是,我建议大家把不同的代码(HTML、CSS 和 JavaScript 代码)分别放在不同的文件中。 2. 在HTML中引用外部样式表 可以把 CSS 保存一个单独的纯文本文件中,扩展名为 .css,比如 style.css。然后, 在 HTML 文档中通过头部的 link 元素引用该外部样式文件即可。

If I were to ask you, as a mere paragraph, would you say that I have style?

这样渲染后的结果与前面的例子完全一样。 3. 插入行内样式 第三种应用 CSS 样式的方法就是把 CSS 规则直接插入 HTML 元素标签内。为此, 要使用 style 属性为元素指定规则。CSS 规则要像其他属性值一样,放到一对双引 号中。比如下面的代码会得到图 3-6 所示的结果。 32 | 第 3 章

Inline styles are kind of a hassle

图 3-6:渲染行内 CSS 规则的效果 因为行内样式是直接应用到具体元素上的,所以就不需要选择符了。 太多的行内样式会导致 HTML 代码混乱,难以理解。可是在需要给某个元素应用特 殊效果,而又不方便把它写进更大的样式表文件时,使用行内样式还是可以接受的。 后面我们会介绍在 D3 中怎么以编程的方式为元素应用行内样式(一次应用一种样 式,很简单的)。 3.6.5 继承、层叠和特指度 在没有为某个元素指定样式的情况下,这个元素的很多样式都是从它的祖先元素继 承来的。比如,下面的文档给 div 指定了一些样式:

I am a sibling to the div.

I am a descendant and child of the div.

这个页面在渲染后,本来是应用给 div 的样式(红色背景、粗体文本等)也被第二 个段落元素继承了(如图 3-7 所示),因为 p 是这个 div 的后代。 技术基础 | 33 图 3-7:继承样式 继承是 CSS 中一个非常重要的特性,子元素因此会拥有父元素的特征。(现实当中 不是也一样嘛。) 最后,我想回答大家在心里憋了很久的一个问题:为什么称之为层叠样式表?因为 选择符会从上到下按照层叠关系匹配。说具体一点,假设多个选择符都给一个元素 应用了样式,那么后定义的规则就会覆盖先定义的规则。比如下面的规则会将文档 中所有 p 元素中文本的颜色设定为蓝色,但带有 highlight 类的 p 元素中的文本 则是黑色,而且带有黄色背景,如图 3-8 所示。第一条规则通过选择符 p 首先应用, 而第二条规则通过选择符 p.highlight 覆盖了不够具体的 p 规则。 p { color: blue; } p.highlight { color: black; background-color: yellow; } 图 3-8:CSS 的层叠与继承 后定义的规则一般会覆盖先定义的规则,但也不全是。关键是要看每个选择符的特 指度(specificity)。选择符 p.highlight 即使被放在第一条规则的位置上,它也会 覆盖选择符 p,因为它是一个更具体的选择符。假如两个选择符具有相同的特指度, 那么这时候后定义的才会胜出。 特指度也是 CSS 中最不好理解的一个地方。计算选择符特指度的规则一两句话讲不 明白,所以干脆我们不在这里讲了。为了避免以后麻烦,最好保证自己的选择符清 晰、好理解。总的原则是把通用选择符放在最前面定义,而把更具体的选择符放在 后面定义,这样就够了。 34 | 第 3 章 3.7 JavaScript JavaScript 是动态脚本语言,通过操作 DOM 动态修改页面。前面提到过,学习 D3 也是学习 JavaScript。更多内容我们会在用到的时候再讲,本节只介绍一些基础知识。 3.7.1 Hello, Console 我们通常都把 JavaScript 代码(或“脚本”)放在一个文本文件中,然后通过网页让 浏览器加载这个文件。实际上,也可以直接在浏览器的控制台(Console)中输入 JavaScript 代码。这是一种测试代码的直接而简单的方式,也可以在其中调试代码。 总之,控制台是观察代码运行情况的一个基本工具。 在 Chrome 中,按 F12 调出开发者工具,然后切换到“Console”选项卡(或直接按 Ctrl+Shift+J)。在 Firefox 中,按 Shift+F2 打开“开发者工具栏”,然后选择“Web 控制台”。在 Safari 中,按 Ctrl+Alt+C 打开“控制台”,参见图 3-9。 图 3-9:新鲜出炉的 JavaScript 控制台……好好品尝! 在控制台中每次只能输入一行代码,而且会返回你输入的结果。比如,输入数值 7, 控制台就会返回 7。一点都不迟疑。 有时候,你需要自己的脚本能把值自动输出到控制台,以便实时观测代码执行过程。 为此,可以使用如下代码:console.log("something");。 下面就跟着我在控制台中输入示例,看看结果吧。 3.7.2 变量 变量是数据的容器。一个简单的变量可以保存一个值: var number = 5; 在这条语句中,var 表示要声明一个新变量,而变量的名字是 number。等于号是 技术基础 | 35 JavaScript 中的赋值操作符,它先取得右边的值(5),然后把这个值赋给左边的变 量(number)。因此,在看到下面这样的代码时 defaultColor = "hot pink"; 可以试着不把等于号理解成“等于”,而是“设置成”。于是,上面的赋值语句就可 以读作“把变量 defaultColor 设置成桃红色("hot pink")”。 刚才我们看到了,变量可以保存数值,也可以保存文本字符串(之所以叫“字符 串”,是因为它们是由一个个的字符串起来的)。字符串必须用引号引起来。除此之 外,变量还可以保存布尔值 true 或 false: var thisMakesSenseSoFar = true; 另外,在 JavaScript 中,语句是以分号结尾的。 好了,现在可以在控制台中试着声明一些变量。比如,输入 var amount = 200, 然后按回车,在下一行继续输入 amount,回车。你会看到控制台返回了 200,这说 明 JavaScript 记住了你赋给变量 amount 的值! 3.7.3 其他数据类型 变量保存着数据,是所有数据结构的基础,复杂的数据结构都是由简单的变量组合 而成的。 下面我们再介绍几种复杂的数据类型,有数组、对象和对象的数组。如果你觉得可 行,现在可以跳过下面的内容,等到考虑向 D3 中加载数据时再回来补课。 1. 数组 数组由一系列值组成,这些值方便地保存在一个变量中。相反,如果用不同的变量 记录多个值会很不方便: var numberA = 5; var numberB = 10; var numberC = 15; var numberD = 20; var numberE = 25; 如果写成数组,那么表示这些值的形式就简单多了。JavaScript 中的方括号([])代 表数组,每个值用英文逗号分隔: var numbers = [ 5, 10, 15, 20, 25 ]; 数组在数据可视化中是无处不在的,所以你会对它非常熟悉的。要取得(访问)数 组中的值,也要使用方括号: 36 | 第 3 章 numbers[2] // 返回 15 方括号中的数字代表数组中值的位置,但这个位置是从零开始计数的。因此,数组 中的第一个位置用 0 表示,第二个位置用 1 表示,依此类推: numbers[0] // 返回 5 numbers[1] // 返回 10 numbers[2] // 返回 15 numbers[3] // 返回 20 numbers[4] // 返回 25 把数组想象成一个由行和列组成的表格可能更好理解: 位  置 值 0 5 1 10 2 15 3 20 4 25 数组可以包含任何类型的数据,不光是整数: var percentages = [ 0.55, 0.32, 0.91 ]; var names = [ "Ernie", "Bert", "Oscar" ]; percentages[1] // 返回 0.32 names[1] // 返回 "Bert" 尽管不推荐,但不同类型的数据照样可以保存在同一个数组中: var mishmash = [ 1, 2, 3, 4.5, 5.6, "oh boy", "say it isn't", true ]; 2. 对象 数组很适合保存一系列的值,但对于更复杂的数据形态,恐怕就得使用对象这种数 据结构了。我们可以把 JavaScript 中的对象想象成一个定义的数据结构,使用花括 号({})构造对象。在花括号之间,是对象的属性和值,两者以冒号分隔。每对属 性和值以分号隔开(不包括最后一对属性和值): var fruit = { kind: "grape", color: "red", quantity: 12, tasty: true }; 要引用对象中的某个值,可以使用点操作符,然后紧跟着属性名: fruit.kind // 返回 "grape" fruit.color // 返回 "red" 技术基础 | 37 fruit.quantity // 返回 12 fruit.tasty // 返回 true 可以把这些值想象为“属于”对象。噢,看哪,这是我的水果(fruit)。“什么水 果啊?”你可能会问。水果种类(fruit.kind)不是告诉你它是葡萄("grape") 了吗。“好吃吗?” 当然啦,水果好吃(fruit.tasty)的值是真(true)。 3. 对象加数组 把对象和数组组合起来就可以创建对象的数组,或者数组的对象,或者对象的对 象……好吧,反正任何适合你的数据集的数据结构。 假设我们又拿到了更多水果,所以需要扩展库存记录。那么我在外面用方括号,也 就是数组,然后在里面用花括号,也就是对象;对象之间呢,当然用逗号分隔: var fruits = [ { kind: "grape", color: "red", quantity: 12, tasty: true }, { kind: "kiwi", color: "brown", quantity: 98, tasty: true }, { kind: "banana", color: "yellow", quantity: 0, tasty: true } ]; 要读取这种数据结构,只要按照属性去查找值即可。别忘了,[] 表示数组,{} 表 示对象。因此,fruits 是数组,首先得用方括号来指定要读取的水果的索引: fruits[1] 接下来,每个数组元素就是一个水果对象呗。所以,再加上一个点和属性名就可以了: fruits[1].quantity // 返回 98 下面列出了访问 fruits 对象数组中所有值的方法: fruits[0].kind == "grape" fruits[0].color == "red" fruits[0].quantity == 12 38 | 第 3 章 fruits[0].tasty == true fruits[1].kind == "kiwi" fruits[1].color == "brown" fruits[1].quantity == 98 fruits[1].tasty == true fruits[2].kind == "banana" fruits[2].color == "yellow" fruits[2].quantity == 0 fruits[2].tasty == true 没错,我们有 fruits[2].quantity 个香蕉。 JSON。 在 使 用 D3 的 过 程 中, 不 可 避 免 地 要 遇 见 JSON(JavaScript Object  Notation,JavaScript 对象表示法)。想继续看就看吧,但 JSON 基本就是一个用 JavaScript 对象语法来组织数据的格式。这种语法经过了优化,可以在 JavaScript ( 显 然 嘛 ) 和 AJAX 请 求 中 使 用。 很 多 基 于 Web 的 应 用 程 序 编 程 接 口(API, Application Programming Interface)都返回 JSON 格式的数据。相对 XML 而言, JSON 更容易在 JavaScript 中被解析,当然 D3 操作它也非常方便。 就这么多了,实际上 JSON 看起来也没什么新东西: { "kind": "grape", "color": "red", "quantity": 12, "tasty": true } 唯一的差别就是属性名现在带着双引号,变成了字符串了。 JSON 对象与其他 JavaScript 对象一样,当然也可以保存在变量中: var jsonFruit = { "kind": "grape", "color": "red", "quantity": 12, "tasty": true }; GeoJSON。正如 JSON 是基于 JavaScript 对象语法的数据格式,GeoJSON 是基于 JSON 格式的一种格式,专门用于保存地理数据。所有 GeoJSON 对象都是 JSON 对 象,所有 JSON 对象都是 JavaScript 对象。 GeoJSON 可以保存地理空间中的点(比如经纬度坐标)或者形状(如直线和多边 形),以及其他空间特征。如果你有很多地理数据,为了在 D3 中更方便地利用它 技术基础 | 39 们,最好将其解析成 GeoJSON 格式。 在将来讨论地图的时候,我们还会详细讲解 GeoJSON。现在,只要了解像下面这样 简单的 GeoJSON 数据即可: { "type": "FeatureCollection", "features": [ { "type": "Feature", "geometry": { "type": "Point", "coordinates": [ 150.1282427, -24.471803 ] }, "properties": { "type": "town" } } ] } 正好,坐标("coordinates")数组的两个值分别是经度和纬度。 3.7.4 数学运算符 JavaScript 中执行数值的加、减、乘、除运算分别有相应的数学运算符: + // 加 - // 减 * // 乘 / // 除 例如,在控制台中输入 8 * 2,按回车,你会看到 16。下面是另外一些例子: 1 + 2 // 返回 3 10 - 0.5 // 返回 9.5 33 * 3 // 返回 99 3 / 4 // 返回 0.75 3.7.5 比较运算符 要想比较两个值,可以使用下列运算符: == // 等于 != // 不等于 < // 小于 > // 大于 <= // 小于等于 >= // 大于等于 40 | 第 3 章 这些运算符都是拿左边的值跟右边的值比较。如果结果为真,返回 true;否则,返 回 false。 快试试吧!在控制台里输入以下比较表达式,看看结果如何: 3 == 3 3 == 5 3 >= 3 3 >= 2 100 < 2 298 != 298 (JavaScript 也提供了 === 和 !== 运算符,这两个相等和不等运算符不会转换数据类 型, 但本书中不用作这种区分。) 3.7.6 控制结构 在代码需要作出决定或重复执行某些操作时,就要用到控制结构。可选择的控制结 构很多,但我们今天只介绍 if 语句和 for 循环。 1. if():只要…… if 语句通过比较运算符来确定某个语句是真是假: if (test) { // 如果为真则执行这里的代码 } 如果条件测试结果为 true,就运行花括号中的代码。否则,就忽略花括号中的代 码,继续向下执行。 if (3 < 5) { console.log("Eureka! Three is less than five!"); } 在前面的例子中,花括号中的代码始终都会执行,因为 3<5 永远为 true。在比较 变量或其他可变条件时,if 语句更能派上用场。 if (someValue < anotherValue) { // 把 someValue 设置成 anotherValue // 从而恢复这个世界的平等和秩序 someValue = anotherValue; } 2. for():现在…… 可以使用 for 循环重复执行相同的代码,语法稍有变化: for (initialization; test; update) { 技术基础 | 41 // 每次都要执行的代码 } 之 所 以 称 它 为 for 循 环, 是 因 为 可 以 指 定 循 环 的 次 数。 首 先, 运 行 初 始 化 (initialization )语句。然后,对测试条件(test )求值,就像运行一个小 if 语句一 样。最后,运行更新语句(update),再重新对测试条件求值 。 for 循环最常见的用法就是使用一个每次递增 1 的计数器控制循环。(计数器一般都 命名为 i,因为很短。) for (var i = 0; i < 5; i++) { console.log(i); // 把值输出到控制台 } 以上代码会在控制台中循环输出以下结果: 0 1 2 3 4 第一次执行代码时,声明了变量 i,其值初始化为 0。因为 i 小于 5,所以执行花 括号内代码,把 i 的当前值(0)输出到控制台。然后,i 加 1(i++ 是 i = i + 1 的简写形式)。好了,现在 i 等于 1,所以测试求值结果仍然为 true,再次执行花 括号内代码。但这一次输出到控制台的值是 1。 怎么样,看这种解释代码的文字是不是跟自己亲手执行代码一样有趣啊?这就是人 类发明计算机的原因。那我就直接跳到最后吧,在最后一次执行代码之后,i 递增 为 5,然后测试求值结果为 false,循环结束。 强调一下,计数器是从 0 而不是 1 开始计数的。换句话说,i 的第一个值是 0。为 什么不是 1 呢?反正就是一个惯例啦,不过倒是跟计算机索引数组的值的计数规则 恰好一致。 3. 对数组使用for()循环 基于代码的数据可视化如果没有数组和 for() 循环,简直就没法想象了。数组加上 for 循环,正是数据极客的“双杀组合”。(如果你不觉得自己是“数据极客”,那么 我得提醒你一下:看看这本书的封面,你在看什么书呢?) 数据把很多数据值组织到了一个方便的地方。而 for() 可以快速“循环”数组中的 每个值,对它们执行某种操作,比如把值转换成可见的形式。D3 一般都会替我们执 行这种循环,比如使用其魔法般的 data() 方法。注意下面这个例子,它遍历了一 42 | 第 3 章 个叫 numbers 的数组中的所有值: var numbers = [ 8, 100, 22, 98, 99, 45 ]; for (var i = 0; i < numbers.length; i++) { console.log(numbers[i]); // 把值输出到控制台 } 看到 numbers.length 了吗? 这是最重要的部分。length 是每个数组都有的属 性。对这个例子来说,numbers 包含 6 个值,因此对 numbers.length 求值会得到 6,也就是说,循环会运行 6 次。如果 numbers 包含 10 个值,循环就运行 10 次。 如果有 100 万个值……,好,你明白了。这也是计算机最擅长做的事:拿到一组指 令,然后反复执行。数据可视化之所以如此吸引人,也是相同的原因。你来设计和 编写可视化系统,系统然后就可以随机应变,就算再给它换一批数据也没问题。只 要系统的映射规则不变,数据变了也没关系。 3.7.7 函数 函数就是执行具体任务的代码块。 说得具体一点,函数的特殊之处表现在它可以接收参数,可以返回值。圆括号可以 调用(执行)函数。如果函数需要参数(输入值),那么就在调用时把参数放到圆括 号中。 看到下面这样的代码,你就知道这是在调用函数: calculateGratuity(38); 事实上,我们在前面使用 console.log 时,已经在调用函数了: console.log("Look at me. I can do stuff!"); 但最重要的,还是可以编写自己的函数。在 JavaScript 中有多种方式可以定义函数, 下面介绍一种比较简单的方式,本书也会统一采用这种方式: var calculateGratuity = function(bill) { return bill * 0.2; }; 这里声明了一个新变量,叫 calculateGratuity。然后,没有赋给它一个数值或 字符串,而是把一个函数保存在了这个变量中。在函数的圆括号内,我们又声明了 另一个变量 bill,只有函数自己可以使用它。bill 就是将来接收输入值的参数。 调用函数时,就可以传入相应的参数值,然后函数会把这个值乘以 0.2,返回计算 结果。 技术基础 | 43 如果这样调用: calculateGratuity(38); 函数就会返回 7.6。不错啊,20% 的小费(gratuity)呢! 当然,可以把输出结果保存在另一个变量里,以便将来使用: var tip = calculateGratuity(38); console.log(tip); // 在控制台中输出 7.6 还有一种匿名函数,跟 calculateGratuity 不一样,没有名字 1。D3 中也使用了 大量匿名函数,但我想在遇到的时候再介绍它们。 3.7.8 注释 /* JavaScript 支持 CSS 风格的注释 */ // 不过,也可以使用双斜杠 // 双斜杠后面这一行的内容全会被忽略 // 这种单行注释很适合写点简短的说明 // 有时候,也可以放在一行代码的后面: console.log("Brilliant"); // 把 "Brilliant" 输出到控制台 3.7.9 引用脚本文件 脚本可以直接放在 HTML 中,位于一对 script 标签之间: 或者,保存一个独立的文件中,以 .js 作为扩展名。然后在 HTML(可能像这里一 样在 head 标签内,也可能在结束的 body 标签之前): Page Title 注 1: 严格来讲,这里展示的定义函数的方式使用了匿名函数表达式,但通过把匿名函数赋值给一 个变量,也就相当于给了函数一个名字。如果这里的函数不是匿名的,比如写成 function calculateGratuity(bill){...},那这个函数就叫具名函数表达式。最后一种定义方式是函数声 明,即不使用 var,直接写 function calculateGratuity(bill){...}。——译者注 44 | 第 3 章 3.7.10 JavaScript陷阱 作为一种馈赠,不额外收钱,我再给大家介绍四个 JavaScript 中的陷阱:如果我早 知道这些陷阱,可能就不用每次都调试到后半夜了,也会少很多次心急火燎的经历, 少分泌一些想让人玩命的皮质醇。在学习本书后面内容的时候,你可能时不时地需 要看看这一节。 1. 动态类型 JavaScript 是一种松散类型的语言。换句话说,不必提前声明保存在变量中的数据是 什么类型。Java(跟 JavaScript 完全不一样)等其他语言都要求提前声明变量类型, 比如 int、float、boolean 或 String: // 在 Java 中声明变量 int number = 5; float value = 12.3467; boolean active = true; String text = "Crystal clear"; 而 JavaScript 则会根据赋给变量的数据,自动推断其类型。(注意,’’或 "" 表示字 符串。我个人喜欢双引号,但有人喜欢单引号。) // 在 JavaScript 中声明变量 var number = 5; var value = 12.3467; var active = true; var text = "Crystal clear"; 好讨厌哪,那么多 var !不过也有方便的地方:不用知道保存什么数据,就可以声 明和命名变量。甚至随意改变保存的数据类型,JavaScript 也不会怪你: var value = 100; value = 99.9999; value = false; value = "This can’t possibly work."; value = "Argh, it does work! No errorzzzz!"; 这里要提醒大家的是,如果你不小心在一个数值变量里保存了一个字符串,后来代 码就出现了一些奇怪的问题,希望你自己能好好反省一下。如果你不确定某个变量 中保存着什么类型的数据,可以使用 typeof 操作符: typeof 67; // 返回 "number" var myName = "Scott"; typeof myName; // 返回 "string" myName = true; typeof myName; // 返回 "boolean" 技术基础 | 45 2. 变量提升 在我们印象里,浏览器会从上到下依次执行 JavaScript 代码。但有时候也不一定! 比如,下面这些代码,你觉得变量 i 什么时候有定义? var numLoops = 100; for (var i = 0; i < numLoops; i++) { console.log(i); } 在其他很多语言中,i 会到 for 循环开始时才被声明,这符合我们的预期。但由于 存在一种叫做变量提升的机制,JavaScript 中的变量声明会被提升到函数上下文的 顶部。对于前面的例子来说,i 实际上在 for 循环开始之前就有了定义。下面的代 码跟上面的代码是等价的: var numLoops = 100; var i; for (i = 0; i < numLoops; i++) { console.log(i); } 如果变量名字有冲突,变量提升可能就会带来问题。因为你本以为某个变量到后面 才会有定义,不成想它早在代码一开始执行时就有定义了。 3. 函数级作用域 在编程领域,变量作用域这个概念用于区分在哪个环境下可以访问哪些变量。一般 来说,在任何地方都能访问任何值是不正确的。因为这样发生冲突的可能性太高, 或者不知道在哪儿意外改了某个值,都会让你急得疯掉。 很多语言都有块级作用域,也就是在当前“块”中,变量才有定义。这里的“块”, 通常就是花括号。在块级作用域下,前面例子中的变量 i 将只存在于 for 循环中。 如果想在循环外部读取或修改 i 的值,都会失败。这样其实挺好,因为你知道循环 中的变量不会跟循环外部的其他变量有冲突。 但是,JavaScript 中的变量只能限制在函数中,即在函数(而不是任何块)中声明 的变量只能在函数内部访问。 假如你使用过其他语言,那么对这一点必须格外注意。关键要记住:要想封闭某个 值,就得把它们放到函数里。 4. 全局命名空间 说到变量冲突,请帮我个忙:打开任意网页,调出 JavaScript 控制台,输入 window, 然后单击那个灰色三角形,看看里面都有什么。 46 | 第 3 章 我知道你就像进入了迷宫一样,而这正是所谓的“全局命名空间”,真是“百闻不如 一见”(参见图 3-10)。 图 3-10:全局命名空间 window 在浏览器中是一个顶级对象,包含所有 JavaScript 中能直接访问到的对 象。你看到的所有这些对象和值都包含在全局作用域中。换句话说,如果你每次都 在全局作用域中声明新变量,那这个变量就会被加到 window 对象下面。正像一些 JavaScript 界敢于仗义直言的人所说:“你这是在污染全局命名空间。”(奇怪的是, 很多在论坛里义正词严说这种话的人,在现实当中都很平易近人。) 比如,我们在控制台中输入:var zebras = "amazing"。 然后,再输入 window,点开灰色小三角,一直向下滚动,滚动到最底部,就会看 到 zebras,如图 3-11 所示。 技术基础 | 47 图 3-11:全局作用域中有了 zebras (让人不解的是,在 JavaScript 中不使用标准的 var 也可以定义新变量。因此,只要 输入 zebras = "amazing" 也会得到与前面相同的结果。) 给 window 增加几个值有什么大问题吗?刚开始的时候,啥事儿也没有。但随着你 的项目越来越复杂,特别是在需要联合使用非 D3 的 JavaScript 代码时(如 jQuery、 Facebook 的“Like”按钮,或谷歌的分析跟踪代码),说不定就会发生命名冲突。 比如,你在自己的项目里使用了变量 zebras,而 zebraTracker.js 恰好也使用了一个 同名变量!结果肯定是一团糟。至少,一些不太规范的数据,很有可能导致你的代 码发生异常或错误。 解决这个问题有两个简单的办法(说明一下,不到后面你不用担心这一点)。 只在函数里面声明变量。虽然有时候也不是绝对可行,但函数级作用域可以防止• 其本地变量跟其他变量发生冲突。 只声明一个全局对象,然后把你本来想作为全局变量的值都作为这个对象的属性。• 48 | 第 3 章 比如: var Vis = {}; // 声明空的全局对象 Vis.zebras = "still pretty amazing"; Vis.monkeys = "too funny LOL"; Vis.fish = "you know, not bad"; 这样,所有变量就都被“关在笼子里了”(在这里就是全局对象 Vis 里),因此 不会再污染全局命名空间。当然,不一定非得叫 Vis,叫什么随你便——不过叫 Menagerie(动物园)输入起来就太麻烦了。无论如何,如果再有冲突发生,那肯 定是其他脚本里也恰好有一个全局对象,而且也起了个相同的名字(Vis)。但这种 事儿发生的概率就低得多了。 3.8 SVG D3 最适合用来生成和操作 SVG(Scalable Vector Graphics,可伸缩矢量图形)。通 过 div 和其他原生 HTML 元素也没问题,但总是略显笨重,而且会遭遇浏览器间不 一致的问题。使用 SVG 更加可靠,图形效果更一致,而且速度也更快。 图 3-12 展示了一个小圆形,对应的 SVG 代码在下面。 图 3-12:小 SVG 圆形 可以使用 Illustrator 等矢量生成软件来生成 SVG 文件,但我们要学习的是如何通过 代码来生成它们。 3.8.1 SVG元素 SVG 是一种文本格式。每个 SVG 图形都是使用与 HTML 类似的标记定义的,而且 SVG 代码可以直接包含在 HTML 文档中,或者动态插入到 DOM 中。除了 IE8 及之 前版本之外,所有浏览器都支持 SVG。SVG 也是一种 XML 语言,所以那些不包含 结束标签的元素,一定要自关闭。比如: 技术基础 | 49 在学习绘图之前,首先得创建 SVG 元素。可以把 SVG 元素想象成一个可以在上 面作画的画布。(从这个意义上说,SVG 与 HTML 中的 canvas 元素相像。)至少 要给新 SVG 元素指定 width 和 height 值。如果不指定,SVG 就会像典型的块级 HTML 元素一样,贪婪地在包含它的元素内尽可能地扩大地盘。 注 意, 像 素 是 默 认 的 度 量 单 位, 因 此 可 以 只 指 定 500 和 50, 而 不 是 500px 和 50px。当然也可以明确地给出单位,除了像素之外,支持的其他单位还包括 em、 pt、in、cm 和 mm。 3.8.2 简单的图形 在 svg 标签中可以嵌入很多可见的元素,包括 rect、circle、ellipse、line、 text 和 path。 如果你熟悉计算机图形编程,那肯定知道基于像素的坐标系统的原点(即 0,0 点) 位于画布的左上角。增大 x 的值,图形会向右移动;增大 y 值,图形会向下移动 (参见图 3-13)。 图 3-13:SVG 坐标系统 rect 用于绘制矩形。x 和 y 用于指定矩形左上角的坐标,而 width 和 height 用于 指定其大小。下面这个矩形会填满 SVG 元素的整个空间,如图 3-14 所示。 图 3-14:SVG 矩形 circle 用于绘制圆形。cx 和 cy 用于指定圆心坐标,而 r 用于指定半径。下面这 50 | 第 3 章 个圆形在 500 像素宽的 SVG 画布上居中,因为其 cx(center-x,即圆心 x 坐标)值 等于 250,参见图 3-15。 图 3-15:SVG 圆形 ellipse 与 circle 类似,只不过每个轴要分别指定半径。因此,半径属性不再是 r,而是 rx 和 ry,结果如图 3-16 所示。 图 3-16:SVG 椭圆形 line 用于绘制直线,如图 3-17 所示。x1 和 y1 用于指定起点坐标,x2 和 y2 用于 指定终点坐标。另外,必须用 stroke 指定直线的颜色才能看得见。 图 3-17:SVG 直线 text 用于绘制文本。x 用于指定文本左上角的位置,y 用于指定字体的基线位置。 (基线是印刷术语,指文本中一条不可见的线,所有字母都以之为基准对齐。“p”和 “y”这样的字母会伸到基线以下,伸出的部分叫下伸部分。)以下代码会得到如图 3-18 所示的结果。 Easy-peasy 技术基础 | 51 图 3-18:SVG 文本 SVG 文本会继承 CSS 为父元素指定的字体样式,除非另有指定。(关于给文本添加样 式,我们稍后再介绍。)要覆盖继承的样式,可以像下面这样,结果如图 3-19 所示。 Easy-peasy 图 3-19:指定 SVG 文本的样式 还要注意一点,要防止任何可见的元素跑到 SVG 画布外面,否则会被切掉。特别 是对于文本,英文字母的下伸部分一不小心就会跑出去。图 3-20 展示了这样一种情 况,因为基线(y)被设置为 50,与 SVG 画布高度一样。 Easy-peasy 图 3-20:SVG 文本跑出去了 path 用于绘制前面图形之外的其他复杂图形(如地图上的国境线),后面我们会单 独介绍。本节只介绍简单的图形。 3.8.3 为SVG元素添加样式 SVG 的默认样式是黑色填充,没有描边。如果你想添加其他样式,必须自己给元素 添加。常见的 SVG 属性有下面这些。 fill• 颜色值。与使用 CSS 一样,可以使用颜色名、十六进制值,或 RGB、RGBA 值。 stroke• 颜色值。 52 | 第 3 章 stroke-width• 带单位的数值(通常单位是像素)。 opacity• 0.0(完全透明)到 1.0(完全不透明)之间的数值。 对于文本,也可以使用下面这些属性,使用方式与 CSS 中类似。 font-family• font-size• 另一个与 CSS 类似的地方,是可以通过两种方式给 SVG 元素应用样式:作为元素 属性直接应用(行内)或使用 CSS 样式规则。 以下是通过属性方式给圆形应用样式的例子,结果如图 3-21 所示。 图 3-21:SVG 圆形 另外,也可以删除所有样式属性,通过 article 类来为它定义样式(就像给其他 HTML 元素应用样式一样): 在样式规则中,使用同样的 fill、stroke 和 stroke-width 属性定义这个类的 样式: .pumpkin { fill: yellow; stroke: orange; stroke-width: 5; } CSS 方式有以下两个明显的好处: 可以只写一次样式,然后把它们应用给多个元素;• CSS 代码比行内属性容易看懂。• 技术基础 | 53 正因为如此,CSS 方式更便于维护,也可以更快地修改设计。• 不过,有时候通过 CSS 给 SVG 应用样式会令人不太放心。因为 fill、stroke 和 stroke-width 都不是 CSS 属性。(最相近的 CSS 属性是 background-color 和 border。)而我们只是在利用 CSS 选择符来应用 SVG 专有的属性。为了方便区分 哪些样式规则是应用给 SVG 的,可以考虑在选择符中增加 svg 标记: svg .pumpkin { /* ... */ } 3.8.4 分层与绘制顺序 SVG 中没有“层”和深度的概念,也不支持 CSS 的 z-index 属性,因此属性只能 在二维画布表面上排布。 不过,如果要绘制多个图形,它们是会重叠的: 代码中元素出现的顺序决定了它们的深度次序。在图 3-22 中,紫色(purple)方 形在代码中出现得最早,因此浏览器首先绘制它。然后,在它的“上面”绘制蓝色 (blue)方形。接着依次是绿色、黄色和红色的方形。 图 3-22:SVG 元素重叠 可以把浏览器绘制 SVG 图形想象成在画布上作画。后涂的油漆会使先涂的油漆变模 糊,因此出现在了“前面”。 在需要同时绘制多个视觉元素,但又不想让它们相互遮盖时,绘制的顺序至关重要。 比如,我们需要给散点图画上数轴和数值标签,那么可以在 SVG 中最后再添加数轴 和标签,这样它们就可以出现在其他元素前面。 54 | 第 3 章 3.8.5 透明度 在元素相互遮挡,但又不能完全遮住先绘制的元素时,或者想弱化某些元素而突出 显示其他元素时,透明度效果扮演着重要角色,如图 3-23 所示。 有两种应用透明度的方式:或者使用带透明通道的 RGB 值,或者设置 opacity (不透明度)值。 可以在为 fill 或 stroke 属性指定颜色的时候使用 rgba()。rgba() 接受 0 到 255 (包含及)之间的三个值,分别代表红、绿、蓝,还接受第四个 0.0 到 1.0(包含及) 之间的透明度值。 图 3-23:RGBA SVG 图形 使用 rgba() 可以分别为填充(fill)和描边(stroke)颜色应用透明度。下面几 个圆形填充都是 75% 不透明,而它们的描边都只有 25% 不透明: 应用透明度之后,可以得到图 3-24 所示的结果。描边与每个圆形的边缘对齐,既不 完全位于圆形内部,也不完全位于圆形外部,而是一半在内一半在外。在应用了透 明度之后,每个 10 像素的描边看起来倒像是两个 5 像素的描边一样。 图 3-24:RGA SVG 图形中的填充和描边 技术基础 | 55 要给整个元素应用透明度,可以设置 opacity 属性。图 3-25 展示了几个完全不透 明的圆形。 图 3-25:完全不透明的圆形 图 3-26 展示了同样的圆形但设置了 opacity 值的效果: 图 3-26:部分透明的圆形 也可以对已经使用 rgba() 设置了颜色的元素应用 opacity 属性。这时候,透明度 值会相乘。图 3-27 所示的三个圆形的 fill 和 stroke 分别使用了相同的 RGBA 值。 而且除第一个圆形外,另外两个都设置了 opacity 值: 图 3-27:RGBA 与 opacity 相乘的效果 以第三个圆形为例,其 opacity 值是 0.2(即 20%)。而其紫色的 fill 值中的透 邮 电 56 | 第 3 章 明通道值是 0.75(即 75%)。结果,紫色区域最终的透明度就是 0.2 × 0.75 = 0.15 (即 15%)。 3.9 关于兼容性 旧版本浏览器不支持 SVG。正常情况下,IE8 及更早版本不会显示 SVG 图形。有时 候确实有点讨厌,因为本该华丽无比的图形成了一片空白。而且,D3 本身也不支持 旧版本浏览器(当然,主要是 IE8 及更早版本)。总之,两者简直形同水火,更谈不 上兼容性了。 在配合使用 Aight 兼容库(https://github.com/shawnbot/aight)的情况下, D3 可以在 IE8 中运行,但更早版本的 IE 就不行了。r2d3(https://github. com/mhemesath/r2d3/)是 D3 整合 Raphaël(http://raphaeljs.com/)的图形 绘制功能后一个版本,可以尽可能兼容 IE7。 当然,最好的办法还是鼓励人们升级,或者选择最近几年才流行的浏览器。使用现 代浏览器的人越多,他们的上网体验也就越好,而我们潜在的用户也就越广。 换句话说,告诉用户为什么这个功能不能用是对用户的尊重。建议使用 Modernizr (http://modernizr.com/)或类似的 JavaScript 工具检测浏览器是否支持 SVG。如果 支持,就加载 D3 代码,并正常处理;否则,就显示静态的非交互性的图表,同时 附上一段说明,解释为什么需要较新版本的浏览器。(措词一定要委婉,同时提供 Chrome 和 Firefox 下载页面的链接。我以前也提供下载 Safari 的链接,但苹果后来 将其整合到了 Mac OS 中,不再提供单独下载了。) 举 个 例 子, 可 以 使 用 Modernizr.load() 检 查 Modernizr 测 试 的 结 果。 如 果 测 试通过(比如检测到浏览器支持 SVG),则告诉 Modernizr 加载 D3 和你自己的 JavaScript 代码。我一般会在文档的 标签中放如下代码: 首先加载 Modernizr,执行检测,然后只在测试成功的情况下才加载其他代码(即 yep 部分)。至于检测失败后显示什么,可以使用 CSS 或其他 JavaScript 脚本,也可 技术基础 | 57 以显示一幅静态图表和鼓励用户尝试新浏览器的几句话。要了解如何通过 Modernizr 实现这些,可以参考它的文档,地址:http://modernizr.com/docs/#load。 caniuse.com 也是一个非常难得的资源,通过它可以查询浏览器支持哪些特性,以及 哪些浏览器支持 SVG(http://caniuse.com/#search=svg)。 有人也尝试让 D3 在老版本浏览器中运行,有时候需要混合使用 Raphaël 在 canvas 上渲染,或者使用其他偏门技术。虽然技术上不是不可能,但我不推荐这么做。 59 第 4 章 安装D3 安装 D3 是非常简单的,只要下载它的最新版本,新建一个空页面来写代码,最后 再设置一下本地服务器就好了。 4.1 下载D3 先创建一个新文件夹,保存项目文件。文件夹的名字随便起,建议叫 project-folder。 在这个文件夹里,最好再建一个子文件夹叫 d3。然后把下载的 D3 的最新版本保存 在这个子文件夹里。因为 D3 下载下来是个 ZIP 文件,所以要把它解压缩。在写作 本书时,D3 的最新版本是 3.0.6(http://d3js.org/d3.v3.zip)。 D3 还提供了一个“瘦身”版本 d3.v3.min.js(http://d3js.org/d3.v3.min.js),由于去 掉了空格,体积更小,加载速度也更快。虽然功能都一样,但在开发项目时最好还 是用正常的版本(为了调试方便) ,等项目可以对外公开发布时再换成缩小版(为 了减少浏览器加载的时间)。到底用哪一个还是你说了算,反正本书会使用标准版。 另外,还可以下载整个 D3 代码库(https://github.com/mbostock/d3/zipball/master), 其中不光包含 JavaScript 文件,还有各种源代码文件。可以先浏览一下代码库 (https://github.com/mbostock/d3),或干脆直接把整个压缩文件下载下来。当然,全 都下载完了,还要把其中的 d3.v3.js 复制到 project-folder/d3/ 里。 60 | 第 4 章 4.2 引用D3 接下来,在项目文件夹里创建一个 HTML 页面,命名为 index.html。记住,HTML 文档就是普通的纯文本文件。TextEdit 和 Notepad 等文本编辑器都可以,但最好是 找一个专门为编码设计的,比如 Coda、Espresso 或 Sublime Text(还有很多很多)。 如果在保存时编辑器提供了文件编码选项,选择 Unicode (UTF-8)。 现在,文件夹的结构如下所示: project-folder/ d3/ d3.v3.js d3.v3.min.js (optional) index.html 把下面的代码粘贴到新建的 HTML 文件中: D3 Page Template 如果这点事都不想做,可以直接下载本书示例代码文件(参见第 1 章),找到 01_ empty_page_template.html,其中的代码与上面的完全相同(但 src 路径里 D3 的版 本号可能不一样,你得自己改成正确的)。 关于这个模板,有以下几点说明。 meta• 标签注明文件的编码为 utf-8,这是确保浏览器正确解析 D3 的功能和数据 的关键; 第一个• script 标签设定了对 d3.v3.js 的引用。如果你想使用缩小版或加载保存 在其他地方的 D3 文件,就需要修改这里的文件路径。 第二个• script 标签在 body 中,是你编写代码的地方。相信我,你的代码一会 很漂亮。 完成! D3 模板文件和文件夹都各就各位了。如果你以后还想创建其他项目,可以 安装D3 | 61 把这个模板项目完整地复制过去。 4.3 配置Web服务器 有时候,可以直接在浏览器中查看本地 HTML 文件。但出于安全方面的考虑,某些 浏览器可能会限制 JavaScript 加载本地文件。这样的话,如果 D3 代码需要加载外部 的数据文件(如 CSV 或 JSON),那就会出问题了。但这不是 D3 的问题,而是浏览 器在阻止加载来自第三方不受信任站点的脚本和其他外部文件。 为了解决这个问题,更可靠的方案是把页面放到 Web 服务器上。建议大家在本机 (也就是你眼前这台电脑)上安装一个 Web 服务器软件,这样比使用远程 Web 服务 器更方便也更快捷。本地计算机作为服务器,托管并向自己提供文件好像有点奇怪。 你可以这么想啊,浏览器和 Web 服务器是两个程序,前者向后者请求文件,后者为 前者提供服务。 实际上,在本机上安装 Web 服务器非常容易。以下介绍几种方式。 4.3.1 基于Python的文本终端方案 如果你使用的是 Mac OS X 或 Linux,那已经安装了 Python。只要你熟悉在文本终 端中输入合集,就可以直接运行一个基于 Python 的迷你服务器。这绝对是最简便的 方法。(如果你使用 Windows,得先安装 Python 才行。) 要使用 Python,打开系统中的终端窗口。在 Mac 上,打开 Terminal 程序,一般是 在 Utilities 文件夹下。或者,直接在 Spotlight(屏幕右上角的放大镜菜单)中直接 输入 Terminal。Linux 用户天生知道怎么打开终端窗口,因此我就不浪费笔墨了。 运行 Python 服务器的步骤如下。 1. 打开一个新的终端窗口。 2. 在命令行中找到你想要公开的文件夹。假如你的项目文件夹在 Mac 的 Desktop 文 件夹里,可以输入:cd ~/Desktop/project-folder。 3. 再输入 python -m SimpleHTTPServer 8888 &。 ( 以 上 命 令 在 Python 2.x 中 可 以 使 用, 但 Python 3.0 及 更 新 版 本 已 经 去 掉 了 SimpleHTTPServer。对 Python 3.x,只要把命令中的 SimpleHTTPServer 替换成 http.server 即可。) 这 样 就 能 在 8888 端 口 激 活 服 务 器。 切 换 到 浏 览 器, 访 问 以 下 URL:http:// localhost:8888/。 没 错, 不 用 输 入 www.something.com 之 类 的 网 址, 只 要 使 用 62 | 第 4 章 localhost 即可,它的意思是让浏览器请求位于本机上的页面。 然后就会看到一个空的“D3 Page Template”页面。因为页面的主体是空的,所以 在浏览器中什么也看不到。选择“查看源代码”,就能看到 HTML 模板页面的内容。 4.3.2 MAMP、WAMP和LAMP 这个方案要费点时间,如果你不喜欢终端,而是希望通过拖放来安装程序,可以试 一试。 标题中的 AMP 代表 Apache(Web 服务器软件)、MySQL(流行的数据库软件)和 PHP(流行的服务器端脚本语言)。我们是主要想用 Apache,那才是 Web 服务器软 件,不过另外两个已经跟它绑定了,而且相处融洽。 在 Mac 上, 可 以 下 载 并 安 装 MAMP(http://mamp.info/en/) 或 XAMPP for Mac (http://www.apachefriends.org/zh_cn/xampp-macosx.html) 。 这两个软件对应的 Windows 版分别是 WampServer(http://www.wampserver.com/ en/) 和 XAMPP for Windows(http://www.apachefriends.org/zh_cn/xampp-windows. html)。 如果你使用 Linux,那么这些程序可能都已经安装好。不过,你还是可以再下载 XAMPP for Linux(http://www.apachefriends.org/zh_cn/xampp-linux.html)。 以上这些程序的安装过程各有不同 ,建议安装时注意看说明。(我发现最容易安装 的是 MAMP。) 安装时,每个程序都会指定一个文件夹作为服务器的目录,以便只公开该目录下的 文件。安装完了,你需要找到这个目录,把 D3 的 project-folder 文件夹复制过去。 安装好本地服务器,接下来就可以通过浏览器(当然是同一台机器上的浏览器)查 看其目录下的页面了。在浏览器地址栏输入:http://localhost/,后面跟不跟端口号, 要看你的 AMP。如果 AMP 配置成通过 8888 端口提供服务,那在地址栏中应该输 入:http://localhost:8888/。 如果服务器端口号是 8888,你的项目文件夹是 project-folder,那你的 D3 模板页面 地址应该是:http://localhost:8888/project-folder/。 4.3.3 快开始吧 准备就绪了?好,下一章就开始讲数据。 63 第 5 章 数据 数据是一个非常宽泛的概念,其宽泛程度仅次于无所不包的信息。什么是数据? (什么不是数据?)你手里有什么数据,D3 需要什么数据? 好吧,宽泛地说,数据就是结构化的信息,反映某些事实。 在可视化编程的语境下,数据保存在数字化文件中,一般是文本格式或二进制格式。 当然,并不是只有文本内容才算数据,那些表示图像、音频、视频、数据库、流、 模型、文档等一切的比特和字节也是数据。 然而,从 D3 和浏览器可视化的角度来说,我们只讨论文本数据。换句话说,就是 那些可以表现为数值或字符串的东西。如果你可以把数据保存到 .txt 纯文本文件, 或者 .csv 逗号分隔值文件,或者 .json JSON 文档中,那么 D3 就可以使用它。 不管什么数据,如果不附着在什么东西上,那么就很难使用和可视化。用 D3 的术 语来说,数据必须绑定到页面中的元素上。所以,本章我们先讨论怎么创建新的页 面元素,然后再介绍怎么把数据绑定到这些元素。 5.1 生成页面元素 通常,在使用 D3 创建新 DOM 元素时,新元素可以是圆形、矩形,或者其他可以 表现数据的图形。但为了大家好理解,我们先从创建简单的 p 元素开始。 为此,首先要根据上一章的 HTML 模板创建一个新文档。可以在示例代码中找到 64 | 第 5 章 01_empty_page_template.html,其代码如下所示。(眼光犀利的读者会注意到,src 属性中的文件路径不一样,那是为了适应示例代码的目录结构。不太明白也没关系。) D3 Page Template 在浏览器中打开这个页面。一定要通过本地 Web 服务器来访问这个文件,第 4 章介 绍过。因此,浏览器中的 URL 应该大致像下面这样: http://localhost:8888/d3-book/chapter_05/01_empty_page_template.html 如果不是通过 Web 服务器访问这个页面,那么 URL 的开头就不是 http://,而是 file://。再确认一下,保证 URL 长得不是这样: file:///.../d3-book/chapter_05/01_empty_page_template.html 好,浏览器加载完页面后,打开 Web 检查器。(关于 Web 检查器,可以参考 3.4 节。)现在看到的只是一个空页面,DOM 内容如图 5-1 所示。 图 5-1:Web 检查器 数据 | 65 回到文本编辑器,把 script 标签中的注释替换成以下代码: d3.select("body").append("p").text("New paragraph!"); 保存,然后刷新浏览器。哇喔,原来的空白页面上多了一行字,而 Web 检查器也变 成了图 5-2 所示。 图 5-2:添加代码后的 Web 检查器 看到不同了吗?现在的 DOM 中多了一个段落元素,这个元素就是代码动态生成 的!只有这一个元素不算什么,呆会儿你会看到使用类似的技术可以动态生成几十 上百个元素,而每个元素都对应着你数据集中的一个值。 接下来我们解释一下到底发生了什么。(没照着做的读者可以打开 02_new_element. html。)为了理解第一行 D3 代码,必须先认识一位新朋友:连缀语法。 5.1.1 连缀方法 D3 聪明地利用了一种叫连缀语法的技术,用过 jQuery 的朋友都知道。通过用句点 把方法“连缀”在一起,一行代码就能执行多个操作。对于这种简单便捷的语法, 最关键是要理解其原理,以避免你将来调试时的麻烦。 噢对了,函数和方法是同一个概念的两种说法,它们都是能够接收参数作为输入, 然后执行某种操作,最后返回信息作为输出的一段代码。 乍一看,下面这行代码 66 | 第 5 章 d3.select("body").append("p").text("New paragraph!"); 好像非常乱,特别是没有编程经验的人,会有这种感觉。所以首先得知道 JavaScript 跟 HTML 一样,都不关心空格和换行。为此,可以把每个方法调用各放在一行上: d3.select("body") .append("p") .text("New paragraph!"); 我是推荐你把每个方法调用都分别写在一行上,但某些程序员可能有自己的编码习 惯。缩进多少、是否换行或使用空格还是制表符,全都取决于你。 5.1.2 各个击破 好了,下面就来逐个分析上面那串代码。 d3• 引用 D3 对象,从而能够调用其方法。D3 探险就此开始了。 .select("body")• 向 select() 方法传入一个 CSS 选择符作为输入,它就会返回一个对 DOM 中匹配 的第一个元素的引用。(如果你想取得多个元素,可以使用 selectAll() 方法。) 这里只是取得文档的 body 元素,然后把对它的引用交给调用链中的下一个方法。 .append("p")• append() 会创建一个你指定的新 DOM 元素,然后将它追加到调用它的元素 末尾(作为最后一个子元素)。这里创建了一个 p 元素,因为调用该方法的是 body,所以结果就是在 body 元素内部追加一个 p 元素。最后,append() 把刚 刚创建的新元素的引用交给下一个方法。 .text("New paragraph!")• text() 接受一个字符串,把它插入到当前元素的开始和结束标签之间。因为上 一个方法传递过来一个 p 元素,因此这里就会把文本插入到

之间。 (如果标签间原来有内容,原来的内容会被覆盖。) ;• 非常重要的分号,表示一行代码结束。连缀完成。 5.1.3 平稳交接 很多 D3 方法都返回选中的元素(实际上是选中的元素的引用),正因为这样才能实 数据 | 67 现方法连缀。一般来说,方法返回的都是正在操作的元素的引用,但有时候也不是。 所以要记住一点:在连缀方法时,次序很重要。每个方法的输出必须与下一个方法 期待的输入匹配。如果相邻的输出和输入不匹配,那么结果就像在接力赛跑中把接 力棒扔到地上一样。 要知道哪个方法期待什么输入,就要多参考 API 文档(https://github.com/mbostock/d3/ wiki/API-Reference)。文档中有对每个方法的详细说明,包括是否返回选中的元素。 5.1.4 不要连缀 不用连缀语法也可以实现同样的功能: var body = d3.select("body"); var p = body.append("p"); p.text("New paragraph!"); 哎哟!太乱了。不过,有时候还不得不断开连缀,因为一口气串起来很多方法可能 并不实际。或者,你只是希望让代码更容易组织,更符合自己的想法。 知道了怎么通过 D3 在页面中创建新元素,下面就该学习为元素附加数据了。 5.2 绑定数据 什么是绑定,为什么要绑定数据? 数据可视化说到底就是把数据映射到图形,数据入而图形出。也许是数值越大条形 越长,也许特殊类别会显示为更亮的颜色。总之,映射规则你说了算。 在 D3 中,为了实现映射规则,需要把数据输入的值绑定到 DOM 中的元素上。绑 定的意思类似于把数据“附加”或关联到特定的元素,以便将来引用数据的值和应 用映射规则。如果没有绑定这一步,那么 DOM 元素就是一堆没用的东西。 5.2.1 怎么绑定 要通过 D3 的 selection.data() 方法把数据绑定到 DOM 元素。但必须具备两个 条件: 数据;• 选中的 DOM 元素。• 下面就分别讨论一下。 68 | 第 5 章 5.2.2 数据 D3 可以很智能地处理各种数据,能够接受任何数值、字符串或对象(对象中包含着 其他数组或键 / 值对)的数组,能够流畅地处理 JSON(和 GeoJSON),甚至还有一 个内置的方法用于加载 CSV 文件。 简单起见,现在我们只以一个包含 5 个数值的数组为例: var dataset = [ 5, 10, 15, 20, 25 ]; 如果你胆子大,而且有一些 CSV 或 JSON 数据,也可以直接按照下面说的做。否 则,可以看一看 5.3 节。 1. 加载CSV数据 CSV 的意思就是逗号分隔的值(Comma-Separated Value)。CSV 数据文件一般是这 样的: Food,Deliciousness Apples,9 Green Beans,5 Egg Salad Sandwich,4 Cookies,10 Vegemite,0.2 Burrito,7 这个文件中每一行都有两个值,值与值用逗号隔开。第一行一般作为标头,充当每 一“列”的列名。 如果你的数据保存在 Excel 文件里,那它多半也是以这种行、列结构保存的。要想 将它转换成 D3 可以使用的格式,先用 Exce 打开它,然后选择“另存为……”,再 选择 CSV 文件类型。 假设把前面的 CSV 数据保存在 food.csv 中,那么就可以用 D3 的 d3.csv() 方法来 加载它: d3.csv("food.csv", function(data) { console.log(data); }); csv() 接受两个参数:表示 CSV 文件路径的字符串和用作回调函数的匿名函数。回 调函数只有在把 CSV 文件加载到内存之后才会执行。因此可以确定,在调用这个函 数时,d3.csv() 已经把数据加载完了。 回调函数在执行时,会接收到加载和解析后的 CSV 数据。姑且把它命名为 data 吧,你叫它什么都行。剩下的事情就都交给回调函数办了。在前面的例子中,回调 数据 | 69 函数只是把接收到的数组数据输出到了控制台,以便验证,如图 5-3 所示。(参见 03_csv_loading_example.html。) 图 5-3:输出到控制台的数组数据 可以看到数据保存在数组中(因为两头儿各有一个方括号),而且有 3 个元素,每个 元素都是一个对象。点开每个对象旁边的小三角,可以看到它们的值,如图 5-4 所示。 图 5-4:扩展开的数组元素 哇!每个对象都有 Food 和 Deliciousness 属性,与 CSV 文件中的值是一一对应 的。(还有第三个属性,叫 __proto__,这个属性与 JavaScript 处理对象有关,现 在不用管它。)D3 利用了 CSV 文件的第一行作为属性名,后续行作为属性值。这样 一来就为我们节省了很多时间。 另外,还要注意:CSV 中的每个值都是以字符串形式保存的,连数字都是。(因为 数字 9 两边有双引号,即是 "9",而非 9。)这一点可能会影响后面的操作,因为你 会把它当数值,但实际上它却是一个字符串。 处理数据加载错误 d3.csv()是一个异步方法。什么意思?就是说在它加载数据的同时,其他 JavaScript代码会照样执行。(D3中其他加载外部资源的方法也都一样,比如 d3.json()。) 异步行为有时候不太好理解,因为我们可能会主观地认为CSV文件已经加载完 了,但事实上还没有。此时常见的错误,就是在回调函数外面引用外部数据。记 住,为了免得日后头疼,只能在回调函数内部(或回调函数调用的其他函数中) 引用数据。 我个人喜欢先声明一个全局变量,然后再调用d3.csv()加载数据。在回调函数 中,把数据复制给这个全局变量(以方便后面的函数使用该数据),最后再调用 依赖数据显示图形的其他函数。例如: 70 | 第 5 章 var dataset; // 全局变量 d3.csv("food.csv", function(data) { dataset = data; // 加载完毕,就将其复制到 dataset. generateVis(); // 再调用其他依赖数据 hideLoadingMsg(); // 显示图形的函数 }); 还有一点更不好理解,回调函数无论是否成功加载数据文件,都会执行。网络连 接可能断开,文件名可能拼错,或者Web服务器出了什么毛病,都可能导致数据 加载失败。但无论如何,回调函数都会执行。如果加载数据失败,那再调用依赖 数据的函数,可能就会在控制台中看到错误,图表也不会出现。这种情况很少发 生,但知道如何处理很重要。 可以在回调函数的定义中增加一个可选的error参数。如果加载文件遇到问题, error中将包含Web服务器返回的错误消息,data就是undefined。如果文件加 载成功,没发生错误,那么error的值就是null,data将保存着相应的数据。注 意,error作为参数必须放在第一位,data是第二个: var dataset; d3.csv("food.csv", function(error, data) { if (error) { // 如果 error 不是 null,肯定出错了 console.log(error); // 输出错误消息 } else { // 如果没出错,说明加载文件成功了 console.log(data); // 输出数据 // 包含成功加载数据后要执行的代码 dataset = data; generateVis(); hideLoadingMsg(); } }); 在 d3.csv() 的回调函数中验证数据很方便,但要在数据加载完毕后继续构建可视 化图表,那最好还是再调用其他函数,比如: var dataset; // 声明全局变量 d3.csv("food.csv", function(data) { // 把 CSV 数据交给全局变量 // 以方便将来使用这些数据 dataset = data; // 调用生成可视化图表的 // 其他函数,比如: generateVisualization(); 数据 | 71 makeAwesomeCharts(); makeEvenAwesomerCharts(); thankAwardsCommittee(); }); 再提醒一下:如果你的数据是 TSV 文件,可以试试 d3.tsv() 方法。这个方法的其 他方面与 d3.csv() 都一样。 2. 加载JSON数据 关于 JSON 数据,后面我们还会仔细讲解。现在,只要知道使用 d3.json() 方法可 以像使用 d3.csv() 一样加载 JSON 数据就行了: d3.json("waterfallVelocities.json", function(json) { console.log(json); // 输出到控制台 }); 这里,我把解析后的输入命名为 json,你可以随便命名。 5.2.3 作出你的选择 数据讲完了,下面就以这个简单数组为例: var dataset = [ 5, 10, 15, 20, 25 ]; 接下来需要决定选择什么。换句话说,你想让哪个元素与数据关联?同样,为简单 起见,我们假设每个段落显示一个值。那么,你可能觉得代码应该这样来写: d3.select("body").selectAll("p") 你或许没有错,但别忘了,我们想要选择的段落还都不存在呢。这就提出了使用 D3 时的一个常见的问题:怎么选择还不存在的元素?别急,因为答案可能会让你费点 脑筋。 答案就在 enter() 这个真正的魔术方法里。先看下面的代码,稍后解释: d3.select("body").selectAll("p") .data(dataset) .enter() .append("p") .text("New paragraph!"); 打开示例代码中的 04_creating_paragraphs.html,可以看到 5 个新段落,每个段落中 的内容都一样,如图 5-5 所示。 72 | 第 5 章 图 5-5:动态生成的段落 下面是详细的解释。 d3.select("body")• 选择 DOM 中的 body 元素,把它交给连缀方法中的下一个方法。 .selectAll("p")• 选择 DOM 中的所有段落。因为还没有段落,所以返回空元素。可以认为这个空 元素代表马上就会创建的段落。 .data(dataset)• 解析并数出数据值。dataset 数组中有 5 个值,因而此后的所有方法都将执行五 遍,每次针对一个值。 .enter()• 要创建新的绑定数据的元素,必须使用 enter()。这个方法会分析当前选择的 DOM 元素和传给它的数据,如果数据值比对应的 DOM 元素多,就创建一个新 的占位元素。然后把这个新占位元素的引用交给链中的下一个方法。 .append("p")• 取得由 enter() 创建的空占位元素,并把一个 p 元素追加到相应的 DOM 中。太 棒了!然后它再把自己刚创建的元素交给链中的下一个方法。 .text("New paragraph!")• 取得新创建的 p 元素,插入文本值。 5.2.4 绑定及确定 好的!现在已经读取、解析了数据,并将数据绑定到了在 DOM 中创建的 p 元素。 不信?打开 04_creating_paragraphs.html,然后看看 Web 检查器,如图 5-6 所示。 数据 | 73 图 5-6:Web 检查器显示了新创建的元素 好,确实有 5 个段落,但数据在哪儿呢?切换到 JavaScript 控制台,输入以下代码, 按回车。结果如图 5-7 所示。 console.log(d3.selectAll("p")) 图 5-7:控制台中的输出 有一个数组!确切地说,是包含另一个数组的数组。单击灰色三角查看数组内容, 如图 5-8 所示。 可以看到 5 个 p,编号从 0 到 4。单击第一个(编号为 0)旁边的三角,可以看到如 图 5-9 所示的结果。 看到了吗,你看到了吗?太激动了,我都快忍不住了。在这儿呢!(见图 5-10)。 74 | 第 5 章 图 5-8:展开后的数组的数组 图 5-9:扩展后的 p 元素 图 5-10:最后一步,绑定的数据 数据中的第一个值,数字 5,出现在了第一个段落的 __data__ 属性中。单击其他 段落,也可以看到它们的 __data__ 属性分别包含 10、15、20 和 25。 你看,D3 绑定的数据没有出现在 DOM 中,而是作为该元素的 __data__ 属性保存 于内存中。控制台就是确认数据绑定是否正确的一个工具。 数据准备好了,下面该考虑对它做点什么了。 数据 | 75 5.3 使用自己的数据 我们看到数据已经加载到页面中,而且也绑定到了 DOM 中新创建的元素。但怎么 利用这些数据呢?以下是目前为止的代码: var dataset = [ 5, 10, 15, 20, 25 ]; d3.select("body").selectAll("p") .data(dataset) .enter() .append("p") .text("New paragraph!"); 把最后一行改一改: .text(function(d) { return d; }); 看看包含新代码的 05_creating_paragraphs_text.html,结果应该如图 5-11 所示。 图 5-11:显示数据的段落 哇喔!我们用数据填充了每个段落,机关都在 data() 方法里。在连缀方法中,只 要调用 data() 了,就可以随时创建一个接收 d 为输入的匿名函数。与当前元素对 应,方法 data() 确保了每个 d 都会被赋予原始数据集中的一个值。 随着 D3 遍历每个元素,“当前元素”的这个值也会跟着变化。比如,循环到第 三次时,代码会创建第三个 p 元素,而 d 就会被赋予数据集中的第三个值(即 dataset[2])。于是,第三个段落的文本就是“15”。 5.3.1 自定义函数 一般我们在写新函数(也叫方法)的时候,基本的结构如下所示: function(input_value) { // 完成一些计算 return output_value; } 76 | 第 5 章 前面我们使用的函数极其简单,没什么可说的: function(d) { return d; } 这种函数就叫匿名函数(anonymous function),因为它没有名字。相对而言,把函 数保存在一个变量中,那个函数就是命名函数(named function): var doSomething = function() { // 执行某些操作的代码 }; 使用 D3 的过程中会写大量匿名函数。匿名函数是访问个别数据值并计算动态属性 的关键所在。 我们例子中的匿名函数写在了 D3 的 text() 函数中,因此它会先执行,然后把执 行结果交给 text()。而 text() 完成最后的工作(将接收到的参数值作为文本插入 到选中的 DOM 元素中): .text(function(d) { return d; }); 不过,我们可以多做一点,因为这个函数是我们自己可以控制的,可以在里面写任何代 码。没错,能写自己的代码既是好事,也是坏事。比如,可以像下面这样加上几个字: .text(function(d) { return "I can count up to " + d; }); 这样就得到了如图 5-12 所示的结果,参见示例文件 06_creating_paragraphs_counting.html。 图 5-12:修改后的段落文本 5.3.2 数据需要拥抱 有读者可能会问:为什么要写成 function(d) { ... },而不是直接传入 d 呢? 比如,这样: 数据 | 77 .text("I can count up to " + d); 此时此刻,如果不把 d 封装在匿名函数里,d 就没有值。可以想象成这个寂寞的小 占位符需要一点温暖,包括来自和蔼可亲的函数圆括号的拥抱。(千万不要想太多, 否则你会觉得匿名函数的拥抱简直要吓死人了,而且还会把问题复杂化。) 因此,需要把数据值轻轻地放到一个函数的怀抱里: .text(function(d) { // <-- 注意,温柔的拥抱在左边 return "I can count up to " + d; }); 实际原因是 D3 中 text()、attr(),还有很多其他方法,都可以接收函数作为参 数。比如,text() 既可以接收一个静态的字符串作为参数 .text("someString") 也可以接收某个函数的返回值: .text(someFunction()) // 一般来说,someFunction() 应该也会返回字符串 或者一个匿名函数写成这样也可以作为参数: .text(function(d) { return d; }) 最后传入的是匿名函数。如果 D3 发现它是一个函数,就会调用它,同时将当前数 据值 d 作为参数传进去。这里把参数命名为 d 只是为了方便,你也可以叫它 datum 或 info 或其他任何名字。D3 只关心要有一个参数,有一个参数才能把当前数据值 传进来。本书将全部使用 d,因为它很简洁,而且与 D3 的很多在线示例一致。 任何情况下,没有那个函数,D3 将无法把当前数据值传出来。没有匿名函数及其接 收数据值的参数 d,D3 会不知所措,甚至会号啕大哭。(D3 比你想象得更容易激动。) 一开始的时候,你可能会觉得为了拿到 d 的值这么做有点笨,因为多费了一些事。 但随着处理的数据越来越复杂,你会慢慢发现这种方法的重要性。 5.2.3 添加样式 了解 D3 的其他方法越多,就会发现越有意思。比如,attr() 和 style() 可以分 别用来设置取得的元素的 HTML 属性和 CSS 属性。 比如,在前面代码中再加一行: 78 | 第 5 章 .style("color","red"); 就 会 得 到 图 5-13 所 示 的 结 果, 可 以 参 考 示 例 文 件 07_creating_paragraphs_with_ style.html。 图 5-13:段落文本变成红色了 所有文本都变成了红色,太好了。不过,我们可以再写一个自定义函数,只让那些 数值大于某个阈值的文本显示为红色。于是,可以把代码中的最后一个字符串替换 成一个函数,改成下面这样: .style("color", function(d) { if (d > 15) { // 阈值是 15 return "red"; } else { return "black"; } }); 打 开 示 例 文 件 08_creating_paragraphs_with_style_functions.html, 就 可 以 看 到 图 5-14 所示的结果。 图 5-14:动态为段落文本添加样式 好,前三行文本是黑色的,而数值超过 15 的后两行文本都变成了红色的。 这一章到现在,我们已经学习了加载数据、动态创建绑定到该数据的 DOM 元素。 我想,接下来该学习绘制数据了! 79 第 6 章 基于数据绘图 这一章学习基于数据绘制图形。 现在,仍然以这个最简单的数据集为例: var dataset = [ 5, 10, 15, 20, 25 ]; 6.1 绘制DIV 我们要使用这几个值来生成最简单的条形图。条形图实际上就是矩形,而 HTML 的 div 元素是绘制矩形的最简单手段。(同样,对浏览器来说,一切都是矩形。所以, 你也可以把这个例子中的 div 换成 span 或其他别的元素试试看。) 严格来说,柱形图(column chart)指的是矩形沿垂直方向度量的图形,而沿水平方 向度量的矩形叫条形图(bar chart)。不过,大多数人都不区分这两个概念,而统一 称其为条形图,我们也沿用这一惯例。如图 6-1 所示,div 作为表示数据的条形是 很合适的。 图 6-1:其貌不扬的 div
对于讲 Web 标准的人来说,这样做可就犯了大忌。一般来说,不能为了展示而使用 空 div。不过,对我们的例子而言,这属于例外。 因为是一个 div,所以它的宽度和高度都通过 CSS 样式设定。对生成图表而言,除 了高度,其他属性都应该是共享的。为此,我们把公共的属性放到一个叫 bar 的类 里面,放在文档头部的 style 标签中: div.bar { display: inline-block; width: 20px; height: 75px; /* 后面会覆盖这个高度 */ background-color: teal; } 接下来应该给每个 div 都应用 bar 类,以便这些 CSS 规则产生作用。如果通过手 工来做,需要这么写:
而使用 D3,给元素添加类要使用 selection.attr() 方法。理解 attr() 和相似 的 style() 的区别很重要:attr() 设定 DOM 属性的值,而 style() 直接给元素 添加 CSS 样式。 6.1.1 设定属性 attr() 用于设定 HTML 元素的属性和值。而 HTML 属性就是包含在尖括号 <> 中 的任意属性 / 值对。比如,下面的标签