可视化工具D3教程

可视化工具D3教程

    • 第1章 D3简介
    • 第2章 第一个程序 Hello World
    • 第3章 正式进入D3的世界
    • 第4章 选择、插入、删除元素
    • 第5章 做一个简单的图表
    • 第6章 比例尺的使用
    • 第7章 坐标轴
    • 第8章 完整的柱形图
    • 第9章 让图表动起来
    • 第10章 理解 update()、enter()、exit()
    • 第11章 交互式操作
    • 第12章 布局
    • 第13章 饼状图
    • 第14章 力导向图
    • 第15章 树状图
    • 第16章 地图可视化
    • 学习D3的站点
    • 建议

第1章 D3简介


近年来,可视化越来越流行,许多报刊杂志、门户网站、新闻媒体都大量使用可视化技术,使得复杂的数据和文字变得十分容易理解,有一句谚语“一张图片价值于一千个字”。D3 正是数据可视化工具中的佼佼者,基于 JavaScript 开发,项目托管于 GitHub。从 D3诞生以来,不断受到好评,在 GitHub 上的项目仓库排行榜也不断上升。可视化越来越流行,许多报刊杂志、门户网站、新闻、媒体都大量使用可视化技术,使得复杂的数据和文字变得十分容易理解,有一句谚语“一张图片价值于一千个字”,的确是名副其实。各种数据可视化工具也如井喷式地发展,D3 正是其中的佼佼者。D3 的全称是(Data-Driven Documents),顾名思义可以知道是一个被数据驱动的文档。听名字有点抽象,说简单一点,其实就是一个 JavaScript 的函数库,主要是用来做数据可视化。

学习D3前的预备知识

  • HTML:超文本标记语言,用于设定网页的内容
  • CSS:层叠样式表,用于设定网页的样式
  • JavaScript:一种直译式脚本语言,用于设定网页的行为
  • DOM:文档对象模型,用于修改文档的内容和结构
  • SVG:可缩放矢量图形,用于绘制可视化的图形

D3“安装”

D3 是一个 JavaScript 函数库,并不需要通常所说的“安装”。它只有一个文件,在 HTML 中引用即可。目前D3已经更新到V5版本。因为原理是差不多的,所以仍然以V3版本为例作讲解。

  • 下载D3.js文件 D3.zip

  • 加载D3.js文件

//直接包含网络的链接

//加载本地文件

第2章 第一个程序 Hello World

1.在HTML 中输出 HelloWorld

	 
  	 
     
    HelloWorld 
  	 
     
    	

Hello World

输出结果

    Hello World

2.用JS更改Hello World

     
     
             
            HelloWorld 
     
         
        

Hello World

输出结果

    SWUSTVIS

3.用D3更改Hello World


     
       
             
            HelloWorld 
             
      
      
           

Hello World

输出结果

    SWUSTVIS

第3章 正式进入D3的世界

选择元素和绑定数据是 D3 最基础的内容,本章将对其进行一个简单的介绍。
可视化工具D3教程_第1张图片

  1. 选择集

在 D3 中,用于选择元素的函数有两个,这两个函数返回的结果称为选择集。
d3.select():选择所有指定元素的第一个
d3.selectAll():选择指定全部元素

例如,选择集的常见用法如下。

    var body = d3.select("body"); //选择文档中的body元素
    var p1 = body.select("p");      //选择body中的第一个p元素
    var p = body.selectAll("p");    //选择body中的所有p元素
    var svg = body.select("svg");   //选择body中的svg元素
    var rects = svg.selectAll("rect");  //选择svg中所有的rect元素
    var id = body.select("#id"); //选择body中id元素
    var class = body.select(".class");//选择body中class类元素

选择元素函数后常用链式表达接其他操作,如:

d3.select("#id").text("SWUSTVIS").attr("font-size","12px");
  1. 绑定数据

选择集和绑定数据通常是一起使用的,D3 中是通过以下两个函数来绑定数据的:
datum():绑定一个数据到选择集上
data():绑定一个数组到选择集上,数组的各项值分别与选择集的各元素绑定

假设现在有三个段落元素如下:

对于datum():
假设有一字符串 SWUSTVIS,要将此字符串分别与三个段落元素绑定,代码如下:

var str = "SWUSTVIS";
var body = d3.select("body");
var p = body.selectAll("p");
p.datum(str);
p.text(function(d, i){
    return "第 "+ i + " 个元素绑定的数据是 " + d;
});

绑定数据后,使用此数据来修改三个段落元素的内容,其结果如下:

第 0 个元素绑定的数据是 SWUSTVIS
第 1 个元素绑定的数据是 SWUSTVIS
第 2 个元素绑定的数据是 SWUSTVIS

对于data():

有一个数组,接下来要分别将数组的各元素绑定到三个段落元素上。

I love three things in this world.Sun, moon and you. Sun for morning, moon for night, and you forever.

var dataset = ["sun","moon","you"];

调用 data() 绑定数据,并替换三个段落元素的字符串为被绑定的字符串,代码如下:

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

p.data(dataset)
  .text(function(d, i){
      return "I love " + d;
  });

结果自然是三个段落的文字分别变成了数组的三个字符串。

I love sun

I love moon

I love you

前面代码也用到了一个无名函数 function(d, i),其对应的情况如下:

d ------- data    数据
i ------- index   索引

