D3.js

D3.js

為什么學(xué)習(xí)D3

D3.js和threejs的應(yīng)用場(chǎng)景完全不一樣。threejs主要應(yīng)用與基于webGL的3D場(chǎng)景,而D3.js確主要應(yīng)用與2D場(chǎng)景。

它們一起形成了一種互補(bǔ)關(guān)系。

簡(jiǎn)而言之D3JS就是一個(gè)數(shù)據(jù)可視化的庫(kù)。

那什么是數(shù)據(jù)可視化呢?

給出一組數(shù)據(jù) [10,80,40,100,30,20,50]

image-20190801162422591.png

類似的庫(kù) eharts

ECharts,一個(gè)使用 JavaScript 實(shí)現(xiàn)的開源可視化庫(kù),可以流暢的運(yùn)行在 PC 和移動(dòng)設(shè)備上,兼容當(dāng)前絕大部分瀏覽器,ECharts 提供了常規(guī)的折線圖柱狀圖、散點(diǎn)圖、餅圖 等等。

和eharts的區(qū)別

eharts是封裝好的各種的圖表可以直接拿來(lái)使用,類似于圖表模具,直接拿來(lái)使用即可。

D3.js就像畫筆一樣,一切都由你自由發(fā)揮。

基本介紹

D3.jsData-Driven Documents)是一個(gè)使用動(dòng)態(tài)圖形進(jìn)行數(shù)據(jù)可視化JavaScript程序庫(kù)。與W3C標(biāo)準(zhǔn)兼容,并且利用廣泛實(shí)現(xiàn)的SVG、JavaScript和CSS標(biāo)準(zhǔn),改良自早期的Protovis程序庫(kù)。與其他的程序庫(kù)相比,D3對(duì)視圖結(jié)果有很大的可控性。D3是2011年面世的,同年的8月發(fā)布了2.0.0版。到2018年4月,D3已被更新到了5.5.0版[1]。

發(fā)展歷史

在D3.js開發(fā)之前已經(jīng)有出現(xiàn)過許多嘗試做數(shù)據(jù)可視化的包,例如Prefuse,F(xiàn)lare和Protovis程序庫(kù),他們都可以視為D3.js的前身。然而Prefuse和Flare皆有缺點(diǎn),皆不能只透過瀏覽器完成渲染,皆須要透過額外插件來(lái)完成。

例如2005年發(fā)布的Prefuse是一個(gè)數(shù)據(jù)可視化程序庫(kù),但是它需要透過網(wǎng)頁(yè)的Java插件才能于瀏覽器中呈現(xiàn);而Flare是2007年發(fā)布的另一個(gè)數(shù)據(jù)可視化工具包,由于其是使用ActionScript編程語(yǔ)言開發(fā),因此也需要額外插件,即Flash插件才能完成渲染。

2009年,史丹佛大學(xué)的史丹佛可視化團(tuán)隊(duì)(Stanford Visualization Group)的杰佛瑞·赫爾、邁克·保斯托和瓦迪姆·歐格菲茲齊利用開發(fā)Prefuse和Flare的經(jīng)驗(yàn)開始用Javscript開發(fā)了可從給定數(shù)據(jù)產(chǎn)生SVG圖形的Protovis程序庫(kù)。而Protovis程序庫(kù)在業(yè)界和學(xué)界皆有一定的知名度[3]。

2011年,史丹佛可視化團(tuán)隊(duì)停止開發(fā)Protovis,并開始開發(fā)新的數(shù)據(jù)可視化程序庫(kù),借由之前開發(fā)Protovis的經(jīng)驗(yàn),開發(fā)出了D3.js程序庫(kù),在注重于Web標(biāo)準(zhǔn)的同時(shí)提供了更豐富的平臺(tái)也有了更好的性能[4]。

技術(shù)原理

