D3.js

D3.js

为什么学习D3

D3.js和threejs的应用场景完全不一样。threejs主要应用与基于webGL的3D场景,而D3.js确主要应用与2D场景。

它们一起形成了一种互补关系。

简而言之D3JS就是一个数据可视化的库。

那什么是数据可视化呢?

给出一组数据 [10,80,40,100,30,20,50]

image-20190801162422591.png

类似的库 eharts

ECharts,一个使用 JavaScript 实现的开源可视化库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器,ECharts 提供了常规的折线图柱状图散点图饼图 等等。

和eharts的区别

eharts是封装好的各种的图表可以直接拿来使用,类似于图表模具,直接拿来使用即可。

D3.js就像画笔一样,一切都由你自由发挥。

基本介绍

D3.jsData-Driven Documents)是一个使用动态图形进行数据可视化JavaScript程序库。与W3C标准兼容,并且利用广泛实现的SVG、JavaScript和CSS标准,改良自早期的Protovis程序库。与其他的程序库相比,D3对视图结果有很大的可控性。D3是2011年面世的,同年的8月发布了2.0.0版。到2018年4月,D3已被更新到了5.5.0版[1]

发展历史

在D3.js开发之前已经有出现过许多尝试做数据可视化的包,例如Prefuse,Flare和Protovis程序库,他们都可以视为D3.js的前身。然而Prefuse和Flare皆有缺点,皆不能只透过浏览器完成渲染,皆须要透过额外插件来完成。

例如2005年发布的Prefuse是一个数据可视化程序库,但是它需要透过网页的Java插件才能于浏览器中呈现;而Flare是2007年发布的另一个数据可视化工具包,由于其是使用ActionScript编程语言开发,因此也需要额外插件,即Flash插件才能完成渲染。

2009年,史丹佛大学的史丹佛可视化团队(Stanford Visualization Group)的杰佛瑞·赫尔、迈克·保斯托和瓦迪姆·欧格菲兹齐利用开发Prefuse和Flare的经验开始用Javscript开发了可从给定数据产生SVG图形的Protovis程序库。而Protovis程序库在业界和学界皆有一定的知名度[3]

2011年,史丹佛可视化团队停止开发Protovis,并开始开发新的数据可视化程序库,借由之前开发Protovis的经验,开发出了D3.js程序库,在注重于Web标准的同时提供了更丰富的平台也有了更好的性能[4]

技术原理

D3.js透过预先创建好迁入于网页中的JavaScript函数来选择网页元素、创建SVG元素、调整CSS来呈现数据,并且也可以设置动画、动态改变对象状态或加入工具提示来完成用户交互功能。使用简单的D3.js函数就能够将大型的数据数据结构与SVG对象进行绑定,并且能生成格式化文本和各种图表。

基本使用

hello world

先尝试用 D3 写第一个 HelloWorld 程序。学编程入门的第一个程序都是在屏幕上输出 HelloWorld,本课稍微有些不同,不是单纯的输出。

在 HTML 中输出 HelloWorld 是怎样的呢,先看下面的代码。

<html> 
  <head> 
        <meta charset="utf-8"> 
        <title>HelloWorld</title> 
  </head> 
    <body> 
        <p>Hello World 1</p>
        <p>Hello World 2</p>
    </body> 
</html>
用 JavaScript 来更改 HelloWorld

对于上面输出的内容,如果想用 JavaScript 来更改这两行文字,怎么办呢?我们会添加代码变为:

<html> 
  <head> 
        <meta charset="utf-8"> 
        <title>HelloWorld</title> 
  </head> 
    <body> 
    <p>Hello World 1</p>
    <p>Hello World 2</p>
        <script>
        var paragraphs = document.getElementsByTagName("p");
        for (var i = 0; i < paragraphs.length; i++) {
          var paragraph = paragraphs.item(i);
          paragraph.innerHTML = "I like dog.";
        }          
        </script> 
    </body> 
</html>
用 D3 来更改 HelloWorld

如果使用 D3.js 来修改这两行呢?只需添加一行代码即可。注意不要忘了引用 D3.js 源文件。

引入