当 i == 0 时, d 为 sun。
当 i == 1 时, d 为 moon。
当 i == 2 时, d 为 you。

此时,三个段落元素与数组 dataset 的三个字符串是一一对应的,在函数 function(d, i) 直接 return d 即可。

第4章 选择、插入、删除元素

可视化工具D3教程_第2张图片
1.选择元素
假设在 body 中有三个段落元素:

Sun

Moon

You

现在,要分别完成以下四种选择元素的任务。

选择第一个元素

d3.select("body").select("p").style("color","red");

可视化工具D3教程_第3张图片
选择第所有元素

d3.select("body").selectAll("p").style("color","red");

可视化工具D3教程_第4张图片
选择第二个元素

方法很多,一种比较简单的是给第二个元素添加一个 id 号。

Moon

------------------------------------- d3.select("#moon").style("color","red");

可视化工具D3教程_第5张图片
选择后两个元素

给后两个元素添加 class,

Moon

You

由于需要选择多个元素,要用 selectAll。

d3.selectAll(".myclass").style("color","red");

可视化工具D3教程_第6张图片
2.插入元素

插入元素涉及的函数有两个:

  • append():在选择集末尾插入元素
  • insert():在选择集前面插入元素

假设有三个段落元素,与上文相同。

append()

d3.select("body").append("p").text("Star");

可视化工具D3教程_第7张图片
insert()

d3.select("body").insert("p","#moon").text("Star");

可视化工具D3教程_第8张图片
3.删除元素

删除一个元素时,对于选择的元素,使用 remove 即可。

  • remove()
d3.select("#moon").remove();

可视化工具D3教程_第9张图片

第5章 做一个简单的图表

柱形图是一种最简单的可视化图标,主要有矩形、文字标签、坐标轴组成。为简单起见,只绘制矩形的部分,用以讲解如何使用 D3 在 SVG 画布中绘图。

画布是什么

之前处理对象都是 HTML 的文字,没有涉及图形的制作。要绘图,首要需要的是一块绘图的“画布”。HTML 5 提供两种强有力的“画布”:SVGCanvas

SVG 绘制的是矢量图,因此对图像进行放大不会失真,可以为每个元素添加 JavaScript 事件处理器。每个图形均视为对象,更改对象的属性,图形也会改变。要注意,在 SVG 中,x 轴的正方向是水平向右,y 轴的正方向是垂直向下的。

可视化工具D3教程_第10张图片
在 canvas 中,一旦图形被绘制完成,它就不会继续得到浏览器的关注。如果其位置发生变化,那么整个场景也需要重新绘制,包括任何或许已被图形覆盖的对象。

添加画布

D3 虽然没有明文规定一定要在 SVG 中绘图,但是 D3 提供了众多的 SVG 图形的生成器,它们都是只支持 SVG 的。因此,建议使用 SVG 画布。

使用 D3 在 body 元素中添加 svg 的代码如下。

var width = 300;  //画布的宽度
var height = 300;   //画布的高度

var svg = d3.select("body")     //选择文档中的body元素
    .append("svg")          //添加一个svg元素
    .attr("width", width)       //设定宽度
    .attr("height", height);    //设定高度

有了画布,接下来就可以在画布上作图了。

绘制矩形

绘制一个横向的柱形图。只绘制矩形,不绘制文字和坐标轴。在 SVG 中,矩形的元素标签是 rect。
例如:





矩形的属性,常用的有四个:

x:矩形左上角的 x 坐标
y:矩形左上角的 y 坐标
width:矩形的宽度
height:矩形的高度

现在给出一组数据,要对此进行可视化。

var dataset = [ 250 , 210 , 170 , 130 , 90 ];  //数据(表示矩形的宽度)

var width = 300;  //画布的宽度
var height = 300;   //画布的高度

var svg = d3.select("body")     //选择文档中的body元素
    .append("svg")          //添加一个svg元素
    .attr("width", width)       //设定宽度
    .attr("height", height);    //设定高度
    
var rectHeight = 25;   //每个矩形所占的像素高度(包括空白)

svg.selectAll("rect")
    .data(dataset)
    .enter()
    .append("rect")
    .attr("x",20)
    .attr("y",function(d,i){
         return i * rectHeight;
    })
    .attr("width",function(d){
         return d;
    })
    .attr("height",rectHeight-2)
    .attr("fill","steelblue");

可视化工具D3教程_第11张图片
PS:横向变纵向?

svg.selectAll("rect")
    .data(dataset)
    .enter()
    .append("rect")
    .attr("y",function(d,i){
         return height - d;
    })
    .attr("x",function(d,i){
         return i * rectHeight;
    })
    .attr("height",function(d){
         return d;
    })
    .attr("width",rectHeight-2)
    .attr("fill","steelblue");

可视化工具D3教程_第12张图片
PS:上面的例子是值和像素大小是一样的,那么就会出现一个问题(引入比例尺)。

第6章 比例尺的使用

比例尺是 D3 中很重要的一个概念,上一章里曾经提到过直接用数值的大小来代表像素不是一种好方法,本章正是要解决此问题。
可视化工具D3教程_第13张图片
为什么需要比例尺

上一章制作了一个柱形图,当时有一个数组:

var dataset = [ 250 , 210 , 170 , 130 , 90 ];