D3.js透過預(yù)先創(chuàng)建好遷入于網(wǎng)頁(yè)中的JavaScript函數(shù)來(lái)選擇網(wǎng)頁(yè)元素、創(chuàng)建SVG元素、調(diào)整CSS來(lái)呈現(xiàn)數(shù)據(jù),并且也可以設(shè)置動(dòng)畫、動(dòng)態(tài)改變對(duì)象狀態(tài)或加入工具提示來(lái)完成用戶交互功能。使用簡(jiǎn)單的D3.js函數(shù)就能夠?qū)⒋笮偷臄?shù)據(jù)數(shù)據(jù)結(jié)構(gòu)與SVG對(duì)象進(jìn)行綁定,并且能生成格式化文本和各種圖表。

基本使用

hello world

先嘗試用 D3 寫第一個(gè) HelloWorld 程序。學(xué)編程入門的第一個(gè)程序都是在屏幕上輸出 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 來(lái)更改 HelloWorld

對(duì)于上面輸出的內(nèi)容,如果想用 JavaScript 來(lái)更改這兩行文字,怎么辦呢?我們會(huì)添加代碼變?yōu)椋?/p>

<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 來(lái)更改 HelloWorld

如果使用 D3.js 來(lái)修改這兩行呢?只需添加一行代碼即可。注意不要忘了引用 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>

接下來(lái)改變字體的顏色和大小,稍微修改上述代碼:

//選擇<body>中所有的<p>,其文本內(nèi)容為 www.ourd3js.com,選擇集保存在變量 p 中
var p = d3.select("body")
          .selectAll("p")
          .text("www.ourd3js.com");

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

選擇元素

在 D3 中,用于選擇元素的函數(shù)有兩個(gè):

  • d3.select():是選擇所有指定元素的第一個(gè)
  • d3.selectAll():是選擇指定元素的全部

這兩個(gè)函數(shù)返回的結(jié)果稱為選擇集。

例如,選擇集的常見用法如下。

var body = d3.select("body"); //選擇文檔中的body元素
var p1 = body.select("p");      //選擇body中的第一個(gè)p元素
var p = body.selectAll("p");    //選擇body中的所有p元素
var svg = body.select("svg");   //選擇body中的svg元素
var rects = svg.selectAll("rect");  //選擇svg中所有的svg元素

綁定數(shù)據(jù)

選擇集和綁定數(shù)據(jù)通常是一起使用的。

D3 有一個(gè)很獨(dú)特的功能:能將數(shù)據(jù)綁定到 DOM 上,也就是綁定到文檔上。這么說(shuō)可能不好理解,例如網(wǎng)頁(yè)中有段落元素 p 和一個(gè)整數(shù) 5,于是可以將整數(shù) 5 與 p 綁定到一起。綁定之后,當(dāng)需要依靠這個(gè)數(shù)據(jù)才操作元素的時(shí)候,會(huì)很方便。

D3 中是通過以下兩個(gè)函數(shù)來(lái)綁定數(shù)據(jù)的:

  • datum():綁定一個(gè)數(shù)據(jù)到選擇集上
  • data():綁定一個(gè)數(shù)組到選擇集上,數(shù)組的各項(xiàng)值分別與選擇集的各元素綁定

相對(duì)而言,data() 比較常用。

假設(shè)現(xiàn)在有三個(gè)段落元素如下。

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

datum()

假設(shè)有一字符串 China,要將此字符串分別與三個(gè)段落元素綁定,代碼如下:

var str = "China";

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

p.datum(str);

p.text(function(d, i){
    return "第 "+ i + " 個(gè)元素綁定的數(shù)據(jù)是 " + d;
});

綁定數(shù)據(jù)后,使用此數(shù)據(jù)來(lái)修改三個(gè)段落元素的內(nèi)容,其結(jié)果如下:

第 0 個(gè)元素綁定的數(shù)據(jù)是 China

第 1 個(gè)元素綁定的數(shù)據(jù)是 China

第 2 個(gè)元素綁定的數(shù)據(jù)是 China

在上面的代碼中,用到了一個(gè)無(wú)名函數(shù) function(d, i)。當(dāng)選擇集需要使用被綁定的數(shù)據(jù)時(shí),常需要這么使用。其包含兩個(gè)參數(shù),其中:

  • d 代表數(shù)據(jù),也就是與某元素綁定的數(shù)據(jù)。
  • i 代表索引,代表數(shù)據(jù)的索引號(hào),從 0 開始。

