1. 程式人生 > >D3 入門筆記

D3 入門筆記

一個點 最大 長度 連接 全部 示意圖 == 改變 失真

一、第一個程序

選擇集: 使用d3.select()或者 d3.selectAll()選擇元素後返回的對象,就是選擇集

d3能夠連續不斷地調用函數,形如:d3.select().selctAll().text()這稱為鏈式語法

二、選擇元素和綁定數據

在D3中,用於選擇元素的函數有兩個:

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

這兩個函數返回的結果稱為選擇集,選擇集合綁定數據通常是一起使用的


D3 中通過以下兩個函數來綁定數據:

1)datum():綁定一個數據到選擇集上
2)data():綁定一個數組到選擇集上,數組的各項值分別於選擇集的各元素綁定

var str="China";
var body=d3.select(‘body‘);
var p=body.selectAll("p");
p.datum(str);
p.text(function(d,i){
return "第"+i+"個元素綁定的數據是"+d;
});

上面代碼中,用到了一個無名函數 function(d,i),當選擇集需要使用被綁定的數據時,
常需要這麽使用,其包含兩個參數,其中:

d 代表數據,也就是與某元素綁定的數據
i 代表索引,代表數據的索引號,從0開始


data()
有一個數組,接下來要分別將數組的各元素綁定到三個段落元素上

var dataset = [‘I like dog‘,‘I like cat‘,‘I like snake‘];
var body = d3.select("body");
var p = body.selectAll("p");
p.data(dataset)
.text(function(d,i){
return d;
});

三個段落元素與數組dataset 的三個字符串是一一對應

三、選擇、插入、刪除元素

select 和selectAll的用法

假設 body中有三個段落元素:

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

(1) 選擇元素

現在,要分別完成以下四種選擇元素的任務

1)選擇第一個p元素

使用select,參數傳入p即可

var p1=d3.select("p");
p1.style("color","red");
2)選擇三個 p 元素

var body=d3.select("body");
var p= body.selectAll("p");
p.style("color","green");

3)選擇第二個p 元素

一種比較簡單的是給第二個元素添加一個id號,然後使用select 選擇元素,註意參數中
id 名稱前要加 # 號

var p2= body.select("#myid");
p2.style("color",‘red‘);

4)選擇後兩個p元素

給後兩個元素添加class

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

關於 select 和selectAll 的參數,其實是符合css 選擇器的條件的,
此外,對於已經綁定了數據的選擇集,還有一種選擇元素的方法,那就是靈活運用
function(d,i),我們已經知道參數 i 是代表索引號的,於是便可以用條件判定語句來指定執行的元素

(2)插入元素

插入元素涉及兩個函數:

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


在body末尾添加一個p元素

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

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

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

(3)刪除元素

刪除一個元素時,對於選擇的元素,使用remove即可,例如:

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


四、做一個簡單的圖表

柱形圖是一種最簡單的可視化圖標,主要有 矩形、文字標簽、坐標軸組成。

如何使用D3 在SVG畫布中繪圖

1)畫布

要繪圖,首要需要的是一塊繪圖的“畫布”;
HTML5 提供兩種強有力的畫布:SVG 和Canvas

SVG 是什麽:指可縮放矢量圖形,用於描述二維矢量圖形的一種圖形格式。SVG使用XML
格式來定義圖形。絕大部分瀏覽器都支持SVG,可將SVG文本直接嵌入HTML中顯示

SVG 有如下特點:

1)SVG繪制的是矢量圖,因此對圖象進行放大不會失真
2)基於XMl,可以為每個元素添加javaScript 事件處理器
3)每個圖形均視為對象,更改對象的屬性,圖形也會改變
4)不適合遊戲應用


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