绘图时,直接使用 250 给矩形的宽度赋值,即矩形的宽度就是 250 个像素。此方式非常具有局限性,如果数值过大或过小,例如:

var dataset_1 = [ 2.5 , 2.1 , 1.7 , 1.3 , 0.9 ];
var dataset_2 = [ 2500, 2100, 1700, 1300, 900 ];

对以上两个数组,绝不可能用 2.5 个像素来代表矩形的宽度,那样根本看不见;也不可能用 2500 个像素来代表矩形的宽度,因为画布没有那么长。于是,我们需要一种计算关系,能够将某一区域的值映射到另一区域,其大小关系不变,这就是比例尺(Scale)。

有哪些比例尺

比例尺,很像数学中的函数。例如,对于一个一元二次函数,有 x 和 y 两个未知数,当 x 的值确定时,y 的值也就确定了。在数学中,x 的范围被称为定义域,y 的范围被称为值域。D3 中的比例尺,也有定义域和值域,分别被称为 domain 和 range。开发者需要指定 domain 和 range 的范围,如此即可得到一个计算关系。

D3 提供了多种比例尺,下面介绍最常用的两种。

  • 线性比例尺
    线性比例尺,能将一个连续的区间,映射到另一区间。要解决柱形图宽度的问题,就需要线性比例尺。假设有以下数组:
var dataset = [1.2, 2.3, 0.9, 1.5, 3.3];

现有要求如下:

将 dataset 中最小的值,映射成 0;将最大的值,映射成 300。代码如下:

var min = d3.min(dataset);
var max = d3.max(dataset);

var linear = d3.scale.linear()
        .domain([min, max])
        .range([0, 300]);

linear(0.9);    //返回 0
linear(2.3);    //返回 175
linear(3.3);    //返回 300

其中,d3.scale.linear() 返回一个线性比例尺。domain() 和 range() 分别设定比例尺的定义域和值域。在这里还用到了两个函数,它们经常与比例尺一起出现:

d3.max()
d3.min()

这两个函数能够求数组的最大值和最小值,是 D3 提供的。按照以上代码,

比例尺的定义域 domain 为:[0.9, 3.3]

比例尺的值域 range 为:[0, 300]

因此,当输入 0.9 时,返回 0;当输入 3.3 时,返回 300。当输入 2.3 时呢?返回 175,这是按照线性函数的规则计算的。有一点请大家记住:d3.scale.linear() 是可以当做函数来使用的,才有这样的用法:linear(0.9)。

  • 序数比例尺
    有时候,定义域和值域不一定是连续的。例如,有两个数组:
    var index = [0, 1, 2, 3, 4];
    var color = ["red", "blue", "green", "yellow", "black"];

我们希望 0 对应颜色 red,1 对应 blue,依次类推。

但是,这些值都是离散的,线性比例尺不适合,需要用到序数比例尺。

var ordinal = d3.scale.ordinal()
        .domain(index)
        .range(color);

ordinal(0); //返回 red
ordinal(2); //返回 green
ordinal(4); //返回 black

用法与线性比例尺是类似的。

给柱形图添加比例尺

在上一章的基础上,修改一下数组,再定义一个线性比例尺。

var dataset = [ 2.5 , 2.1 , 1.7 , 1.3 , 0.9 ];

var linear = d3.scale.linear()
        .domain([0, d3.max(dataset)])
        .range([0, 250]);

var rectHeight = 25;   //每个矩形所占的像素高度(包括空白)

svg.selectAll("rect")
    .data(dataset)
    .enter()
    .append("rect")
    .attr("x",20)
    .attr("y",function(d,i){
         return i * rectHeight;
    })
    .attr("width",function(d){
         return linear(d);   //在这里用比例尺
    })
    .attr("height",rectHeight-2)
    .attr("fill","steelblue");

如此一来,所有的数值,都按照同一个线性比例尺的关系来计算宽度,因此数值之间的大小关系不变。
可视化工具D3教程_第14张图片

第7章 坐标轴

坐标轴,是可视化图表中经常出现的一种图形,由一些列线段和刻度组成。坐标轴在 SVG 中是没有现成的图形元素的,需要用其他的元素组合构成。D3 提供了坐标轴的组件,如此在 SVG 画布中绘制坐标轴变得像添加一个普通元素一样简单。

定义坐标轴

上一章提到了比例尺的概念,要生成坐标轴,需要用到比例尺,它们二者经常是一起使用的。下面,在上一章的数据和比例尺的基础上,添加一个坐标轴的组件。

//数据
var dataset = [ 2.5 , 2.1 , 1.7 , 1.3 , 0.9 ];
//定义比例尺
var linear = d3.scale.linear()
      .domain([0, d3.max(dataset)])
      .range([0, 250]);

var axis = d3.svg.axis() //坐标轴组件
     .scale(linear)      //指定比例尺
     .orient("bottom")   //指定刻度的方向
     .ticks(7);          //指定刻度的数量

在 SVG 中添加坐标轴

定义了坐标轴之后,只需要在 SVG 中添加一个分组元素 ,再将坐标轴的其他元素添加到组里即可。代码如下:

svg.append("g").call(axis);

可视化工具D3教程_第15张图片
设定坐标轴的样式和位置

默认的坐标轴样式不太美观,下面提供一个常见的样式:


分别定义了类 axis 下的 path、line、text 元素的样式。接下来,只需要将坐标轴的类设定为 axis 即可。