例如,上述例子中:第 0 個(gè)元素 apple 綁定的數(shù)據(jù)是 China。

data()

有一個(gè)數(shù)組,接下來(lái)要分別將數(shù)組的各元素綁定到三個(gè)段落元素上。

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

綁定之后,其對(duì)應(yīng)關(guān)系的要求為:

  • Apple 與 I like dog 綁定
  • Pear 與 I like cat 綁定
  • Banana 與 I like snake 綁定

調(diào)用 data() 綁定數(shù)據(jù),并替換三個(gè)段落元素的字符串為被綁定的字符串,代碼如下:

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

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

這段代碼也用到了一個(gè)無(wú)名函數(shù) function(d, i),其對(duì)應(yīng)的情況如下:

  • 當(dāng) i == 0 時(shí), d 為 I like dog。
  • 當(dāng) i == 1 時(shí), d 為 I like cat。
  • 當(dāng) i == 2 時(shí), d 為 I like snake。

此時(shí),三個(gè)段落元素與數(shù)組 dataset 的三個(gè)字符串是一一對(duì)應(yīng)的,因此,在函數(shù) function(d, i) 直接 return d 即可。

結(jié)果自然是三個(gè)段落的文字分別變成了數(shù)組的三個(gè)字符串。

I like dog

I like cat

I like snake

選擇、插入、刪除元素

已經(jīng)講解了 select 和 selectAll,以及選擇集的概念。本節(jié)具體講解這兩個(gè)函數(shù)的用法。

假設(shè)在 body 中有三個(gè)段落元素:

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

現(xiàn)在,要分別完成以下四種選擇元素的任務(wù)。

選擇第一個(gè) p 元素
t("p");
p1.style("color","red");
選擇三個(gè) p 元素
var p = body.selectAll("p");
p.style("color","red");
選擇第二個(gè) p 元素

有不少方法,一種比較簡(jiǎn)單的是給第二個(gè)元素添加一個(gè) id 號(hào)。

Pear

然后,使用 select 選擇元素,注意參數(shù)中 id 名稱前要加 # 號(hào)。

var p2 = body.select("#myid");
p2.style("color","red");
選擇后兩個(gè) p 元素

給后兩個(gè)元素添加 class,

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

由于需要選擇多個(gè)元素,要用 selectAll。注意參數(shù),class 名稱前要加一個(gè)點(diǎn)。

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

插入元素

插入元素涉及的函數(shù)有兩個(gè):

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

假設(shè)有三個(gè)段落元素,與上文相同。

append()

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

在 body 的末尾添加一個(gè) p 元素,結(jié)果為:

Apple
Pear
Banana
append p element

insert()

在 body 中 id 為 myid 的元素前添加一個(gè)段落元素。

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

已經(jīng)指定了 Pear 段落的 id 為 myid,因此結(jié)果如下。

Apple
insert p element
Pear
Banana

刪除元素

刪除一個(gè)元素時(shí),對(duì)于選擇的元素,使用 remove 即可,例如:

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

SVG 基本使用

SVG 意為可縮放矢量圖形(Scalable Vector Graphics)。

SVG 使用 XML 格式定義圖像。

什么是svg

  • SVG 指可伸縮矢量圖形 (Scalable Vector Graphics)
  • SVG 用來(lái)定義用于網(wǎng)絡(luò)的基于矢量的圖形
  • SVG 使用 XML 格式定義圖形
  • SVG 圖像在放大或改變尺寸的情況下其圖形質(zhì)量不會(huì)有所損失
  • SVG 是萬(wàn)維網(wǎng)聯(lián)盟的標(biāo)準(zhǔn)
  • SVG 與諸如 DOM 和 XSL 之類的 W3C 標(biāo)準(zhǔn)是一個(gè)整體

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>

簡(jiǎn)單的 SVG 實(shí)例

一個(gè)簡(jiǎn)單的SVG圖形例子:

這里是SVG文件(SVG文件的保存與SVG擴(kuò)展):