<script src="https://d3js.org/d3.v5.js"></script>
<html> 
  <head> 
        <meta charset="utf-8"> 
        <title>HelloWorld</title> 
  </head> 
    <body> 
        <p>Hello World 1</p>
        <p>Hello World 2</p>
        <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> 
        <script>  
        d3.select("body").selectAll("p").text("www.ourd3js.com");      
        </script> 
    </body> 
</html>

接下来改变字体的颜色和大小,稍微修改上述代码:

//选择<body>中所有的<p>,其文本内容为 www.ourd3js.com,选择集保存在变量 p 中
var p = d3.select("body")
          .selectAll("p")
          .text("www.ourd3js.com");

//修改段落的颜色和字体大小
p.style("color","red")
 .style("font-size","72px");

选择元素

在 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中所有的svg元素

绑定数据

选择集和绑定数据通常是一起使用的。

D3 有一个很独特的功能:能将数据绑定到 DOM 上,也就是绑定到文档上。这么说可能不好理解,例如网页中有段落元素 p 和一个整数 5,于是可以将整数 5 与 p 绑定到一起。绑定之后,当需要依靠这个数据才操作元素的时候,会很方便。

D3 中是通过以下两个函数来绑定数据的:

  • datum():绑定一个数据到选择集上
  • data():绑定一个数组到选择集上,数组的各项值分别与选择集的各元素绑定

相对而言,data() 比较常用。

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

<p>Apple</p>
<p>Pear</p>
<p>Banana</p>

datum()

假设有一字符串 China,要将此字符串分别与三个段落元素绑定,代码如下:

var str = "China";

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

p.datum(str);

p.text(function(d, i){
    return "第 "+ i + " 个元素绑定的数据是 " + d;
});

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

第 0 个元素绑定的数据是 China

第 1 个元素绑定的数据是 China

第 2 个元素绑定的数据是 China

在上面的代码中,用到了一个无名函数 function(d, i)。当选择集需要使用被绑定的数据时,常需要这么使用。其包含两个参数,其中:

  • d 代表数据,也就是与某元素绑定的数据。
  • i 代表索引,代表数据的索引号,从 0 开始。

例如,上述例子中:第 0 个元素 apple 绑定的数据是 China。

data()

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

var dataset = ["I like dog","I like cat","I like snake"];

绑定之后,其对应关系的要求为:

  • Apple 与 I like dog 绑定
  • Pear 与 I like cat 绑定
  • Banana 与 I like snake 绑定

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

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

p.data(dataset)
  .text(function(d, i){
      return d;
});

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

  • 当 i == 0 时, d 为 I like dog。
  • 当 i == 1 时, d 为 I like cat。
  • 当 i == 2 时, d 为 I like snake。

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

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

I like dog

I like cat

I like snake

选择、插入、删除元素

已经讲解了 select 和 selectAll,以及选择集的概念。本节具体讲解这两个函数的用法。

假设在 body 中有三个段落元素:

<p>Apple</p>
<p>Pear</p>
<p>Banana</p>

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

选择第一个 p 元素
t("p");
p1.style("color","red");
选择三个 p 元素
var p = body.selectAll("p");
p.style("color","red");
选择第二个 p 元素

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

Pear

然后,使用 select 选择元素,注意参数中 id 名称前要加 # 号。

var p2 = body.select("#myid");
p2.style("color","red");
选择后两个 p 元素

给后两个元素添加 class,

<p class="myclass">Pear</p>
<p class="myclass">Banana</p>

由于需要选择多个元素,要用 selectAll。注意参数,class 名称前要加一个点。

var p = body.selectAll(".myclass");
p.style("color","red");

插入元素

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

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

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

append()

body.append("p")
    .text("append p element");

在 body 的末尾添加一个 p 元素,结果为:

Apple
Pear
Banana
append p element

insert()

在 body 中 id 为 myid 的元素前添加一个段落元素。

body.insert("p","#myid")
  .text("insert p element");

已经指定了 Pear 段落的 id 为 myid,因此结果如下。

Apple
insert p element
Pear
Banana

删除元素

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

var p = body.select("#myid");
p.remove();

SVG 基本使用

SVG 意为可缩放矢量图形(Scalable Vector Graphics)。

SVG 使用 XML 格式定义图像。