坐标轴的位置,可以通过 transform 属性来设定。

通常在添加元素的时候就一并设定,写成如下形式:

svg.append("g")
  .attr("class","axis")
  .attr("transform","translate(20,130)")
  .call(axis);

可视化工具D3教程_第16张图片

第8章 完整的柱形图

一个完整的柱形图包含三部分:矩形、文字、坐标轴。本章将对前几章的内容进行综合的运用,制作一个实用的柱形图,内容包括:选择集、数据绑定、比例尺、坐标轴等内容。

可视化工具D3教程_第17张图片
添加 SVG 画布

//画布大小
var width = 400;
var height = 400;

//在 body 里添加一个 SVG 画布   
var svg = d3.select("body")
    .append("svg")
    .attr("width", width)
    .attr("height", height);

//画布周边的空白
 var padding = {left:30, right:30, top:20, bottom:20};

定义数据和比例尺

//定义一个数组
var dataset = [10, 20, 30, 40, 33, 24, 12, 5];

//x轴的比例尺
var xScale = d3.scale.ordinal()
    .domain(d3.range(dataset.length))
    .rangeRoundBands([0, width - padding.left - padding.right]);

//y轴的比例尺
var yScale = d3.scale.linear()
    .domain([0,d3.max(dataset)])
    .range([height - padding.top - padding.bottom, 0]);

定义坐标轴

//定义x轴
var xAxis = d3.svg.axis()
    .scale(xScale)
    .orient("bottom");

//定义y轴
var yAxis = d3.svg.axis()
    .scale(yScale)
    .orient("left");

添加矩形和文字元素

//矩形之间的空白
var rectPadding = 4;

//添加矩形元素
var rects = svg.selectAll(".MyRect")
        .data(dataset)
        .enter()
        .append("rect")
        .attr("class","MyRect")
        .attr("transform","translate(" + padding.left + "," + padding.top + ")")
        .attr("x", function(d,i){
            return xScale(i) + rectPadding/2;
        } )
        .attr("y",function(d){
            return yScale(d);
        })
        .attr("width", xScale.rangeBand() - rectPadding )
        .attr("height", function(d){
            return height - padding.top - padding.bottom - yScale(d);
        })
        .attr("fill","steelblue");

//添加文字元素
var texts = svg.selectAll(".MyText")
        .data(dataset)
        .enter()
        .append("text")
        .attr("class","MyText")
        .attr("transform","translate(" + padding.left + "," + padding.top + ")")
        .attr("x", function(d,i){
            return xScale(i) + rectPadding/2;
        } )
        .attr("y",function(d){
            return yScale(d);
        })
        .attr("dx",function(){
            return (xScale.rangeBand() - rectPadding)/2;
        })
        .attr("dy",function(d){
            return 20;
        })
        .text(function(d){
            return d;
        })			
        .style({
		"fill":"#FFF",
		"text-anchor":"middle"
		});

添加坐标轴的元素

//添加x轴
svg.append("g")
  .attr("class","axis")
  .attr("transform","translate(" + padding.left + "," + (height - padding.bottom) + ")")
  .call(xAxis); 

//添加y轴
svg.append("g")
  .attr("class","axis")
  .attr("transform","translate(" + padding.left + "," + padding.top + ")")
  .call(yAxis);

可视化工具D3教程_第18张图片

第9章 让图表动起来

D3 支持制作动态的图表。有时候,图表的变化需要缓慢的发生,以便于让用户看清楚变化的过程。

什么是动态效果

前面几章制作的图表是一蹴而就地出现,然后绘制完成后不再发生变化的,这是静态的图表。动态的图表,是指图表在某一时间段会发生某种变化,可能是形状、颜色、位置等,而且用户是可以看到变化的过程的。

例如,有一个圆,圆心为 (100, 100)。现在我们希望圆的 x 坐标从 100 移到 300,并且移动过程在 2 秒的时间内发生。这种时候就需要用到动态效果,在 D3 里我们称之为过渡(transition)。

实现动态的方法

D3 提供了 4 个方法用于实现图形的过渡:从状态 A 变为状态 B。

  • transition() 启动过渡效果

其前后是图形变化前后的状态(形状、位置、颜色等等),例如:

.attr("fill","red")         //初始颜色为红色
.transition()               //启动过渡
.attr("fill","steelblue")   //终止颜色为铁蓝色

D3 会自动对两种颜色(红色和铁蓝色)之间的颜色值(RGB值)进行插值计算,得到过渡用的颜色值。我们无需知道中间是怎么计算的,只需要享受结果即可。

  • duration() 指定过渡的持续时间,单位为毫秒。

如 duration(2000) ,指持续 2000 毫秒,即 2 秒。

  • ease()指定过渡的方式,常用的有:

linear:普通的线性变化
circle:慢慢地到达变换的最终状态
elastic:带有弹跳的到达最终状态
bounce:在最终状态处弹跳几次
调用时,格式形如: ease(“bounce”)。

  • delay() 指定延迟的时间,表示一定时间后才开始转变,此函数可以对整体指定延迟,也可以对个别指定延迟。

例如,对整体指定时:

.transition()
.duration(1000)
.delay(500)

如此,图形整体在延迟 500 毫秒后发生变化,变化的时长为 1000 毫秒。因此,过渡的总时长为1500毫秒。