<?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 聲明。請(qǐng)注意 standalone 屬性!該屬性規(guī)定此 SVG 文件是否是"獨(dú)立的",或含有對(duì)外部文件的引用。

standalone="no" 意味著 SVG 文檔會(huì)引用一個(gè)外部文件 - 在這里,是 DTD 文件。

第二和第三行引用了這個(gè)外部的 SVG DTD。該 DTD 位于 "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"。該 DTD 位于 W3C,含有所有允許的 SVG 元素。

SVG 代碼以 <svg>元素開始,包括開啟標(biāo)簽<svg>和關(guān)閉標(biāo)簽 </svg>。這是根元素。width 和 height 屬性可設(shè)置此 SVG 文檔的寬度和高度。version 屬性可定義所使用的 SVG 版本,xmlns 屬性可定義 SVG 命名空間。

SVG 的<circle> 用來(lái)創(chuàng)建一個(gè)圓。cx 和 cy 屬性定義圓中心的 x 和 y 坐標(biāo)。如果忽略這兩個(gè)屬性,那么圓點(diǎn)會(huì)被設(shè)置為 (0, 0)。r 屬性定義圓的半徑。

stroke 和 stroke-width 屬性控制如何顯示形狀的輪廓。我們把圓的輪廓設(shè)置為 2px 寬,黑邊框。

fill 屬性設(shè)置形狀內(nèi)的顏色。我們把填充顏色設(shè)置為紅色。

關(guān)閉標(biāo)簽的作用是關(guān)閉 SVG 元素和文檔本身。

SVG 在 HTML 頁(yè)面

SVG 文件可通過以下標(biāo)簽嵌入 HTML 文檔:<embed>、<object> 或者 <iframe>。

SVG的代碼可以直接嵌入到HTML頁(yè)面中,或您可以直接鏈接到SVG文件。

使用 <embed> 標(biāo)簽
<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有一些預(yù)定義的形狀元素,可被開發(fā)者使用和操作:

  • 矩形 <rect>
  • 圓形 <circle>
  • 橢圓 <ellipse>
  • 線 <line>
  • 折線 <polyline>
  • 多邊形 <polygon>
  • 路徑 <path>
矩形

EX1:

<rect> 標(biāo)簽可用來(lái)創(chuàng)建矩形,以及矩形的變種:

<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 屬性用來(lái)定義 CSS 屬性
  • CSS 的 fill 屬性定義矩形的填充顏色(rgb 值、顏色名或者十六進(jìn)制值)
  • CSS 的 stroke-width 屬性定義矩形邊框的寬度
  • CSS 的 stroke 屬性定義矩形邊框的顏色

EX2:

讓我們看看另一個(gè)例子,它包含一些新的屬性:

<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 屬性定義矩形的左側(cè)位置(例如,x="0" 定義矩形到瀏覽器窗口左側(cè)的距離是 0px)
  • y 屬性定義矩形的頂端位置(例如,y="0" 定義矩形到瀏覽器窗口頂端的距離是 0px)
  • CSS 的 fill-opacity 屬性定義填充顏色透明度(合法的范圍是:0 - 1)
  • CSS 的 stroke-opacity 屬性定義輪廓顏色的透明度(合法的范圍是:0 - 1)
圓形

<circle> 標(biāo)簽可用來(lái)創(chuàng)建一個(gè)圓:

<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屬性定義圓點(diǎn)的x和y坐標(biāo)。如果省略cx和cy,圓的中心會(huì)被設(shè)置為(0, 0)
  • r屬性定義圓的半徑
橢圓

<ellipse> 元素是用來(lái)創(chuàng)建一個(gè)橢圓:

橢圓與圓很相似。不同之處在于橢圓有不同的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> 元素是用來(lái)創(chuàng)建一個(gè)直線:

<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 軸定義線條的結(jié)束
  • y2 屬性在 y 軸定義線條的結(jié)束
多邊形

<polygon> 標(biāo)簽用來(lái)創(chuàng)建含有不少于三個(gè)邊的圖形。

polygon來(lái)自希臘。 "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>

下面的示例創(chuàng)建一個(gè)四邊的多邊形:

