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]

類似的庫(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.js(Data-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ū)別?
- 繪制的圖片格式不同
Canvas 的工具getContext 繪制出來(lái)的圖形或傳入的圖片都依賴分辨率,能夠以 .png 和 .jpg格式保存存儲(chǔ)圖像,可以說(shuō)是位圖
SVG 可以在H5中直接繪制,但繪制的是矢量圖
由于位圖依賴分辨率,矢量圖不依賴分辨率,所以Canvas和SVG的圖片格式的不同實(shí)際上是他們繪制出來(lái)的圖片的格式不同造成的。
- Canvas不支持事件處理器,SVG支持事件處理器
Canvas 繪制的圖像 都在Canvas這個(gè)畫布里面,是Canvas的一部分,不能用js獲取已經(jīng)繪制好的圖形元素。
- 適用范圍不同
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)單的柱狀圖

畫布
前幾章的處理對(duì)象都是 HTML 的文字,沒有涉及圖形的制作。
要繪圖,首要需要的是一塊繪圖的“畫布”。
HTML 5 提供兩種強(qiáng)有力的“畫布”:SVG 和 Canvas。
添加畫布
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)單。

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