var width = 300;
var height = 300;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
var dataset =[250 ,210 ,170 ,130,90];
var rectHeight = 25;
svg.selectAll("rect")
.data(dataset)
.enter() //這個不能少
.append("rect") //添加rect
.attr("x",20) //設置x
.attr("y",function(d,i){ //設置y
return i* rectHeight;
})
.attr("width",function(d,i){ //設置寬度
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代表索引號。給屬性賦值的時候,是需要用到被綁定的數據,已經索引號的


五、比例尺的使用

比例尺是D3中很重要的一個概念,上一章裏曾經提到過直接用數值的大小來代表像素不是一種好辦大
本章正是要解決此問題。

上面制作的柱形圖,繪圖時,直接使用數組中的值給矩形的寬度賦值,即矩形的寬度就是 250 個像素

此方式非常有局限性,如果數值過大或過小就不合理了。

於是,我們需要一種計算關系,能夠 將某一區域的值映射到另一區域,其大小關系不變

這就是比例尺,那麽有哪些比例尺呢

在數學中,x範圍被稱為定義域,y的範圍被稱為值域。

D3中的比例尺,也有定義域和值域,分別被稱為 domain 和range

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

線性比例尺:

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

假設有以下數組:
var dataset = [1.2,2.3,0.9,1.5,3.3];

要求如下: 將dataset 中最小值,映射成0,將最大的值映射成300

代碼如下:

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

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

var linear = d3.scale.linear()
.domain([min,max])
.range([0,300]);
console.log(linear(0.9)); //0
console.log( linear(2.3)); //175
console.log(linear(3.3)); //300

其中,d3.scale.linear()返回一個線性比例尺。domain()和range()分別設定比例尺的
定義域和值域。在這裏還用到了兩個函數,它們經常與比例尺一起出現:
d3.max()
d3.min()

這兩個函數能夠求數組的最大值和最小值,是D3提供的。
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 index = [0,1,2,3,4];
var color = ["red","blue","green","yellow","black"];
var ordinal = d3.scale.ordinal()
.domain(index)
.range(color);

console.log(ordinal(0));
console.log(ordinal(2));
console.log(ordinal(4));

用法和線性比例尺是類似的。

給柱形圖添加比例尺

var width = 300;
var height = 300;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
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") //添加rect
.attr("x",20) //設置x
.attr("y",function(d,i){ //設置y
return i* rectHeight;
})
.attr("width",function(d,i){ //設置寬度
return linear(d);
})
.attr("height",rectHeight-2) //設置高度
.attr("fill","steelblue");


六、 坐標軸

坐標軸是可視化圖表中經常出現的一種圖形,由一些列線和刻度組成。坐標軸在SVG中
是沒有現成的圖形元素的,需要用其他的元素組合構成。D3提供了坐標軸的組件。

坐標軸由什麽構成

在SVG 畫布的預定義元素裏,有六種基本圖形:

1)矩形
2)圓形
3)橢圓
4)線段
5)折線
6)多邊形

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

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

D3提供了一個組件:d3.svg.axis(),它為我們完成了坐標軸的工作

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

定義了坐標軸之後,只需要在svg中添加一個分組元素,再將坐標軸的其他元素添加到
這個分組元素中即可。代碼如下:

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

上面有一個call()函數,其參數是前面定義的坐標軸axis

在D3中,call()的參數是一個函數。調用之後,將當前的選擇集作為參數傳遞給此函數。

設定坐標軸的樣式和位置

提供了一個常見的樣式:
<style>
.axis path,.axis line{
fill:none;
stroke:black;
shape-rendering: crispEdges;
}
.axis text{
font-family:sans-serif;
font-size:11px;
}
</style>

帶刻度的柱狀圖

var width = 300;
var height = 300;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
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.append("g")
.attr("class","axis")
.attr("transform","translate(20,130)")
.call(axis);
var rectHeight = 25;
svg.selectAll("rect")
.data(dataset)
.enter() //這個不能少
.append("rect") //添加rect
.attr("x",20) //設置x
.attr("y",function(d,i){ //設置y
return i* rectHeight;
})
.attr("width",function(d,i){ //設置寬度
return linear(d);
})
.attr("height",rectHeight-2) //設置高度
.attr("fill","steelblue");


七、完整的柱形圖

一個完整的柱形圖包含三部分:矩形、文字、坐標軸。一下是完整柱形圖:內容包括:選擇集、數據綁定、比例尺、坐標軸等內容


//添加SVG畫布
var width = 400;
var height = 400;
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]) //因為y軸默認是越往下越大,所以反著寫的

//定義坐標軸
//定義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",‘#e4393c‘);

//添加文字元素
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;
});
//添加坐標軸的元素 +padding.left+","+"+(height-padding.bottom)+"
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中制作動態圖表稱為 過渡

實現動態的方法:

D3 提供了4個方法用於實現圖形的過渡: 從狀態A到狀態 B

1)transition()

啟動過渡效果

其前後是圖形變化前後的狀態(形狀、位置、顏色等等) 例如:

.attr("fill","red") //初始顏色為紅色
.transition() //啟動過渡
.attr("fill","steelblue") //終止顏色為鐵藍色

D3會自動對兩種顏色(紅色和鐵藍色)之間的顏色值進行插值計算,得到過渡用的顏色值。我們無需
知道中間是怎麽計算的,只需要享受結果即可

duration()

