1、用D3更改Hello World
<html>
<head>
<meta charset="utf-8">
<title>HelloWorld</title>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<p>Hello World</p>
<script>
d3.select("body").select("p").text("SWUSTVIS");
</script>
</body>
</html>
輸出結果SWUSTVIS
2、選擇集
在 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");
3、綁定數據
選擇集和綁定數據通常是一起使用的,D3 中是通過以下兩個函數來綁定數據的:
datum():綁定一個數據到選擇集上
data():綁定一個數組到選擇集上,數組的各項值分別與選擇集的各元素綁定
假設現在有三個段落元素如下:
<p></p>
<p></p>
<p></p>
對於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、選擇、插入、刪除元素
1.選擇元素
假設在 body 中有三個段落元素:
<p>Sun</p>
<p>Moon</p>
<p>You</p>
現在,要分別完成以下四種選擇元素的任務。
選擇第一個元素
d3.select("body").select("p").style("color","red");
選擇第所有元素
d3.select("body").selectAll("p").style("color","red");
選擇第二個元素
方法很多,一種比較簡單的是給第二個元素添加一個 id 號。
<p id="moon">Moon</p>
d3.select("#moon").style("color","red");
選擇後兩個元素
給後兩個元素添加 class,
<p class="myclass">Moon</p>
<p class="myclass">You</p>
由於需要選擇多個元素,要用 selectAll
。
d3.selectAll(".myclass").style("color","red");
2.插入元素
插入元素涉及的函數有兩個:
append():在選擇集末尾插入元素
insert():在選擇集前面插入元素
假設有三個段落元素,與上文相同。
append()
d3.select("body").append("p").text("Star");
insert()
d3.select("body").insert("p","#moon").text("Star");
3.刪除元素
刪除一個元素時,對於選擇的元素,使用 remove 即可。
remove()
d3.select("#moon").remove();
5、做一個簡單的圖表
柱形圖是一種最簡單的可視化圖標,主要有矩形、文字標籤、座標軸組成。爲簡單起見,只繪製矩形的部分,用以講解如何使用 D3 在 SVG 畫布中繪圖。
畫布是什麼
之前處理對象都是 HTML 的文字,沒有涉及圖形的製作。要繪圖,首要需要的是一塊繪圖的“畫布”。HTML 5 提供兩種強有力的“畫布”:SVG 和 Canvas。
SVG 繪製的是矢量圖,因此對圖像進行放大不會失真,可以爲每個元素添加 JavaScript 事件處理器。每個圖形均視爲對象,更改對象的屬性,圖形也會改變。要注意,在 SVG 中,x 軸的正方向是水平向右,y 軸的正方向是垂直向下的。
在 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。
例如:
<svg>
<rect></rect>
<rect></rect>
</svg>
矩形的屬性,常用的有四個:
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");
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");
PS:上面的例子是值和像素大小是一樣的,那麼就會出現一個問題(引入比例尺)。
6、比例尺的使用
爲什麼需要比例尺
上一章製作了一個柱形圖,當時有一個數組:
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 提供了多種比例尺,下面介紹最常用的兩種。
1)線性比例尺
線性比例尺,能將一個連續的區間,映射到另一區間。要解決柱形圖寬度的問題,就需要線性比例尺。假設有以下數組:
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() 分別設定比例尺的定義域和值域。在這裏還用到了兩個函數,它們經常與比例尺一起出現:
在v4、v5中,d3.scale.linear()
應該寫爲d3.scaleLinear()
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)。
2)序數比例尺
有時候,定義域和值域不一定是連續的。例如,有兩個數組:
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");
如此一來,所有的數值,都按照同一個線性比例尺的關係來計算寬度,因此數值之間的大小關係不變。
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);
設定座標軸的樣式和位置
默認的座標軸樣式不太美觀,下面提供一個常見的樣式:
<style>
.axis path,
.axis line{
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
}
</style>
分別定義了類 axis 下的 path、line、text 元素的樣式。接下來,只需要將座標軸的類設定爲 axis 即可。
座標軸的位置,可以通過 transform 屬性來設定。
通常在添加元素的時候就一併設定,寫成如下形式:
svg.append("g")
.attr("class","axis")
.attr("transform","translate(20,130)")
.call(axis);
8、完整的柱形圖
一個完整的柱形圖包含三部分:矩形、文字、座標軸。本章將對前幾章的內容進行綜合的運用,製作一個實用的柱形圖,內容包括:選擇集、數據綁定、比例尺、座標軸等內容。
添加 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.scaleLinear()
.domain([0,dataset.length])
.range([0, 340]);
//y軸的比例尺
var yScale = d3.scaleLinear()
.domain([0,d3.max(dataset)])
.range([height - padding.top - padding.bottom, 0]);
定義座標軸
//定義x軸
var xAxis = d3.axisBottom()
.scale(xScale) //指定比例尺
// .orient("bottom");
// .ticks(7); //指定刻度的數量
//定義y軸
var yAxis = d3.axisLeft()
.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 35+i*40;
} )
.attr("y",function(d){
return yScale(d);
})
//矩形的寬
.attr("width", 25)
.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('fill','#fff') //設置字體顏色
.attr("class","MyText")
.attr("transform","translate(" + padding.left + "," + padding.top + ")")
.attr("x", function(d,i){ //文本x座標
return 35+i*40;
} )
.attr("y",function(d){ //文本y座標
return yScale(d);
})
.attr("dx",function(){ //文本水平平移
return 4;
})
.attr("dy",function(d){ //文本垂直平移
return 20;
})
.text(function(d){
return d;
})
添加座標軸的元素
//添加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);
9、讓圖表動起來
D3 支持製作動態的圖表。有時候,圖表的變化需要緩慢的發生,以便於讓用戶看清楚變化的過程。
什麼是動態效果
前面幾章製作的圖表是一蹴而就地出現,然後繪製完成後不再發生變化的,這是靜態的圖表。動態的圖表,是指圖表在某一時間段會發生某種變化,可能是形狀、顏色、位置等,而且用戶是可以看到變化的過程的。
例如,有一個圓,圓心爲 (100, 100)。現在我們希望圓的 x 座標從 100 移到 300,並且移動過程在 2 秒的時間內發生。這種時候就需要用到動態效果,在 D3 裏我們稱之爲過渡(transition)。
實現動態的方法
D3 提供了 4 個方法用於實現圖形的過渡:從狀態 A 變爲狀態 B。
1)transition() 啓動過渡效果
其前後是圖形變化前後的狀態(形狀、位置、顏色等等),例如:
.attr("fill","red") //初始顏色爲紅色
.transition() //啓動過渡
.attr("fill","steelblue") //終止顏色爲鐵藍色
D3 會自動對兩種顏色(紅色和鐵藍色)之間的顏色值(RGB值)進行插值計算,得到過渡用的顏色值。我們無需知道中間是怎麼計算的,只需要享受結果即可。
2)duration() 指定過渡的持續時間,單位爲毫秒。
如 duration(2000) ,指持續 2000 毫秒,即 2 秒。
3)ease()指定過渡的方式,常用的有:
.ease(d3.easeElasticInOut)
表示過渡方式
d3.easeLinear(t)
Linear(線性) 緩動
d3.easePolyOut(t)
反轉 polynomial 緩動;
d3.easeQuadInOut(t)
Symmetric cubic(對稱三次緩動)
d3.easeSinIn(t)
Sinusoidal(正弦緩動); 返回 sin(t).
d3.easeBounceIn(t)
Bounce(彈跳緩動), 就像是一個橡皮球.
d3.easeBounce(t)
d3.easeBounceOut(t)
反轉 Bounce(彈跳緩動)
ease過渡方法
4)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 畫布裏添加三個圓,圓出現之後,立即啓動過渡效果。
<template>
<div class="circles">
</div>
</template>
<script>
import * as d3 from "d3"
import { delay } from 'q';
export default {
mounted(){
//畫布大小
var width = 600;
var height =600;
//在 body 裏添加一個 SVG 畫布
var svg = d3.select(".circles")
// var svg=scale.select('svg')
.append("svg")
.attr("width", width)
.attr("height", height)
.attr('background','#ededed');
//畫布周邊的空白
var padding = {left:30, right:30, top:20, bottom:20};
//第一個圓
var circle1=svg.append('circle') //添加圓
.attr('cx',100) //圓心橫座標
.attr('cy',100) //圓心縱座標
.attr('r',50) //圓的半徑
.attr('fill','skyblue'); //圓的顏色
circle1.transition() //啓動動畫,在3秒內平移到500處
.duration(3000)
.attr('cx',500);
//第二個圓
var circle2=svg.append('circle')
.attr('cx',100)
.attr('cy',200)
.attr('r',50)
.attr('fill','yellow');
circle2.transition() //啓動動畫,在2秒內顏色變爲藍色,半徑變爲30
.duration(2000)
// .delay(2000)
.attr('fill','blue')
.attr('r',30)
//第三個圓
var circle3=svg.append('circle')
.attr('cx',100)
.attr('cy',400)
.attr('r',50)
.attr('fill','red');
circle3.transition() //啓動動畫,在3秒內顏色變爲藍色#333,半徑變爲60,平移到500處
.duration(3000)
.ease(d3.easeElasticInOut)
.attr('cx',500)
.style('fill','#333')
.attr('r',60)
}
}
</script>
給柱形圖加上動態效果
在上一章完整柱形圖的基礎上稍作修改,即可做成一個帶動態效果的、有意思的柱形圖。在添加文字元素和矩形元素的時候,啓動過渡效果,讓各柱形和文字緩慢升至目標高度,並且在目標處跳動幾次。
對於文字元素,代碼如下:
var texts = svg.selectAll(".MyText")
.data(dataset) //填充數據
.enter()
.append("text")
.attr('fill','#fff') //設置字體顏色
.attr("class","MyText")
.attr("transform","translate(" + padding.left + "," + padding.top + ")")
.attr("x", function(d,i){ //文本x座標
return 35+i*40;
} )
.attr("y",function(d){ //文本y座標
var min=yScale.domain()[0];
return yScale(min);
})
.transition() //啓動動畫
.delay(function(d,i){ //延遲時間
return i*200
})
.duration(3000) //持續時間
.ease(d3.easeBounce) //過渡方式
.attr('y',function(d){
return yScale(d);
})
.attr("dx",function(){ //文本水平平移
return 4;
})
.attr("dy",function(d){ //文本垂直平移
return 20;
})
.text(function(d){
return d;
})
文字元素的過渡前後,發生變化的是 y 座標。其起始狀態是在 y 軸等於 0 的位置(但要注意,不能在起始狀態直接返回 0,要應用比例尺計算畫布中的位置)。終止狀態是目標值。
10、理解update()、enter()、exit()
Update、Enter、Exit 是 D3 中三個非常重要的概念,它處理的是當選擇集和數據的數量關係不確定的情況。
前幾章裏,反覆出現了形如以下的代碼。
svg.selectAll("rect") //選擇svg內所有的矩形
.data(dataset) //綁定數組
.enter() //指定選擇集的enter部分
.append("rect") //添加足夠數量的矩形元素
update()
當對應的元素正好滿足時 ( 綁定數據數量 = 對應元素 ),實際上並不存在這樣一個函數,只是爲了要與之後的 enter 和 exit 一起說明纔想象有這樣一個函數。但對應元素正好滿足時,直接操作即可,後面直接跟 text ,style 等操作即可。
enter()
當對應的元素不足時 ( 綁定數據數量 > 對應元素 ),當對應的元素不足時,通常要添加元素,使之與綁定數據的數量相等。後面通常先跟 append 操作。
exit()
當對應的元素過多時 ( 綁定數據數量 < 對應元素 ),當對應的元素過多時,通常要刪除元素,使之與綁定數據的數量相等。後面通常要跟 remove 操作。
Update與Enter的使用
<template>
<div class="upd">
<p></p>
<p></p>
<p></p>
</div>
</template>
<script>
import * as d3 from "d3"
export default {
mounted(){
var dataset=[3,5,16,20,58];
var p=d3.select('.upd')
.selectAll('p');
var update=p.data(dataset)
var enter=update.enter();
update.text(function(d,i){
return "update:" +d +",index:"+i;
})
var pEnter = enter.append("p")
pEnter.text(function(d,i){
return "enter: "+d+",index: "+i;
})
}
}
</script>
Update與Exit的使用
<template>
<div class="upd">
<p></p>
<p></p>
<p></p>
</div>
</template>
<script>
import * as d3 from "d3"
export default {
mounted(){
var dataset=[3,5];
var p=d3.select('.upd')
.selectAll('p');
var update=p.data(dataset)
var exit=update.exit();
update.text(function(d,i){
return "update:" +d +",index:"+i;
})
exit.text(function(d,i){
return 'exit';
})
}
}
</script>
11、交互式操作
與圖表的交互,指在圖形元素上設置一個或多個監聽器,當事件發生時,做出相應的反應。
什麼是交互
交互,指的是用戶輸入了某種指令,程序接受到指令之後必須做出某種響應。對可視化圖表來說,交互能使圖表更加生動,能表現更多內容。例如,拖動圖表中某些圖形、鼠標滑到圖形上出現提示框、用觸屏放大或縮小圖形等等。用戶用於交互的工具一般有三種:鼠標、鍵盤、觸屏。
如何添加交互
對某一元素添加交互操作十分簡單,代碼如下:
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")
.attr("transform","translate(" + padding.left + "," + padding.top + ")")
.attr("x", function(d,i){
return 35+i*40;
} )
.attr("y",function(d){
return yScale(d);
})
//矩形的寬
.attr("width", 25)
.attr("height", function(d){
return height - padding.top - padding.bottom - yScale(d);
})
.attr("fill","red") //鼠標滑過前爲紅色
.on('mouseover',function(d,i){
d3.select(this) //this指當前元素
.attr('fill','yellow'); //鼠標滑過時爲黃色
})
.on("mouseout",function(d,i){
d3.select(this)
.transition()
.duration(1000)
.attr('fill','steelblue') //鼠標滑過後爲海藍色
})
這段代碼添加了鼠標移入(mouseover),鼠標移出(mouseout)兩個事件的監聽器。監聽器函數中都使用了 d3.select(this),表示選擇當前的元素,this 是當前的元素,要改變響應事件的元素時這麼寫就好。
12、佈局
佈局,可以理解成 “製作常見圖形的函數”,有了它製作各種相對複雜的圖表就方便多了。
佈局是什麼
佈局,英文是 Layout。從字面看,可以想到有“決定什麼元素繪製在哪裏”的意思。佈局是 D3 中一個十分重要的概念。D3 與其它很多可視化工具不同,相對來說較底層,對初學者來說不太方便,但是一旦掌握了,就比其他工具更加得心應手。下圖展示了 D3 與其它可視化工具的區別:
如何理解佈局
從上面的圖可以看到,佈局的作用是:將不適合用於繪圖的數據轉換成了適合用於繪圖的數據。因此,爲了便於初學者理解,將佈局的作用解釋成:數據轉換。
佈局有哪些
D3 總共提供了 12 個佈局:餅狀圖(Pie)、力導向圖(Force)、弦圖(Chord)、樹狀圖(Tree)、集羣圖(Cluster)、捆圖(Bundle)、打包圖(Pack)、直方圖(Histogram)、分區圖(Partition)、堆棧圖(Stack)、矩陣樹圖(Treemap)、層級圖(Hierarchy)。
12 個佈局中,層級圖(Hierarchy)不能直接使用。集羣圖、打包圖、分區圖、樹狀圖、矩陣樹圖是由層級圖擴展來的。如此一來,能夠使用的佈局是 11 個(有 5 個是由層級圖擴展而來)。這些佈局的作用都是將某種數據轉換成另一種數據,而轉換後的數據是利於可視化的。
Bundle —捆圖
Chord — 弦圖
Cluster — 集羣圖
Force —力學圖、力導向圖
Histogram —- 直方圖(數據分佈圖)
Pack —- 打包圖
Partition —- 分區圖
Pie —- 餅狀圖
Stack —- 堆棧圖
Tree —- 樹狀圖
Treemap —- 矩陣樹圖
13、餅狀圖
本章製作一個餅狀圖。在佈局的應用中,最簡單的就是餅狀圖,通過本文你將對佈局有一個初步瞭解。
數據
假設有如下數據,需要可視化:
var dataset = [ 30 , 10 , 43 , 55 , 13 ];
這樣的值是不能直接繪圖的。例如繪製餅狀圖的一個部分,需要知道一段弧的起始角度和終止角度,這些值都不存在於數組 dataset 中。因此,需要用到佈局,佈局的作用就是計算出適合於作圖的數據。
佈局(數據轉換)
定義一個佈局,d3.pie(),餅狀圖生成器
var pie = d3.pie();
返回值賦給變量 pie,此時 pie 可以當做函數使用。
var piedata = pie(dataset);
將數組 dataset 作爲 pie() 的參數,返回值給 piedata。如此一來,piedata 就是轉換後的數據。
繪製圖形
爲了根據轉換後的數據 piedata 來作圖,還需要一樣工具:生成器。SVG 有一個元素,叫做路徑 path,是 SVG 中功能最強的元素,它可以表示其它任意的圖形。顧名思義,路徑元素就是通過定義一個段“路徑”,來繪製出各種圖形。但是,路徑是很難計算的,通過佈局轉換後的數據 piedata 仍然很難手動計算得到路徑值。爲我們完成這項任務的,就是生成器。
這裏要用到的叫做弧生成器 d3.arc( {} )
,能夠生成弧的路徑,因爲餅圖的每一部分都是一段弧。
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 纔是轉換前的整數的值。
<template>
<div class="pies">
</div>
</template>
<script>
import * as d3 from "d3"
export default {
mounted(){
var width=400;
var height=400;
var marge = {top:60,bottom:60,left:60,right:60}
var svg=d3.select('.pies')
.append('svg')
.attr('width',width)
.attr('height',height);
var g = svg.append("g")
.attr("transform","translate("+marge.top+","+marge.left+")");
var dataset=[30,10,43,55,13];
//設置顏色比例尺
var colorScale=d3.scaleOrdinal()
.domain(d3.range(dataset.length))
.range(d3.schemeCategory10);
//新建一個餅狀圖
var pie=d3.pie();
//新建一個弧生成器
var innerRadius=50; //內半徑(爲0則中間沒有空白)
var outerRadius=100; //外半徑
var arc_generator = d3.arc() //弧生成器
.innerRadius(50)
.outerRadius(100);
//將原始數據變成可以繪製餅狀圖的數據
var piedata=pie(dataset);
console.log(piedata)
//開始繪製,先爲每個扇形及對應文字建立分組
var gs=g.selectAll(".g")
.data(piedata)
.enter()
.append("g")
.attr("transform","translate("+width/2+","+height/2+")")
//繪製餅狀圖的各個扇形
gs.append("path")
.attr("d",function(d){
return arc_generator(d); //往弧形生成器中出入數據
})
.attr("fill",function(d,i){
return colorScale(i); //設置顏色
});
//繪製餅狀圖上面的文字信息
gs.append("text")
.attr("transform",function(d){//位置設在中心處
return "translate("+arc_generator.centroid(d)+")";
})
.attr("text-anchor","middle")
.text(function(d){
return d.data;
})
}
}
</script>
14、力導向圖
力導向圖(Force-Directed Graph),是繪圖的一種算法。在二維或三維空間裏配置節點,節點之間用線連接,稱爲連線。各連線的長度幾乎相等,且儘可能不相交。節點和連線都被施加了力的作用,力是根據節點和連線的相對位置計算的。根據力的作用,來計算節點和連線的運動軌跡,並不斷降低它們的能量,最終達到一種能量很低的安定狀態。
數據
初始數據如下:
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; });
});
<template>
<div class="force">
</div>
</template>
<script>
import * as d3 from "d3"
export default {
mounted(){
var width=600;
var height=500;
var marge = {top:60,bottom:60,left:60,right:60}
var svg=d3.select('.force')
.append('svg')
.attr('width',width)
.attr('height',height);
var g = svg.append("g")
.attr("transform","translate("+marge.top+","+marge.left+")");
//準備數據
var nodes = [//節點集
{name:"湖南邵陽"},
{name:"山東萊州"},
{name:"廣東陽江"},
{name:"山東棗莊"},
{name:"澤"},
{name:"恆"},
{name:"鑫"},
{name:"明山"},
{name:"班長"}
];
var edges = [//邊集
{source:0,target:4,relation:"籍貫",value:1.3},
{source:4,target:5,relation:"舍友",value:1},
{source:4,target:6,relation:"舍友",value:1},
{source:4,target:7,relation:"舍友",value:1},
{source:1,target:6,relation:"籍貫",value:2},
{source:2,target:5,relation:"籍貫",value:0.9},
{source:3,target:7,relation:"籍貫",value:1},
{source:5,target:6,relation:"同學",value:1.6},
{source:6,target:7,relation:"朋友",value:0.7},
{source:6,target:8,relation:"職責",value:2}
];
//設置一個color的顏色比例尺,爲了讓不同的扇形呈現不同的顏色
var colorScale = d3.scaleOrdinal()
.domain(d3.range(nodes.length))
.range(d3.schemeCategory10);
//新建一個力導向圖
var forceSimulation = d3.forceSimulation()
.force("link",d3.forceLink())
.force("charge",d3.forceManyBody())
.force("center",d3.forceCenter());
//生成節點數據
forceSimulation.nodes(nodes)
.on("tick",ticked);//這個函數很重要,後面給出具體實現和說明
//生成邊數據
forceSimulation.force("link")
.links(edges)
.distance(function(d){//每一邊的長度
return d.value*100;
})
//設置圖形的中心位置
forceSimulation.force("center")
.x(width/2)
.y(height/2);
//在瀏覽器的控制檯輸出
console.log(nodes);
console.log(edges);
//繪製邊
var links = g.append("g")
.selectAll("line")
.data(edges)
.enter()
.append("line")
.attr("stroke",function(d,i){
return colorScale(i);
})
.attr("stroke-width",1);
var linksText = g.append("g")
.selectAll("text")
.data(edges)
.enter()
.append("text")
.text(function(d){
return d.relation;
})
var gs = g.selectAll(".circleText")
.data(nodes)
.enter()
.append("g")
.attr("transform",function(d,i){
var cirX = d.x;
var cirY = d.y;
return "translate("+cirX+","+cirY+")";
})
.call(d3.drag()
.on("start",started)
.on("drag",dragged)
.on("end",ended)
)
//繪製節點
gs.append("circle")
.attr("r",10)
.attr("fill",function(d,i){
return colorScale(i);
})
//文字
gs.append("text")
.attr("x",-10)
.attr("y",-20)
.attr("dy",10)
.text(function(d){
return d.name;
})
function ticked(){
links
.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;});
linksText
.attr("x",function(d){
return (d.source.x+d.target.x)/2;
})
.attr("y",function(d){
return (d.source.y+d.target.y)/2;
});
gs
.attr("transform",function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
function started(d){
if(!d3.event.active){
forceSimulation.alphaTarget(0.8).restart(); //設置衰減係數,對節點位置移動過程的模擬,數值越高移動越快,數值範圍[0,1]
}
d.fx = d.x;
d.fy = d.y;
}
function dragged(d){
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function ended(d){
if(!d3.event.active){
forceSimulation.alphaTarget(0);
}
d.fx = null;
d.fy = null;
}
}
}
</script>
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) { //可將數據定義在該頁面,不用.json模擬數據
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() //d3.svg.diagonal()在v5中報錯
.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); //使用對角線生成器
<template>
<div class="tree">
</div>
</template>
<script>
import * as d3 from 'd3'
export default {
mounted(){
var width=600;
var height=500;
var svg=d3.select(".tree")
.append('svg')
.attr('width',width)
.attr('height',height);
//定義邊界
var marge = {top:50, bottom:0, left:10, right:0};
var g = svg.append("g")
.attr("transform","translate("+marge.top+","+marge.left+")");
var scale = svg.append("g")
.attr("transform","translate("+marge.top+","+marge.left+")");
//數據
var dataset = {
name:"中國",
children:[
{
name:"浙江",
children:[
{name:"杭州" ,value:100},
{name:"寧波",value:100},
{name:"溫州",value:100},
{name:"紹興",value:100}
]
},
{
name:"廣西",
children:[
{
name:"桂林",
children:[
{name:"秀峯區",value:100},
{name:"疊彩區",value:100},
{name:"象山區",value:100},
{name:"七星區",value:100}
]
},
{name:"南寧",value:100},
{name:"柳州",value:100},
{name:"防城港",value:100}
]
},
{
name:"黑龍江",
children:[
{name:"哈爾濱",value:100},
{name:"齊齊哈爾",value:100},
{name:"牡丹江",value:100},
{name:"大慶",value:100}
]
},
{
name:"新疆" ,
children:
[
{name:"烏魯木齊"},
{name:"克拉瑪依"},
{name:"吐魯番"},
{name:"哈密"}
]
}
]
};
var hierarchyData = d3.hierarchy(dataset)
.sum(function(d){
return d.value;
});
var tree=d3.tree()
.size([width-200,height-200]) //設置尺寸
.separation(function(a,b){
return (a.parent == b.parent ?1:2);
})
var treeData = tree(hierarchyData);
var nodes=treeData.descendants();
var links=treeData.links();
console.log(nodes);
console.log(links);
var Bézier_curve_generator = d3.linkHorizontal()
.x(function(d) { return d.y; })
.y(function(d) { return d.x; });
//繪製邊
g.append("g")
.selectAll("path")
.data(links)
.enter()
.append("path")
.attr("d",function(d){
var start = {x:d.source.x,y:d.source.y};
var end = {x:d.target.x,y:d.target.y};
return Bézier_curve_generator({source:start,target:end});
})
.attr("fill","none")
.attr("stroke","yellow")
.attr("stroke-width",1);
var gs = g.append("g")
.selectAll("g")
.data(nodes)
.enter()
.append("g")
.attr("transform",function(d){
var cx = d.x;
var cy= d.y;
return "translate("+cy+","+cx+")";
});
//繪製節點
gs.append("circle")
.attr("r",6)
.attr("fill","white")
.attr("stroke","blue")
.attr("stroke-width",1);
//文字
gs.append("text")
.attr("x",function(d){
return d.children?-40:8;
})
.attr("y",-5)
.attr("dy",10)
.text(function(d){
return d.data.name;
})
}
}
</script>
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));
});
});