什么是svg

  • SVG 指可伸缩矢量图形 (Scalable Vector Graphics)
  • SVG 用来定义用于网络的基于矢量的图形
  • SVG 使用 XML 格式定义图形
  • SVG 图像在放大或改变尺寸的情况下其图形质量不会有所损失
  • SVG 是万维网联盟的标准
  • SVG 与诸如 DOM 和 XSL 之类的 W3C 标准是一个整体

hello-world

<html>
<body>
 
<h1>My first SVG</h1>
 
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <circle cx="100" cy="50" r="40" stroke="black"
  stroke-width="2" fill="red" />
</svg>
 
</body>
</html>

简单的 SVG 实例

一个简单的SVG图形例子:

这里是SVG文件(SVG文件的保存与SVG扩展):

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <circle cx="100" cy="50" r="40" stroke="black"
  stroke-width="2" fill="red" />
</svg>

第一行包含了 XML 声明。请注意 standalone 属性!该属性规定此 SVG 文件是否是"独立的",或含有对外部文件的引用。

standalone="no" 意味着 SVG 文档会引用一个外部文件 - 在这里,是 DTD 文件。

第二和第三行引用了这个外部的 SVG DTD。该 DTD 位于 "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"。该 DTD 位于 W3C,含有所有允许的 SVG 元素。

SVG 代码以 <svg>元素开始,包括开启标签<svg>和关闭标签 </svg>。这是根元素。width 和 height 属性可设置此 SVG 文档的宽度和高度。version 属性可定义所使用的 SVG 版本,xmlns 属性可定义 SVG 命名空间。

SVG 的<circle> 用来创建一个圆。cx 和 cy 属性定义圆中心的 x 和 y 坐标。如果忽略这两个属性,那么圆点会被设置为 (0, 0)。r 属性定义圆的半径。

stroke 和 stroke-width 属性控制如何显示形状的轮廓。我们把圆的轮廓设置为 2px 宽,黑边框。

fill 属性设置形状内的颜色。我们把填充颜色设置为红色。

关闭标签的作用是关闭 SVG 元素和文档本身。

SVG 在 HTML 页面

SVG 文件可通过以下标签嵌入 HTML 文档:<embed>、<object> 或者 <iframe>。

SVG的代码可以直接嵌入到HTML页面中,或您可以直接链接到SVG文件。

使用 <embed> 标签
<embed src="circle1.svg" type="image/svg+xml" />
直接在HTML嵌入SVG代码

在Firefox、Internet Explorer9、谷歌Chrome和Safari中,你可以直接在HTML嵌入SVG代码。

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
   <circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" />
</svg>

SVG图形

SVG有一些预定义的形状元素,可被开发者使用和操作:

  • 矩形 <rect>
  • 圆形 <circle>
  • 椭圆 <ellipse>
  • 线 <line>
  • 折线 <polyline>
  • 多边形 <polygon>
  • 路径 <path>
矩形

EX1:

<rect> 标签可用来创建矩形,以及矩形的变种:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <rect width="300" height="100"
  style="fill:rgb(0,0,255);stroke-width:1;stroke:rgb(0,0,0)"/>
</svg>
  • rect 元素的 width 和 height 属性可定义矩形的高度和宽度
  • style 属性用来定义 CSS 属性
  • CSS 的 fill 属性定义矩形的填充颜色(rgb 值、颜色名或者十六进制值)
  • CSS 的 stroke-width 属性定义矩形边框的宽度
  • CSS 的 stroke 属性定义矩形边框的颜色

EX2:

让我们看看另一个例子,它包含一些新的属性:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <rect x="50" y="20" width="150" height="150"
  style="fill:blue;stroke:pink;stroke-width:5;fill-opacity:0.1;
  stroke-opacity:0.9"/>
</svg>
  • x 属性定义矩形的左侧位置(例如,x="0" 定义矩形到浏览器窗口左侧的距离是 0px)
  • y 属性定义矩形的顶端位置(例如,y="0" 定义矩形到浏览器窗口顶端的距离是 0px)
  • CSS 的 fill-opacity 属性定义填充颜色透明度(合法的范围是:0 - 1)
  • CSS 的 stroke-opacity 属性定义轮廓颜色的透明度(合法的范围是:0 - 1)
圆形

<circle> 标签可用来创建一个圆:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <circle cx="100" cy="50" r="40" stroke="black"
  stroke-width="2" fill="red"/>