指定過渡的持續時間 ,單位為毫秒

如 duration(2000)


ease()

指定過渡方式,常用的有:

linear:普通的線性變化
circle:慢慢地到達變換的最終狀態
elastic:帶有彈跳的到達最終狀態
bounce:在最終狀態處彈跳幾次

調用時,形如;ease("bounce")

delay()

指定延遲的時間,表示一定時間後才開始轉變,單位同樣為毫秒。此函數可以對整體指定延遲,
也可以對個別指定延遲

例如:對整體指定時:

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

又如,對一個個的圖形進行指定:

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

實現簡單的動態效果

var width = 400;
var height = 400;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
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);

var circle2 = svg.append("circle")
.attr("cx", 100)
.attr("cy", 200)
.attr("r", 45)
.style("fill","green");//與第一個圓一樣,省略部分代碼

//在1.5秒(1500毫秒)內將圓心坐標由100變為300,
//將顏色從綠色變為紅色
circle2.transition()
.duration(1500)
.attr("cx", 300)
.style("fill","red");

var circle3 = svg.append("circle")
.attr("cx", 100)
.attr("cy", 300)
.attr("r", 45)
.style("fill","green");//與第一個圓一樣,省略部分代碼
circle3.transition()
.duration(2000)
.ease("bounce")
.attr("cx", 300)
.style("fill","red")
.attr("r", 25);

動起來的柱狀圖

<style>
.axis path,.axis line{
fill:none;
stroke:black;
shape-rendering: crispEdges;
}
.axis text{
font-family:sans-serif;
font-size:11px;
}
</style>

<script>
//添加SVG畫布
var width = 400;
var height = 400;
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]) //因為y軸默認是越往下越大,所以反著寫的

//定義坐標軸
//定義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;
} )
.transition()
.delay(function(d,i){
return i*200;
})
.duration(2000)
.ease("bounce")
.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",‘#e4393c‘);

//添加文字元素
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){
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)})
.attr("dx",function(){
return (xScale.rangeBand() - rectPadding)/2;
})
.attr("dy",function(d){
return 20;
})
.style("stroke","#fff")
.text(function(d){
return d;
});
//添加坐標軸的元素 +padding.left+","+"+(height-padding.bottom)+"
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);
</script>


九、理解 Update 、Enter 、Exit

Update 、Enter 、Exit 是D3 中三個非常重要的概念,它處理的是當選擇集和數據的數量關系
不確定的情況。

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

這段代碼使用的情況是當以下情況出現的時候:

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


假設,在 body 中有三個 p 元素,有一數組 [3, 6, 9],則可以將數組中的每一
項分別與一個 p 元素綁定在一起。但是,有一個問題:當數組的長度與元素數量不
一致(數組長度 > 元素數量 or 數組長度 < 元素數量)時呢?這時候就需要理解
Update、Enter、Exit 的概念。

如果數組為 [3, 6, 9, 12, 15],將此數組綁定到三個 p 元素的選擇集上。可以想
象,會有兩個數據沒有元素與之對應,這時候 D3 會建立兩個空的元素與數據對應,
這一部分就稱為 Enter。而有元素與數據對應的部分稱為 Update。如果數組為 [3],
則會有兩個元素沒有數據綁定,那麽沒有數據綁定的部分被稱為 Exit。示意圖如下
所示。

此時 SVG 裏沒有 rect 元素,即元素數量為 0。有一數組 dataset,將數組與元素
數量為 0 的選擇集綁定後,選擇其 Enter 部分(請仔細看上圖),然後添加
(append)元素,也就是添加足夠的元素,使得每一個數據都有元素與之對應。

Update 和 Enter 的使用
當對應的元素不足時 ( 綁定數據數量 > 對應元素 ),需要添加元素(append)。

現在 body 中有三個 p 元素,要綁定一個長度大於 3 的數組到 p 的選擇集上,然後分別處理 update 和 enter 兩部分。

var dataset = [ 3 , 6 , 9 , 12 , 15 ];

//選擇body中的p元素
var p = d3.select("body").selectAll("p");

//獲取update部分
var update = p.data(dataset);

//獲取enter部分
var enter = update.enter();

//update部分的處理:更新屬性值
update.text(function(d){
return "update " + d;
});

//enter部分的處理:添加元素後賦予屬性值
enter.append("p")
.text(function(d){
return "enter " + d;
});
結果如下圖,update 部分和 enter 部分被綁定的數據很清晰地表示了出來。

update和enter

請大家記住:

update 部分的處理辦法一般是:更新屬性值
enter 部分的處理辦法一般是:添加元素後,賦予屬性值