又如,对一个一个的图形(图形上绑定了数据)进行指定时:

.transition()
.duration(1000)
.delay(funtion(d,i){
    return 200*i;
})

如此,假设有 10 个元素,那么第 1 个元素延迟 0 毫秒(因为 i = 0),第 2 个元素延迟 200 毫秒,第 3 个延迟 400 毫秒,依次类推….整个过渡的长度为 200 * 9 + 1000 = 2800 毫秒。

实现简单的动态效果

下面将在 SVG 画布里添加三个圆,圆出现之后,立即启动过渡效果。

第一个圆,要求移动 x 坐标。

var circle1 = svg.append("circle")
        .attr("cx", 100)
        .attr("cy", 100)
        .attr("r", 45)
        .style("fill","green");

//在1秒(1000毫秒)内将圆心坐标由100变为300
circle1.transition()
    .duration(1000)
    .attr("cx", 300);

第二个圆,要求既移动 x 坐标,又改变颜色。

var circle2 = svg.append("circle")
        .attr("cx", 100)
        .attr("cy", 100)
        .attr("r", 45)
        .style("fill","green");//与第一个圆一样,省略部分代码

//在1.5秒(1500毫秒)内将圆心坐标由100变为300,
//将颜色从绿色变为红色
circle2.transition()
    .duration(1500)
    .attr("cx", 300)
    .style("fill","red");

第三个圆,要求既移动 x 坐标,又改变颜色,还改变半径。

var circle3 = svg.append("circle")
        .attr("cx", 100)
        .attr("cy", 100)
        .attr("r", 45)
        .style("fill","green");

//在2秒(2000毫秒)内将圆心坐标由100变为300
//将颜色从绿色变为红色
//将半径从45变成25
//过渡方式采用bounce(在终点处弹跳几次)
circle3.transition()
    .duration(2000)
    .ease("bounce")
    .attr("cx", 300)
    .style("fill","red")
    .attr("r", 25);

给柱形图加上动态效果

在上一章完整柱形图的基础上稍作修改,即可做成一个带动态效果的、有意思的柱形图。在添加文字元素和矩形元素的时候,启动过渡效果,让各柱形和文字缓慢升至目标高度,并且在目标处跳动几次。

对于文字元素,代码如下:

.attr("y",function(d){
    var min = yScale.domain()[0];
    return yScale(min);
})
.transition()
.delay(function(d,i){
    return i * 200;
})
.duration(2000)
.ease("bounce")
.attr("y",function(d){
    return yScale(d);
});

文字元素的过渡前后,发生变化的是 y 坐标。其起始状态是在 y 轴等于 0 的位置(但要注意,不能在起始状态直接返回 0,要应用比例尺计算画布中的位置)。终止状态是目标值。

可视化工具D3教程_第19张图片

第10章 理解 update()、enter()、exit()

Update、Enter、Exit 是 D3 中三个非常重要的概念,它处理的是当选择集和数据的数量关系不确定的情况。
可视化工具D3教程_第20张图片
前几章里,反复出现了形如以下的代码。

svg.selectAll("rect")   //选择svg内所有的矩形
    .data(dataset)      //绑定数组
    .enter()            //指定选择集的enter部分
    .append("rect")     //添加足够数量的矩形元素
  • update()
    当对应的元素正好满足时 ( 绑定数据数量 = 对应元素 ),实际上并不存在这样一个函数,只是为了要与之后的 enter 和 exit 一起说明才想象有这样一个函数。但对应元素正好满足时,直接操作即可,后面直接跟 text ,style 等操作即可。
  • enter()
    当对应的元素不足时 ( 绑定数据数量 > 对应元素 ),当对应的元素不足时,通常要添加元素,使之与绑定数据的数量相等。后面通常先跟 append 操作。
  • exit()
    当对应的元素过多时 ( 绑定数据数量 < 对应元素 ),当对应的元素过多时,通常要删除元素,使之与绑定数据的数量相等。后面通常要跟 remove 操作。
    可视化工具D3教程_第21张图片

第11章 交互式操作

与图表的交互,指在图形元素上设置一个或多个监听器,当事件发生时,做出相应的反应。
可视化工具D3教程_第22张图片

什么是交互

交互,指的是用户输入了某种指令,程序接受到指令之后必须做出某种响应。对可视化图表来说,交互能使图表更加生动,能表现更多内容。例如,拖动图表中某些图形、鼠标滑到图形上出现提示框、用触屏放大或缩小图形等等。用户用于交互的工具一般有三种:鼠标、键盘、触屏。

如何添加交互
对某一元素添加交互操作十分简单,代码如下:

var circle = svg.append("circle");

circle.on("click", function(){
    //在这里添加交互内容
});

这段代码在 SVG 中添加了一个圆,然后添加了一个监听器,是通过 on() 添加的。在 D3 中,每一个选择集都有 on() 函数,用于添加事件监听器。

on() 的第一个参数是监听的事件,第二个参数是监听到事件后响应的内容,第二个参数是一个函数。

鼠标事件:

  • click:鼠标单击某元素时,相当于 mousedown 和 mouseup 组合在一起。
  • mouseover:光标放在某元素上。
  • mouseout:光标从某元素上移出来时。
  • mousemove:鼠标被移动的时候。
  • mousedown:鼠标按钮被按下。**
  • mouseup:鼠标按钮被松开。**
  • dblclick:鼠标双击。**