</svg>
  • cx和cy属性定义圆点的x和y坐标。如果省略cx和cy,圆的中心会被设置为(0, 0)
  • r属性定义圆的半径
椭圆

<ellipse> 元素是用来创建一个椭圆:

椭圆与圆很相似。不同之处在于椭圆有不同的x和y半径,而圆的x和y半径是相同的:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <ellipse cx="300" cy="80" rx="100" ry="50"
  style="fill:yellow;stroke:purple;stroke-width:2"/>
</svg>
直线

<line> 元素是用来创建一个直线:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <line x1="0" y1="0" x2="200" y2="200"
  style="stroke:rgb(255,0,0);stroke-width:2"/>
</svg>
  • x1 属性在 x 轴定义线条的开始
  • y1 属性在 y 轴定义线条的开始
  • x2 属性在 x 轴定义线条的结束
  • y2 属性在 y 轴定义线条的结束
多边形

<polygon> 标签用来创建含有不少于三个边的图形。

polygon来自希腊。 "Poly" 意味 "many" , "gon" 意味 "angle".

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <polygon points="200,10 250,190 160,210"
  style="fill:lime;stroke:purple;stroke-width:1"/>
</svg>

下面的示例创建一个四边的多边形:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <polygon points="220,10 300,210 170,250 123,234"
  style="fill:lime;stroke:purple;stroke-width:1"/>
</svg>
曲线

<polyline> 元素是用于创建任何只有直线的形状:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <polyline points="20,20 40,25 60,40 80,120 120,140 200,180"
  style="fill:none;stroke:black;stroke-width:3" />
</svg>

只有直线的另一个例子:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <polyline points="0,40 40,40 40,80 80,80 80,120 120,120 120,160" style="fill:white;stroke:red;stroke-width:4" />
</svg>
路径

<path> 元素用于定义一个路径。

下面的命令可用于路径数据:

  • M = moveto
  • L = lineto
  • H = horizontal lineto
  • V = vertical lineto
  • C = curveto
  • S = smooth curveto
  • Q = quadratic Bézier curve
  • T = smooth quadratic Bézier curveto
  • A = elliptical Arc
  • Z = closepath

例子定义了一条路径,它开始于位置150 0,到达位置75 200,然后从那里开始到225 200,最后在150 0关闭路径。

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
    <path d="M150 0 L75 200 L225 200 Z" />
</svg>
文本

<text> 元素用于定义文本。

EX:1

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <text x="0" y="15" fill="red">I love SVG</text>
</svg>
Svg与Canvas的区别?
  1. 绘制的图片格式不同
Canvas 的工具getContext 绘制出来的图形或传入的图片都依赖分辨率,能够以 .png 和 .jpg格式保存存储图像,可以说是位图

SVG 可以在H5中直接绘制,但绘制的是矢量图

由于位图依赖分辨率,矢量图不依赖分辨率,所以Canvas和SVG的图片格式的不同实际上是他们绘制出来的图片的格式不同造成的。
  1. Canvas不支持事件处理器,SVG支持事件处理器
Canvas 绘制的图像 都在Canvas这个画布里面,是Canvas的一部分,不能用js获取已经绘制好的图形元素。
  1. 适用范围不同
Canvas是逐像素进行渲染的,一旦图形绘制完成,就不会继续被浏览器关注。而SVG是通过DOM操作来显示的。

所以Canvas的文本渲染能力弱,而SVG最适合带有大型渲染区域的应用程序,比如地图。

而Canvas 最适合有许多对象要被频繁重绘的图形密集型游戏。

而SVG由于DOM操作 在复杂度高的游戏应用中 会减慢渲染速度。所以不适合在游戏应用。

实践

做一个简单的柱状图

image-20190802172953054.png
画布

前几章的处理对象都是 HTML 的文字,没有涉及图形的制作。

要绘图,首要需要的是一块绘图的“画布”。

HTML 5 提供两种强有力的“画布”:SVGCanvas

添加画布

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。例如:

<svg>
  <rect></rect>
  <rect></rect>
</svg>

上面的 rect 里没有矩形的属性。矩形的属性,常用的有四个:

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

要注意,在 SVG 中,x 轴的正方向是水平向右,y 轴的正方向是垂直向下的。

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

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