Update 和 Exit 的使用
當對應的元素過多時 ( 綁定數據數量 < 對應元素 ),需要刪掉多余的元素。

現在 body 中有三個 p 元素,要綁定一個長度小於 3 的數組到 p 的選擇集上,然後分別處理 update 和 exit 兩部分。

var dataset = [ 3 ];

//選擇body中的p元素
var p = d3.select("body").selectAll("p");

//獲取update部分
var update = p.data(dataset);

//獲取exit部分
var exit = update.exit();

//update部分的處理:更新屬性值
update.text(function(d){
return "update " + d;
});

//exit部分的處理:修改p元素的屬性
exit.text(function(d){
return "exit";
});

//exit部分的處理通常是刪除元素
// exit.remove();
結果如下,請大家區分好 update 部分和 exit 部分。這裏為了表明哪一部分是 exit,並沒有刪除掉多余的元素,但實際上 exit 部分的絕大部分操作是刪除。

update和exit

請大家記住:

exit 部分的處理辦法一般是:刪除元素(remove)

十、交互式操作

用戶用於交互的工具一般有三種:鼠標、鍵盤、觸屏。
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:當用戶釋放鍵時觸發,不區分字母的大小寫。 觸屏常用的事件有三個:

touchstart:當觸摸點被放在觸摸屏上時。
touchmove:當觸摸點在觸摸屏上移動時。
touchend:當觸摸點從觸摸屏上拿開時。 當某個事件被監聽到時,D3 會把當前的事件存到 d3.event 對象,裏面保存了當前事件的各種參數

//添加SVG畫布
var width = 400;
var height = 400;
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]) //因為y軸默認是越往下越大,所以反著寫的

//定義坐標軸
//定義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",‘#e4393c‘)
.on("mouseover",function(d,i){
d3.select(this)
.attr("fill","yellow");
})
.on("mouseout",function(d,i){
d3.select(this)
.transition()
.duration(500)
.attr("fill","#e4393c");
});

//添加文字元素
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){
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)})
.attr("dx",function(){
return (xScale.rangeBand() - rectPadding)/2;
})
.attr("dy",function(d){
return 20;
})
.attr("stroke","#fff")
.text(function(d){
return d;
});
//添加坐標軸的元素 +padding.left+","+"+(height-padding.bottom)+"
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);

這段代碼添加了鼠標移入(mouseover),鼠標移出(mouseout)兩個事件的監聽器。
監聽器函數中都使用了 d3.select(this),表示選擇當前的元素,this 是當前的
元素,要改變響應事件的元素時這麽寫就好。

mouseover 監聽器函數的內容為:將當前元素變為黃色

mouseout 監聽器函數的內容為:緩慢地將元素變為原來的顏色(藍色)

十一、布局

我們可以據此定義什麽時候選擇 D3 比較好:

選擇 D3:如果希望開發腦海中任意想象到的圖表。
選擇 Highcharts、Echarts 等:如果希望開發幾種固定種類的、十分大眾化的圖表。
如何理解布局
從上面的圖可以看到,布局的作用是:將不適合用於繪圖的數據轉換成了適合用於繪圖的數據。

因此,為了便於初學者理解,本站的教程叫布局的作用解釋成:數據轉換。


布局有哪些
D3 總共提供了 12 個布局:餅狀圖(Pie)、力導向圖(Force)、弦圖(Chord)、
樹狀圖(Tree)、集群圖(Cluster)、捆圖(Bundle)、打包圖(Pack)、
直方圖(Histogram)、分區圖(Partition)、堆棧圖(Stack)、
矩陣樹圖(Treemap)、層級圖(Hierarchy)。

1)餅圖


利用布局繪制餅圖:

var width = 400;
var height = 400;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
//用D3進行數據可視化