<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> 元素是用于創(chuàng)建任何只有直線的形狀:

<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>

只有直線的另一個(gè)例子:

<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> 元素用于定義一個(gè)路徑。

下面的命令可用于路徑數(shù)據(jù):

  • 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,到達(dá)位置75 200,然后從那里開始到225 200,最后在150 0關(guān)閉路徑。

<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的區(qū)別?
  1. 繪制的圖片格式不同
Canvas 的工具getContext 繪制出來(lái)的圖形或傳入的圖片都依賴分辨率,能夠以 .png 和 .jpg格式保存存儲(chǔ)圖像,可以說(shuō)是位圖

SVG 可以在H5中直接繪制,但繪制的是矢量圖

由于位圖依賴分辨率,矢量圖不依賴分辨率,所以Canvas和SVG的圖片格式的不同實(shí)際上是他們繪制出來(lái)的圖片的格式不同造成的。
  1. Canvas不支持事件處理器,SVG支持事件處理器
Canvas 繪制的圖像 都在Canvas這個(gè)畫布里面,是Canvas的一部分,不能用js獲取已經(jīng)繪制好的圖形元素。
  1. 適用范圍不同
Canvas是逐像素進(jìn)行渲染的,一旦圖形繪制完成,就不會(huì)繼續(xù)被瀏覽器關(guān)注。而SVG是通過DOM操作來(lái)顯示的。

所以Canvas的文本渲染能力弱,而SVG最適合帶有大型渲染區(qū)域的應(yīng)用程序,比如地圖。

而Canvas 最適合有許多對(duì)象要被頻繁重繪的圖形密集型游戲。

而SVG由于DOM操作 在復(fù)雜度高的游戲應(yīng)用中 會(huì)減慢渲染速度。所以不適合在游戲應(yīng)用。

實(shí)踐

做一個(gè)簡(jiǎn)單的柱狀圖

image-20190802172953054.png
畫布

前幾章的處理對(duì)象都是 HTML 的文字,沒有涉及圖形的制作。

要繪圖,首要需要的是一塊繪圖的“畫布”。

HTML 5 提供兩種強(qiáng)有力的“畫布”:SVGCanvas。

添加畫布

D3 雖然沒有明文規(guī)定一定要在 SVG 中繪圖,但是 D3 提供了眾多的 SVG 圖形的生成器,它們都是只支持 SVG 的。因此,建議使用 SVG 畫布。

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

var width = 300;  //畫布的寬度
var height = 300;   //畫布的高度

var svg = d3.select("body")     //選擇文檔中的body元素
    .append("svg")          //添加一個(gè)svg元素
    .attr("width", width)       //設(shè)定寬度
    .attr("height", height);    //設(shè)定高度

有了畫布,接下來(lái)就可以在畫布上作圖了。

繪制矩形

本文繪制一個(gè)橫向的柱形圖。只繪制矩形,不繪制文字和坐標(biāo)軸。

在 SVG 中,矩形的元素標(biāo)簽是 rect。例如:

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

上面的 rect 里沒有矩形的屬性。矩形的屬性,常用的有四個(gè):

  • x:矩形左上角的 x 坐標(biāo)
  • y:矩形左上角的 y 坐標(biāo)
  • width:矩形的寬度
  • height:矩形的高度

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

現(xiàn)在給出一組數(shù)據(jù),要對(duì)此進(jìn)行可視化。數(shù)據(jù)如下:

var dataset = [ 250 , 210 , 170 , 130 , 90 ];  //數(shù)據(jù)(表示矩形的寬度)

為簡(jiǎn)單起見,我們直接用數(shù)值的大小來(lái)表示矩形的像素寬度(后面會(huì)說(shuō)到這不是一種好方法)。然后,添加以下代碼。

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

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 數(shù)組的長(zhǎng)度相同數(shù)量的矩形,所使用的語(yǔ)句是:

svg.selectAll("rect")   //選擇svg內(nèi)所有的矩形
    .data(dataset)  //綁定數(shù)組
    .enter()        //指定選擇集的enter部分
    .append("rect") //添加足夠數(shù)量的矩形元素