键盘事件:

  • keydown:当用户按下任意键时触发,按住不放会重复触发此事件。该事件不会区分字母的大小写,例如“A”和“a”被视为一致。
  • keypress:当用户按下字符键(大小写字母、数字、加号、等号、回车等)时触发,按住不放会重复触发此事件。该事件区分字母的大小写。
  • keyup:当用户释放键时触发,不区分字母的大小写。 触屏常用的事件有三个:

当某个事件被监听到时,D3 会把当前的事件存到 d3.event 对象,里面保存了当前事件的各种参数,如果需要监听到事件后立刻输出该事件,可以添加一行代码:
circle.on(“click”, function(){
console.log(d3.event);
});

带有交互的柱形图

将之前的柱形图部分代码修改成如下代码。

var rects = svg.selectAll(".MyRect")
        .data(dataset)
        .enter()
        .append("rect")
        .attr("class","MyRect")   //把类里的 fill 属性清空
        .attr("transform","translate(" + padding.left + "," + padding.top + ")")
        .attr("x", function(d,i){
            return xScale(i) + rectPadding/2;
        } )
        .attr("y",function(d){
            return yScale(d);
        })
        .attr("width", xScale.rangeBand() - rectPadding )
        .attr("height", function(d){
            return height - padding.top - padding.bottom - yScale(d);
        })
        .attr("fill","steelblue")       //填充颜色不要写在CSS里
        .on("mouseover",function(d,i){
            d3.select(this)
                .attr("fill","yellow");
        })
        .on("mouseout",function(d,i){
            d3.select(this)
                .transition()
                .duration(500)
                .attr("fill","steelblue");
        });

这段代码添加了鼠标移入(mouseover),鼠标移出(mouseout)两个事件的监听器。监听器函数中都使用了 d3.select(this),表示选择当前的元素,this 是当前的元素,要改变响应事件的元素时这么写就好。

mouseover 监听器函数的内容为:将当前元素变为黄色

mouseout 监听器函数的内容为:缓慢地将元素变为原来的颜色(蓝色)

第12章 布局

布局,可以理解成 “制作常见图形的函数”,有了它制作各种相对复杂的图表就方便多了。

可视化工具D3教程_第23张图片
布局是什么

布局,英文是 Layout。从字面看,可以想到有“决定什么元素绘制在哪里”的意思。布局是 D3 中一个十分重要的概念。D3 与其它很多可视化工具不同,相对来说较底层,对初学者来说不太方便,但是一旦掌握了,就比其他工具更加得心应手。下图展示了 D3 与其它可视化工具的区别:
可视化工具D3教程_第24张图片
如何理解布局

从上面的图可以看到,布局的作用是:将不适合用于绘图的数据转换成了适合用于绘图的数据。因此,为了便于初学者理解,将布局的作用解释成:数据转换。

布局有哪些

D3 总共提供了 12 个布局:饼状图(Pie)、力导向图(Force)、弦图(Chord)、树状图(Tree)、集群图(Cluster)、捆图(Bundle)、打包图(Pack)、直方图(Histogram)、分区图(Partition)、堆栈图(Stack)、矩阵树图(Treemap)、层级图(Hierarchy)。

12 个布局中,层级图(Hierarchy)不能直接使用。集群图、打包图、分区图、树状图、矩阵树图是由层级图扩展来的。如此一来,能够使用的布局是 11 个(有 5 个是由层级图扩展而来)。这些布局的作用都是将某种数据转换成另一种数据,而转换后的数据是利于可视化的。

  • Bundle —捆图
    可视化工具D3教程_第25张图片
  • Chord — 弦图
    可视化工具D3教程_第26张图片
  • Cluster — 集群图
    可视化工具D3教程_第27张图片
  • Force —力学图、力导向图
    可视化工具D3教程_第28张图片
  • Histogram —- 直方图(数据分布图)
    可视化工具D3教程_第29张图片
  • Pack —- 打包图
    可视化工具D3教程_第30张图片
  • Partition —- 分区图
    可视化工具D3教程_第31张图片
  • Pie —- 饼状图
    可视化工具D3教程_第32张图片
  • Stack —- 堆栈图
    可视化工具D3教程_第33张图片
  • Tree —- 树状图
    可视化工具D3教程_第34张图片
  • Treemap —- 矩阵树图
    可视化工具D3教程_第35张图片

第13章 饼状图

本章制作一个饼状图。在布局的应用中,最简单的就是饼状图,通过本文你将对布局有一个初步了解。

可视化工具D3教程_第36张图片
数据

假设有如下数据,需要可视化:

var dataset = [ 30 , 10 , 43 , 55 , 13 ];

这样的值是不能直接绘图的。例如绘制饼状图的一个部分,需要知道一段弧的起始角度和终止角度,这些值都不存在于数组 dataset 中。因此,需要用到布局,布局的作用就是计算出适合于作图的数据。

布局(数据转换)

定义一个布局,

var pie = d3.layout.pie();

返回值赋给变量 pie,此时 pie 可以当做函数使用。

var piedata = pie(dataset);

将数组 dataset 作为 pie() 的参数,返回值给 piedata。如此一来,piedata 就是转换后的数据。

绘制图形