var dataset = [30,10,43,55,13];
//定義一個布局
var pie = d3.layout.pie();
//返回值賦給變量pie,此時 pie 可以當做函數使用
var piedata = pie(dataset);
//將數組dataset作為pie()的參數,返回值給piedata,這樣 piedata 就是轉換後的數據
// console.log(piedata); //轉換後為一個包含5個對象的數組
//布局不是要繪圖而是為了得到繪圖所需的數據
//為了根據轉換後的數據 piedata 來作圖,還需要一樣工具:生成器。
//SVG 有一個元素,叫做路徑 path,是 SVG 中功能最強的元素,
// 它可以表示其它任意的圖形。顧名思義,路徑元素就是通過定義
// 一個段“路徑”,來繪制出各種圖形。但是,路徑是很難計算的,
// 通過布局轉換後的數據 piedat
// a 仍然很難手動計算得到路徑值。為我們完成這項任務的,就是生成器。
//這裏要用到的叫做弧生成器,能夠生成弧的路徑,因為餅圖的每一部分都是一段弧。
var outerRadius = 150;
var innerRadius = 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
//color是一個顏色比例尺,它能根據傳入的索引號獲取相應的顏色值
var color = d3.scale.category10();
var selectColor = "";
arcs.append("path")
.attr("fill",function(d,i){
return color(i);
})
.attr("d",function(d){
return arc(d); //調用弧生成器,得到路徑值
});
//在每一個弧線中心添加文本

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 才是轉換前的整數的值。

2)力導向圖


力導向圖(Force-Directed Graph),是繪圖的一種算法。在二維或三維空間裏配置
節點,節點之間用線連接,稱為連線。各連線的長度幾乎相等,且盡可能不相交。
節點和連線都被施加了力的作用,力是根據節點和連線的相對位置計算的。
根據力的作用,來計算節點和連線的運動軌跡,並不斷降低它們的能量,
最終達到一種能量很低的安定狀態。

力導向圖能表示節點之間的多對多的關系

布局(數據轉換) d3.layout.force()。

var width = 800;
var height = 400;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
//用D3進行數據可視化

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(); //開始作用
console.log(nodes);
console.log(edges);
//轉換後,節點對象裏多了一些變量。其意義如下:

// index:節點的索引號
//px, py:節點上一個時刻的坐標
//x, y:節點的當前坐標
//weight:節點的權重
//有了轉換後的數據就可以作圖了。分別繪制三種圖形元素
//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; });
});


var width = 800;
var height = 400;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
//用D3進行數據可視化

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(); //開始作用
console.log(nodes);
console.log(edges);
//轉換後,節點對象裏多了一些變量。其意義如下:

// index:節點的索引號
//px, py:節點上一個時刻的坐標
//x, y:節點的當前坐標
//weight:節點的權重
//有了轉換後的數據就可以作圖了。分別繪制三種圖形元素
//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; });
});


3)樹狀圖

樹狀圖,可表示節點之間的包含與被包含關系

數據

初始數據先寫在一個JSON文件中,再用D3來讀取

JSON 是一種輕量級的數據交換格式。

{
"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":"哈密"}
]
}
]
}

這段數據表示:“中國”“省份名”“城市名”的包含與被包含關系

布局(數據轉換)

<style>
.node circle{
fill:#fff;
stroke:steelblue;
stroke-width: 1.5px;
}
.node{
font: 12px sans-serif;
}
.link{
fill:none;
stroke:#ccc;
stroke-width: 1.5px;
}
</style>
var width = 800;
var height = 800;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height)
.style("padding-top","50px");
var tree = d3.layout.tree()
.size([width,height-500])
.separation(function(a,b){return (a.parent == b.parent ?1:2);})
//布局保存在變量tree中
//size():設定尺寸,即轉換後的各節點的坐標在哪一個範圍內
//separation():設定節點之間的間隔

//轉換數據
d3.json("data/data.json",function(error,root){
//root是讀入的數據
var nodes = tree.nodes(root);
var links = tree.links(nodes);
// console.log(nodes);
//console.log(links);
//畫點
var node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class","node")
.attr("transform",function(d){return "translate("+ d.x+","+ d.y+")"})
//加圈圈
node.append("circle")
.attr("r",4.5)
//加文字
var text = node.append("text")
.attr("dy",[15,15,15,15])
.attr("dx",[-11,-11,-11,-11])
.style("text-anchor", function (d) {return d.children?"end":"start"})
.text(function(d){
return d.name
})

var diagonal = d3.svg.diagonal()
.projection(function(d){return [d.x, d.y];})
//畫線
var line = svg.selectAll("link")
.data(links)
.enter()
.append("path")
.attr("class","link")
.attr("d",diagonal)
});
// //d3.json() 是用來向服務器請求JSON文件的
// //d3.json()函數後面跟一個無名函數function(error),參數root是讀取的數據
//
// //後兩行代碼調用tree轉換數據,保存到變量nodes和links中。然後輸出轉換後的數據
// //nodes 中有各個節點的子節點(children)、深度(depth)、名稱(name)、位置(x,y)信息,其中名稱(name)是 json 文件中就有的屬性。
//
// //links 中有連線兩端( source , target )的節點信息。
// //繪制
// //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); //使用對角線生成器

D3 入門筆記