這段代碼以后會(huì)常常出現(xiàn)在 D3 的代碼中,請(qǐng)務(wù)必牢記。目前不深入討論它的作用機(jī)制是怎樣的,只需要讀者牢記,當(dāng):

有數(shù)據(jù),而沒有足夠圖形元素的時(shí)候,使用此方法可以添加足夠的元素。

添加了元素之后,就需要分別給各元素的屬性賦值。在這里用到了 function(d, i),前面已經(jīng)講過,d 代表與當(dāng)前元素綁定的數(shù)據(jù),i 代表索引號(hào)。給屬性賦值的時(shí)候,是需要用到被綁定的數(shù)據(jù),以及索引號(hào)的。

最后一行的:

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

比例尺是 D3 中很重要的一個(gè)概念,上一章里曾經(jīng)提到過直接用數(shù)值的大小來(lái)代表像素不是一種好方法,本章正是要解決此問題。

為什么需要比例尺

上一章制作了一個(gè)柱形圖,當(dāng)時(shí)有一個(gè)數(shù)組:

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

繪圖時(shí),直接使用 250 給矩形的寬度賦值,即矩形的寬度就是 250 個(gè)像素。

此方式非常具有局限性,如果數(shù)值過大或過小,例如:

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

對(duì)以上兩個(gè)數(shù)組,絕不可能用 2.5 個(gè)像素來(lái)代表矩形的寬度,那樣根本看不見;也不可能用 2500 個(gè)像素來(lái)代表矩形的寬度,因?yàn)楫嫴紱]有那么長(zhǎng)。

于是,我們需要一種計(jì)算關(guān)系,能夠:

將某一區(qū)域的值映射到另一區(qū)域,其大小關(guān)系不變。

這就是比例尺(Scale)。

有哪些比例尺

比例尺,很像數(shù)學(xué)中的函數(shù)。例如,對(duì)于一個(gè)一元二次函數(shù),有 x 和 y 兩個(gè)未知數(shù),當(dāng) x 的值確定時(shí),y 的值也就確定了。

在數(shù)學(xué)中,x 的范圍被稱為定義域,y 的范圍被稱為值域。

D3 中的比例尺,也有定義域和值域,分別被稱為 domain 和 range。開發(fā)者需要指定 domain 和 range 的范圍,如此即可得到一個(gè)計(jì)算關(guān)系。

D3 提供了多種比例尺,下面介紹最常用的兩種。

線性比例尺

線性比例尺,能將一個(gè)連續(xù)的區(qū)間,映射到另一區(qū)間。要解決柱形圖寬度的問題,就需要線性比例尺。

假設(shè)有以下數(shù)組:

var dataset = [1.2, 2.3, 0.9, 1.5, 3.3];

現(xiàn)有要求如下:

將 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() 返回一個(gè)線性比例尺。domain() 和 range() 分別設(shè)定比例尺的定義域和值域。在這里還用到了兩個(gè)函數(shù),它們經(jīng)常與比例尺一起出現(xiàn):

  • d3.max()
  • d3.min()

這兩個(gè)函數(shù)能夠求數(shù)組的最大值和最小值,是 D3 提供的。按照以上代碼,

比例尺的定義域 domain 為:[0.9, 3.3]

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

因此,當(dāng)輸入 0.9 時(shí),返回 0;當(dāng)輸入 3.3 時(shí),返回 300。當(dāng)輸入 2.3 時(shí)呢?返回 175,這是按照線性函數(shù)的規(guī)則計(jì)算的。

有一點(diǎn)請(qǐng)大家記?。?/p>

d3.scale.linear() 的返回值,是可以當(dāng)做函數(shù)來(lái)使用的。因此,才有這樣的用法:linear(0.9)。

序數(shù)比例尺

有時(shí)候,定義域和值域不一定是連續(xù)的。例如,有兩個(gè)數(shù)組:

var index = [0, 1, 2, 3, 4];
var color = ["red", "blue", "green", "yellow", "black"];

我們希望 0 對(duì)應(yīng)顏色 red,1 對(duì)應(yīng) blue,依次類推。