为了根据转换后的数据 piedata 来作图,还需要一样工具:生成器。SVG 有一个元素,叫做路径 path,是 SVG 中功能最强的元素,它可以表示其它任意的图形。顾名思义,路径元素就是通过定义一个段“路径”,来绘制出各种图形。但是,路径是很难计算的,通过布局转换后的数据 piedata 仍然很难手动计算得到路径值。为我们完成这项任务的,就是生成器。

这里要用到的叫做弧生成器,能够生成弧的路径,因为饼图的每一部分都是一段弧。

var outerRadius = 150; //外半径
var innerRadius = 100; //内半径,为0则中间没有空白

var arc = d3.svg.arc()  //弧生成器
    .innerRadius(innerRadius)   //设置内半径
    .outerRadius(outerRadius);  //设置外半径

弧生成器返回的结果赋值给 arc。此时,arc 可以当做一个函数使用,把 piedata 作为参数传入,即可得到路径值。接下来,可以在 SVG 中添加图形元素了。先在 svg 里添加足够数量(5个)个分组元素(g),每一个分组用于存放一段弧的相关元素。

var arcs = svg.selectAll("g")
    .data(piedata)
    .enter()
    .append("g")
    .attr("transform","translate("+ (width/2) +","+ (width/2) +")");

接下来对每个 g 元素,添加 path 。

arcs.append("path")
    .attr("fill",function(d,i){
        return color(i);
    })
    .attr("d",function(d){
        return arc(d);   //调用弧生成器,得到路径值
    });

因为 arcs 是同时选择了 5 个 g 元素的选择集,所以调用 append(“path”) 后,每个 g 中都有 path 。路径值的属性名称是 d,调用弧生成器后返回的值赋值给它。要注意,arc(d) 的参数 d 是被绑定的数据。

另外,color 是一个颜色比例尺,它能根据传入的索引号获取相应的颜色值,定义如下。

var color = d3.scale.category10();   //有十种颜色的颜色比例尺

然后在每一个弧线中心添加文本。

arcs.append("text")
    .attr("transform",function(d){
        return "translate(" + arc.centroid(d) + ")";
    })
    .attr("text-anchor","middle")
    .text(function(d){
        return d.data;
    });

arc.centroid(d) 能算出弧线的中心。要注意,text() 里返回的是 d.data ,而不是 d 。因为被绑定的数据是对象,里面有 d.startAngle、d.endAngle、d.data 等,其中 d.data 才是转换前的整数的值。

第14章 力导向图

力导向图(Force-Directed Graph),是绘图的一种算法。在二维或三维空间里配置节点,节点之间用线连接,称为连线。各连线的长度几乎相等,且尽可能不相交。节点和连线都被施加了力的作用,力是根据节点和连线的相对位置计算的。根据力的作用,来计算节点和连线的运动轨迹,并不断降低它们的能量,最终达到一种能量很低的安定状态。
可视化工具D3教程_第37张图片
数据

初始数据如下:

var nodes = [ { name: "桂林" }, { name: "广州" },
              { name: "厦门" }, { name: "杭州" },
              { name: "上海" }, { name: "青岛" },
              { name: "天津" } ];

var edges = [ { source : 0 , target: 1 } , { source : 0 , target: 2 } ,
              { source : 0 , target: 3 } , { source : 1 , target: 4 } ,
              { source : 1 , target: 5 } , { source : 1 , target: 6 } ];

布局(数据转换)

定义一个力导向图的布局如下。

var force = d3.layout.force()
      .nodes(nodes) //指定节点数组
      .links(edges) //指定连线数组
      .size([width,height]) //指定作用域范围
      .linkDistance(150) //指定连线长度
      .charge([-400]); //相互之间的作用力

然后,使力学作用生效:

force.start();    //开始作用

绘制

有了转换后的数据,就可以作图了。分别绘制三种图形元素:

line,线段,表示连线。
circle,圆,表示节点。
text,文字,描述节点。

代码如下:

//添加连线 
 var svg_edges = svg.selectAll("line")
     .data(edges)
     .enter()
     .append("line")
     .style("stroke","#ccc")
     .style("stroke-width",1);

 var color = d3.scale.category20();

 //添加节点 
 var svg_nodes = svg.selectAll("circle")
     .data(nodes)
     .enter()
     .append("circle")
     .attr("r",20)
     .style("fill",function(d,i){
         return color(i);
     })
     .call(force.drag);  //使得节点能够拖动

 //添加描述节点的文字
 var svg_texts = svg.selectAll("text")
     .data(nodes)
     .enter()
     .append("text")
     .style("fill", "black")
     .attr("dx", 20)
     .attr("dy", 8)
     .text(function(d){
        return d.name;
     });

调用 call( force.drag ) 后节点可被拖动。force.drag() 是一个函数,将其作为 call() 的参数,相当于将当前选择的元素传到 force.drag() 函数中。

最后,还有一段最重要的代码。由于力导向图是不断运动的,每一时刻都在发生更新,因此,必须不断更新节点和连线的位置。力导向图布局 force 有一个事件 tick,每进行到一个时刻,都要调用它,更新的内容就写在它的监听器里就好。

