D3 学习笔记二:创建柱状图,第一部分

(原文地址:https://bost.ocks.org/mike/bar/,原文作者:Mike Bostock

简介:出于个人学习目的,计划将 D3 的学习资料逐步翻译为系列文章,鉴于本人水平有限,如有不到或错误之处,欢迎大家指正,谢谢!

概述

如果你有一小拨数据,一个数组:

var data = [4, 8, 15, 16, 23, 42];

简而来说,一个柱状图就是比例上来正确呈现该数据的恰当图表。本篇教程将介绍如何使用 D3 Javascript 函数库来创建柱状图。首先,我们将介绍 HTML 版的原始数据法,然后是更复杂的 SVG 方法,最后提供一个带动态过渡的版本。

本篇教程假设您具备网页开发的基础知识:了解如何编辑页面并用浏览器查看、如何加载 D3 程序库等。你也会发现简单的从 CodePen 模板修改是最简单的开始方式。

选择一个元素

在一般的 JavaScript 编程中,通常一次只能处理一个元素,比如要创建一个 div 元素、设置它的内容,然后将其添加到body

var div = document.createElement("div");

div.innerHTML = "Hello, world!";
document.body.appendChild(div);

有了 D3 (当然 jQuery 等类似函数库也一样),您可以通过选择器来实现同样的功能,通过大批的处理选择结果并赋予它们丰富的操作功能,您可以任意处理器中想要的内容而不用重复数写代码。虽然这看起来像一个小小的挑战,消灭循环和其他程序流控制可以让您的代码更加整洁。

选择器可以通过很多种方式实现,最常见的是通过请求一个通过特殊符号构建的选择符字符串(比如 div.foo 等),比如创建一个单一元素的选择符:

var body = d3.select("body");
var div = body.append("div");

div.html("Hello, world!");

您也可以同样的处理多种元素:

var section = d3.selectAll("section");
var div = section.append("div");

div.html("Hello, world!");

修改操作的串接

另一种方便的选择法是通过对选择器的串接:选择函数,比如 selection.attr 返回当前的选择。这样,您可以继续对该元素进行操作,比如设置 body 的背景色和文字颜色:

var body = d3.select("body");

body.style("color", "black");
body.style("background-color", "white");

bodybodybody!”反复的呼叫 body 法可以通过如下的操作串接消除:

d3.select("body")
  .style("color", "black")
  .style("background-color", "white");

注意,我们再也没有使用一个 var 类型声明,在进行毕要的操作之后,选择结果可以被丢弃。操作串接让您可以缩短代码,减少过多用于变量命名的时间消耗。

在学到方法串接的好处之后我们要留意,大多数操作都返回同样的选择内容,然而有些方法却不这样!比如,selection.append 返回仅包含新元素的选择,从而让您可以对新元素进行串接操作:

d3.selectAll("section")
  .attr("class", "special")
  .append("div")
    .html("Hello, world!"); // 注意:本教程中刻意使用缩进来呈现结构关系。

方法串接只能让选择内容不断沉入文档结构内,此时使用 var 可以保留中间过程从而返回原状态:

var section = d3.selectAll("section");

section.append("div")
  .html("First!");

section.append("div")
  .html("Second.");

手动创建图表

现在,假设您需要抛弃 JavaScript 而写一个柱状图,反正在数据表中只有六个数据,所以手工写几个 div 并不是复杂的事情,根据数据设置好它们的宽度,不就完成了:

<!DOCTYPE html>
<html>
<head>
  <style>
  .chart div {
    font: 10px sans-serif;
    background-color: steelblue;
    text-align: right;
    padding: 3px;
    margin: 1px;
    color: white;
  }
  </style>
</head>
<body>
  <div class="chart">
    <div style="width: 40px;">4</div>
    <div style="width: 80px;">8</div>
    <div style="width: 150px;">15</div>
    <div style="width: 160px;">16</div>
    <div style="width: 230px;">23</div>
    <div style="width: 420px;">42</div>
  </div>
</body>
</html>

结果显示如下:

本图用一个 div 做容器,每个柱子采用一个子节点 div,子 div 拥有蓝色背景色与白色前景色,呈现出了向右对齐的文字标签。当然您可以移掉容器 div 而让实现更简洁,但一般来说,您的页面在图标之外还有其他内容,表格容器可以让您不影响其他页面的情况下布局和样式化图表。

自动创建图表

手工绘制并不适合现实中绝大多数数据源,本文的目的正是教您如何自动通过数据创建图表,所以让我们从一个仅包含一个类别为 chartdiv 的空页面开始,用 D3 创建同样的图表:

d3.select(".chart")
  .selectAll("div")
    .data(data)
  .enter().append("div")
    .style("width", function (d) { return d * 10 + "px"; })
    .text(function(d) {return d;});

虽然部分代码看起来熟悉,我们引入了一个新概念:数据接合。下面让我们将它拆开,将以上简洁代码写成原本的加长格式,从而展现它的工作原理。

首先,我们使用类别选择器得到图表容器:

var chart = d3.select(".chart");

下一步,我们通过定义需要接合的部位来接合数据:

var bar = chart.selectAll("div");

数据接合,便是在数据改变时,使用标准模式来创建、更新和销毁元素。这感觉上有些奇怪,好处是您可以只学此单一的模式来改变页面,所以无论您构建静态图表还是动态渐变图表时都可以保持代码一致,就像您选择希望存在的元素时的初始选择器。(见“使用接合思考”)。

下一步,我们使用 selection.data 将数据接合到选择结果上:

var barUpdate = bar.data(data);

因为我们知道选择结果为空,其返回的 updateexit 选择结果同样为空,我们只需要处理包含了不存在的新数据的 enter 选择结果。

var barEnter = bar.Update.enter().append("div");

下面我们根据关联的数据为每个新柱子设置宽度 d

barEnter.style("width", function (d) {
  return d * 10 + "px";
});

因为这些元素是通过数据接合创建的,每个柱子已经绑定了数据,我们通过传递一个计算宽度风格的函数来恰当设置柱子的尺寸。

最后,我们使用一个函数来设置每个柱子的文字标签:

barEnter.text(function (d) {
  return d;
});

D3 的选择器操作符,比如 attrstyleproperty,允许您将数值设置为常量(每个选择元素相同)和函数(每个单独计算)两种方式。如果某个属性必须基于元素绑定的数据计算,那么就使用函数来实现,如果每个元素都相同,那么就使用字符串或数值来赋值。

用缩放匹配画幅

以上代码的软肋,是我们使用固定数字 10 来转换每个柱子和数据的关系。该数字应当取决于数据集内容的分布区域(0到42)和图表所定制的宽度(420),当然,针对以上图表,我们选择 10 是没有问题的。

我们可以使用 线性缩放 功能让以上数量关系更加明确,并消灭魔术数字 10,D3 的缩放功能输入为数据范围及显示尺寸:

var x = d3.scale.linear()
  .domain([0, d3.max(data)])
  .range([0, 420]);

虽然,在这里 x 看起来像个对象,它也是一个包含根据输入数值转换原始数据的函数。比如,传入 4 可以返回 40, 输入 16 返回 160。为了使用新的缩放,我们将手写的乘法转换代码变为呼叫该函数:

d3.select(".chart")
  .selectAll("div")
    .data(data)
  .enter().append("div")
    .style("width", function (d) {
      return x(d) + "px";
    })
    .text(function (d) {
      return d;
    });

下一篇:创建柱状图,第二部分

本篇原始柱状图的呈现很容易实现,但很显然也是有限的。柱状图应当有格线来辅助数据对比,您也可能更喜欢垂直的柱状图,或者您想要其他不同的图表类型,比如饼状图和流状图等。为了更好的视觉表达,您需要下一篇我们将呈现的 SVG 技术。

个人笔记

本篇教程之中,D3 的原创作者通过“非图表”技术展示了 D3 的选择器与修改器等功能,然而,选择器与修改器仅仅是 D3 框架之中最基础部分的功能,作者书写本教程的目的应该是为读者打下开发的基础,而实际并未深入 D3 作为图形绘制系统的实质核心内容。

使用 div 绘制图表,可能是十年前 Web 技术没有发展到 HTML5 时,使用网站进行数据呈现的“权宜之计”。然而,当今 HTML5 技术将 Web 技术提高到了一定的高度,通过 svgcanvas 标签,如今的浏览器已经成为了强大的矢量绘图工具(当然,还有不可忽略的 webgl 图形接口)。

D3 在 v3 版本及之前,还是一个一体化开发的普通插件,在 v4 到来之时,D3 进行了技术上的革新,将原本一体的 Javascript 代码拆分成了几十个可以独立使用的模块,并已经完整提供了对 svg 绘图组件的全面支持。在 v5 到来之后,D3 完整支持了 canvas 标签,并且提供了两者可以随意选择的技术方案。

我们作为 D3 框架的学习者,特别是掌握了 jQuery 插件使用方法的读者们肯定感觉到了内容的枯燥。但是,作为未来的 D3 应用者,我们会在未来的开发使用中越来越发现这些基础知识的重要性,因为每一幅通过 D3 绘制的 Web 图表都必须使用以上所描述的内容。

在下一篇,作者将深入 D3 的 svg 支持功能之中,带领大家进入真正的 D3 领域,来体会高效的绘图技术和接口。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • 1.发现故事 本课讲述可视化用到的:叙事结构数据收集过程数据处理 2.新闻方法 给可视化添加语境围绕数据进行叙事 ...
    esskeetit阅读 2,777评论 0 2
  • d3 (核心部分)选择集d3.select - 从当前文档中选择一系列元素。d3.selectAll - 从当前文...
    谢大见阅读 3,424评论 1 4
  • 1. 动画与互动 在叙事结构中全面应用创意D3如何帮你在可视化图表中添加动画与互动用地理特征创建D3地图了解别人如...
    esskeetit阅读 831评论 1 0
  • 搜集了网上一些常用的开发小技巧,集中到一起看-,- 1 TableView下面的那些cell的空显示 2 Sc...
    不会学习的睿睿阅读 214评论 0 0
  • 词丨印安淋 答答屋檐雨滴响 空调的暖让我推开了窗 满地雪白映入眼帘 我要裹紧我的衣裳 冷 是没有关掉保险的枪 ...
    歌词写作印安淋阅读 329评论 0 0