但是,這些值都是離散的,線性比例尺不適合,需要用到序數(shù)比例尺。

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

ordinal(0); //返回 red
ordinal(2); //返回 green
ordinal(4); //返回 black
給柱形圖添加比例尺

修改一下數(shù)組,再定義一個(gè)線性比例尺。

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

其后,按照上一章的方法添加矩形,在給矩形設(shè)置寬度的時(shí)候,應(yīng)用比例尺。

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

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

如此一來(lái),所有的數(shù)值,都按照同一個(gè)線性比例尺的關(guān)系來(lái)計(jì)算寬度,因此數(shù)值之間的大小關(guān)系不變。

坐標(biāo)軸

坐標(biāo)軸,是可視化圖表中經(jīng)常出現(xiàn)的一種圖形,由一些列線段和刻度組成。坐標(biāo)軸在 SVG 中是沒有現(xiàn)成的圖形元素的,需要用其他的元素組合構(gòu)成。D3 提供了坐標(biāo)軸的組件,如此在 SVG 畫布中繪制坐標(biāo)軸變得像添加一個(gè)普通元素一樣簡(jiǎn)單。

image-20190802180149326.png

在 SVG 畫布的預(yù)定義元素里,有六種基本圖形:

  • 矩形
  • 圓形
  • 橢圓
  • 線段
  • 折線
  • 多邊形

另外,還有一種比較特殊,也是功能最強(qiáng)的元素:

  • 路徑

畫布中的所有圖形,都是由以上七種元素組成。

顯然,這里面沒有坐標(biāo)軸 這種元素。如果有的話,我們可以采用類似以下的方式定義:

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

很可惜,沒有這種元素。但是,這種設(shè)計(jì)是合理的:不可能為每一種圖形都配備一個(gè)單獨(dú)的元素,那樣 SVG 就會(huì)過于龐大。

因此,我們需要用其他元素來(lái)組合成坐標(biāo)軸,最終使其變?yōu)轭愃埔韵碌男问剑?/p>

<g>
<!-- 第一個(gè)刻度 -->
<g>
<line></line>   <!-- 第一個(gè)刻度的直線 -->
<text></text>   <!-- 第一個(gè)刻度的文字 -->
</g>
<!-- 第二個(gè)刻度 -->
<g>
<line></line>   <!-- 第二個(gè)刻度的直線 -->
<text></text>   <!-- 第二個(gè)刻度的文字 -->
</g> 
...
<!-- 坐標(biāo)軸的軸線 -->
<path></path>
</g>

分組元素 ,是 SVG 畫布中的元素,意思是 group。此元素是將其他元素進(jìn)行組合的容器,在這里是用于將坐標(biāo)軸的其他元素分組存放。

如果需要手動(dòng)添加這些元素就太麻煩了,為此,D3 提供了一個(gè)組件:d3.svg.axis()。它為我們完成了以上工作。

定義坐標(biāo)軸

上一章提到了比例尺的概念,要生成坐標(biāo)軸,需要用到比例尺,它們二者經(jīng)常是一起使用的。下面,在上一章的數(shù)據(jù)和比例尺的基礎(chǔ)上,添加一個(gè)坐標(biāo)軸的組件。

var dataset = [1, 2, 3, 4, 5];  // 數(shù)據(jù)源  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]);

// 添加坐標(biāo)軸
var xAxis = d3.axisBottom(linear);
在svg中添加坐標(biāo)軸

定義了坐標(biāo)軸之后,只需要在 SVG 中添加一個(gè)分組元素 ,再將坐標(biāo)軸的其他元素添加到這個(gè) 里即可。代碼如下:

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

讓你的坐標(biāo)軸動(dòng)起來(lái)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)

        // 完成 靜態(tài)的  坐標(biāo)軸
        // 線性比例尺
        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() {
            // 更新數(shù)據(jù)
            scale.domain([0, Math.random() * 100]);
            // g.call(axis);
            // transition d3提供默認(rèn)的動(dòng)畫
            d3.select('#g').transition().call(axis)
        })

    </script>
</body>

</html>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容