force.on("tick", function(){ //对于每一个时间间隔
    //更新连线坐标
    svg_edges.attr("x1",function(d){ return d.source.x; })
        .attr("y1",function(d){ return d.source.y; })
        .attr("x2",function(d){ return d.target.x; })
        .attr("y2",function(d){ return d.target.y; });

    //更新节点坐标
    svg_nodes.attr("cx",function(d){ return d.x; })
        .attr("cy",function(d){ return d.y; });

    //更新文字坐标
    svg_texts.attr("x", function(d){ return d.x; })
       .attr("y", function(d){ return d.y; });
 });

第15章 树状图

树状图,可表示节点之间的包含与被包含关系。

现有数据如下:

{
"name":"中国",
"children":
[
    { 
      "name":"浙江" , 
      "children":
      [
            {"name":"杭州" },
            {"name":"宁波" },
            {"name":"温州" },
            {"name":"绍兴" }
      ] 
    },

    { 
        "name":"广西" , 
        "children":
        [
            {
            "name":"桂林",
            "children":
            [
                {"name":"秀峰区"},
                {"name":"叠彩区"},
                {"name":"象山区"},
                {"name":"七星区"}
            ]
            },
            {"name":"南宁"},
            {"name":"柳州"},
            {"name":"防城港"}
        ] 
    },

    { 
        "name":"黑龙江",
        "children":
        [
            {"name":"哈尔滨"},
            {"name":"齐齐哈尔"},
            {"name":"牡丹江"},
            {"name":"大庆"}
        ] 
    },

    { 
        "name":"新疆" , 
        "children":
        [
            {"name":"乌鲁木齐"},
            {"name":"克拉玛依"},
            {"name":"吐鲁番"},
            {"name":"哈密"}
        ]
    }
]
}

这段数据表示:“中国 – 省份名 – 城市名”的包含于被包含关系。

搭建HTTP服务器(解决Chrome无法读取本地文件)
1.安装Node
2.npm install http-server -g
3.目录下执行 http-server -c-1
4.localhost:8080/xxxx.html

布局(数据转换)

定义一个集群图布局:

var tree = d3.layout.tree() 
  	.size([width, height-200]) //设定尺寸
 	.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2); });//设定节点之间的间隔

接下来,转换数据:

d3.json("city_tree.json", function(error, root) {

  var nodes = tree.nodes(root);
  var links = tree.links(nodes);

  console.log(nodes);
  console.log(links);
}

绘制

D3 已经基本上为我们准备好了绘制的函数:d3.svg.diagonal() 。这是一个对角线生成器,只需要输入两个顶点坐标,即可生成一条贝塞尔曲线。

创建一个对角线生成器:

var diagonal = d3.svg.diagonal()
    .projection(function(d) { return [d.y, d.x]; });

projection() 是一个点变换器,默认是 [ d.x , d.y ],即保持原坐标不变,如果写成 [ d.y , d.x ] ,即是说对任意输入的顶点,都交换 x 和 y 坐标。

绘制连线时,使用方法如下:

var link = svg.selectAll(".link")
      .data(links)
      .enter()
      .append("path")
      .attr("class", "link")
      .attr("d", diagonal);   //使用对角线生成器

样式

.node circle {
fill:#FFF;
  stroke: steelblue;
  stroke-width: 1.5px;
}

.node {
  font: 12px sans-serif;
}

.link {
  fill: none;
  stroke: #ccc;
  stroke-width: 1.5px;
}

可视化工具D3教程_第38张图片

第16章 地图可视化

在数据可视化中,地图是很重要的一部分。很多情况会与地图有关联,如中国各省的人口多少,GDP多少等,都可以和地图联系在一起。

D3地图绘制
制作地图需要 JSON 文件,将 JSON 的格式应用于地理上的文件,叫做 GeoJSON 文件。

  • 投影函数
    var projection = d3.geo.mercator()//投影函数
        .center([107, 31])//设定地图的中心位置--经度和纬度
        .scale(850)//设定放大的比例
        .translate([width/2, height/2]);//设定平移

由于 GeoJSON 文件中的地图数据,都是经度和纬度的信息。它们都是三维的,而要在网页上显示的是二维的,所以要设定一个投影函数来转换经度纬度。如上所示,使用 d3.geo.mercator() 的投影方式。

  • 地理路径生成器

为了根据地图的地理数据生成 SVG 中 path 元素的路径值,需要用到 d3.geo.path(),称为地理路径生成器。

var path = d3.geo.path()
    .projection(projection);

projection() 是设定生成器的投影函数,把上面定义的投影传入即可。

  • 加载文件并绘制地图
    d3.json("world.json", function(error, root) {
    
        if (error) 
            return console.error(error);
        console.log(root.features);
    
        svg.selectAll("path")
            .data( root.features )
            .enter()
            .append("path")
            .attr("stroke","#000")
            .attr("stroke-width",1)
            .attr("fill", function(d,i){
                return color(i);
            })
            .attr("d", path )   //使用地理路径生成器
            .on("mouseover",function(d,i){
                        d3.select(this)
                           .attr("fill","yellow");
                    })
                    .on("mouseout",function(d,i){
                        d3.select(this)
                           .attr("fill",color(i));
                    });
    });

可视化工具D3教程_第39张图片

其他开源地图

  • 高德地图
  • 百度地图
  • 谷歌地图
  • Mapbox

学习D3的站点

  • D3官方网站
  • Mike Bostock 的博客和作品展示板
  • 1897 D3.js Examples

建议

  • 多看
  • 多练
  • 多想

你可能感兴趣的:(可视化工具D3教程)