为简单起见,我们直接用数值的大小来表示矩形的像素宽度(后面会说到这不是一种好方法)。然后,添加以下代码。

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");

这段代码添加了与 dataset 数组的长度相同数量的矩形,所使用的语句是:

svg.selectAll("rect")   //选择svg内所有的矩形
    .data(dataset)  //绑定数组
    .enter()        //指定选择集的enter部分
    .append("rect") //添加足够数量的矩形元素

这段代码以后会常常出现在 D3 的代码中,请务必牢记。目前不深入讨论它的作用机制是怎样的,只需要读者牢记,当:

有数据,而没有足够图形元素的时候,使用此方法可以添加足够的元素。

添加了元素之后,就需要分别给各元素的属性赋值。在这里用到了 function(d, i),前面已经讲过,d 代表与当前元素绑定的数据,i 代表索引号。给属性赋值的时候,是需要用到被绑定的数据,以及索引号的。

最后一行的:

.attr("fill","steelblue");
比例尺的使用

比例尺是 D3 中很重要的一个概念,上一章里曾经提到过直接用数值的大小来代表像素不是一种好方法,本章正是要解决此问题。

为什么需要比例尺

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

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.scaleLinear()
        .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.scaleOrdinal()
        .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");

如此一来,所有的数值,都按照同一个线性比例尺的关系来计算宽度,因此数值之间的大小关系不变。

坐标轴

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

image-20190802180149326.png

在 SVG 画布的预定义元素里,有六种基本图形:

  • 矩形
  • 圆形
  • 椭圆
  • 线段
  • 折线
  • 多边形

另外,还有一种比较特殊,也是功能最强的元素:

  • 路径

画布中的所有图形,都是由以上七种元素组成。

显然,这里面没有坐标轴 这种元素。如果有的话,我们可以采用类似以下的方式定义:

<axis x1="" x2="" ...></axis>

很可惜,没有这种元素。但是,这种设计是合理的:不可能为每一种图形都配备一个单独的元素,那样 SVG 就会过于庞大。

因此,我们需要用其他元素来组合成坐标轴,最终使其变为类似以下的形式:

<g>
<!-- 第一个刻度 -->
<g>
<line></line>   <!-- 第一个刻度的直线 -->
<text></text>   <!-- 第一个刻度的文字 -->
</g>
<!-- 第二个刻度 -->
<g>
<line></line>   <!-- 第二个刻度的直线 -->
<text></text>   <!-- 第二个刻度的文字 -->
</g> 
...
<!-- 坐标轴的轴线 -->
<path></path>
</g>

分组元素 ,是 SVG 画布中的元素,意思是 group。此元素是将其他元素进行组合的容器,在这里是用于将坐标轴的其他元素分组存放。

如果需要手动添加这些元素就太麻烦了,为此,D3 提供了一个组件:d3.svg.axis()。它为我们完成了以上工作。

定义坐标轴

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

var dataset = [1, 2, 3, 4, 5];  // 数据源  x

// 比例尺  让图表更加的直观, 合理

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

// console.log(max)

// scaleLinear可以定义比例尺  domain range
var linear = d3.scaleLinear().domain([0, max]).range([0, 300]);

// 添加坐标轴
var xAxis = d3.axisBottom(linear);
在svg中添加坐标轴

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

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

让你的坐标轴动起来demo

<!DOCTYPE html>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        #update {
            position: absolute;
            top: 10px;
            left: 10px;
        }
    </style>
</head>

<body>
    <button id="update">更新</button>
    <script src="https://d3js.org/d3.v5.js"></script>
    <script>

        // 1. 添加画布
        var width = 960;
        var height = 500;

        var svg = d3.select('body').append('svg').attr('width', width).attr('height', height)

        // 完成 静态的  坐标轴
        // 线性比例尺
        var scale = d3.scaleLinear().domain([0, 100]).range([100, 860]);

        var axis = d3.axisBottom(scale);

        
        var g = svg.append('g').attr('id', 'g').call(axis);

        // 绑定事件
        d3.select('#update').on('click', function() {
            // 更新数据
            scale.domain([0, Math.random() * 100]);
            // g.call(axis);
            // transition d3提供默认的动画
            d3.select('#g').transition().call(axis)
        })

    </script>
</body>

</html>

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