
「前言」
最開始的初衷是想畫個弦圖(chord)與桑基圖(sankey),真的很炫有沒有!然而D3零基礎(chǔ)的我表示源碼看不懂,受到1萬點暴擊(+﹏+)~ 于是果斷去惡補D3的基礎(chǔ)知識,并加以整理同時加深對自己的印象。
「基礎(chǔ)概念」
選擇集
使用 d3.select() 或 d3.selectAll() 選擇元素后返回的對象,就是選擇集
無名函數(shù)
function(d, i) 這個函數(shù)以后經(jīng)常要使用到
- d 代表數(shù)據(jù),也就是與某元素綁定的數(shù)據(jù)。
- i 代表索引,代表數(shù)據(jù)的索引號,從 0 開始。
「數(shù)據(jù)綁定」
D3可以用兩種函數(shù)來綁定數(shù)據(jù):
- datum(): 綁定一個數(shù)據(jù)到選擇集上,這里的數(shù)據(jù)并非一定要是number(數(shù)值型),也可以是string(字符串)、bollean(布爾型)和object(對象)
- data(): 綁定一個數(shù)組到選擇集上,數(shù)組的各項值分別與選擇集的各元素綁定,更常用
data() 函數(shù)的常用語法
var dataset = [10,20,30,40,50];
var body = d3.select("body");
body.selectAll("p") //選擇body中的所有p,但是目前還沒有,所以是空集
.data(dataset) //綁定數(shù)組
.enter() //指定選擇集的enter部分
.append("p")
.text(function(d){
return d;
})
這里要解釋下 Enter 的概念,它與Update、Exit是D3中三個非常重要的概念,處理的是當選擇集和數(shù)據(jù)的數(shù)量關(guān)系不確定的情況。
如果數(shù)組為 [3, 6, 9, 12, 15],將此數(shù)組綁定到三個 p 元素的選擇集上??梢韵胂螅瑫袃蓚€數(shù)據(jù)沒有元素與之對應(yīng),這時候 D3 會建立兩個空的元素與數(shù)據(jù)對應(yīng),這一部分就稱為 Enter。而有元素與數(shù)據(jù)對應(yīng)的部分稱為 Update。如果數(shù)組為 [3],則會有兩個元素沒有數(shù)據(jù)綁定,那么沒有數(shù)據(jù)綁定的部分被稱為 Exit。示意圖如下所示。

「柱形圖」
Bar Chart一般包括:矩形、坐標軸與文字。
矩形
這里我們直接定義一個數(shù)組,用數(shù)組項對應(yīng)矩形的長短(然而這種方法并不理想)。
var dataset = [50, 43, 120, 87, 99, 167, 142];
定義一塊SVG的繪制區(qū)域:
var width = 600; // SVG的寬度
var height = 600; // SVG的長度
var svg = d3.select("body")
.append('svg') // body中添加SVG
.attr('width', width)
.attr('height', height);
定義三個我們要用的變量
var padding = {top: 20, right: 20, bottom: 20, left: 20};
var rectStep = 35;
var rectWidth = 30;
padding是svg內(nèi)的最外一層區(qū)域,留一段空白寬度是為了防止圖形繪制帶svg區(qū)域外。rectStep表示前一個矩形到下一個矩形的距離(包括空白間隔),而rectWidth是矩形實際的寬度。說了這么多還是看圖更易懂:

添加矩形元素
var rect = svg.selectAll("rect")
.data(dataset)
.enter() //獲取enter部分
.append("rect") //添加rect元素,使其與綁定數(shù)組的長度一致
.attr("fill","steelblue")
.attr("x",function(d,i){ //設(shè)置X坐標
return padding.left + i * rectStep;
})
.attr("y",function(d,i){ //設(shè)置Y坐標
return height - padding.bottom - d;
})
.attr("width",rectWidth) //設(shè)置矩形寬度,之前定義的
.attr("height",function(d){ //設(shè)置矩形高度,即為數(shù)組中的各項值
return d
});
因為數(shù)組dataset的長度為7,所以最后生成7個矩形。x與y坐標是矩形的左上角頂點。 這個坐標是相對應(yīng)svg繪圖區(qū)域來講的,坐標原點位于左上角(0,0)。
一張圖直接說明:

標簽文字
var text = svg.selectAll(text)
.data(dataset)
.enter()
.append("text")
.attr("fill","white")
.attr("font-size","14px")
.attr("text-anchor","middle")
.attr("x",function(d,i){ //與矩形的X坐標一樣
return padding.left + i * rectStep;
})
.attr("y",function(d){
return height - padding.bottom - d;
})
.attr('dx', rectWidth/2) //x軸相對平移距離
.attr('dy', "1em") //em單位表示的是當前文字所占一行的高度
.text(function(d){ //要顯示的文字內(nèi)容
return d;
});
添加文字標簽的方法與添加矩形元素方法相類似,不過顏色要與矩形的顏色區(qū)分。通過設(shè)置元素的text-anchor、x、y、dx與dy五個屬性,讓文字顯示在每個矩形的正中心。
其中dx,dy表示相對(x,y)平移的大小,所以文本會從(x+dx,y+dy)位置開始顯示,這個位置也叫<u>起始位置</u>。屬性text-anchor有三個值:start、middle、end,,這里用middle表示文字中心位于<u>起始位置</u>上。
還是上圖說明問題:

效果圖:

坐標軸
坐標軸的主直線由path構(gòu)成,刻度由line繪制,刻度文字用text完成。之前我們直接用數(shù)值的大小來表示像素的大小,這里我們使用比例尺,定義如下:
// SVG畫布
var width = 600;
var height = 600;
var svg = d3.select("body").append('svg')
.attr('width', width)
.attr('height', height);
// 坐標軸的線性比例尺
var xScale = d3.scale.linear()
.domain([0,10]) //定義域
.range([0,300]); //值域
// 定義坐標軸
var axis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5); //刻度的數(shù)量,這里顯示5個
//在 SVG 中添加一個分組元素,再將坐標軸的其他元素添加到里面
var gAxis = svg.append("g")
.attr("transform","translate(80,80)");
.call(axis)
gAxis.attr('class', 'axis'); //添加一些樣式,否則太太太丑了...
call()函數(shù)的使用十分常見,這里使用的參數(shù)是前面定義的坐標軸axis,等價于axis(gAxis);的形式。效果圖如下:

「柱形圖的坐標軸」
對初學者而言,這里的坑更多(老司機請無視)。主要是因為使用了比例尺之后,XY坐標軸、矩形長寬、刻度都要與之相對應(yīng)。不要問我為什么知道這么多,都是淚......
為矩形圖定義比例尺
var xAxisWidth = 300; //x軸寬度
var yAxisWidth = 300; //y軸寬度
var xScale = d3.scale.ordinal() //x軸比例尺(序數(shù)比例尺)
.domain(d3.range(dataset.length))
.rangeRoundBands([0,xAxisWidth],0.2);
var yScale = d3.scale.linear() //y軸比例尺(線性比例尺)
.domain([0,d3.max(dataset)])
.range([0,yAxisWidth]);
定義完比例尺之后,矩形的高度、位置都要用比例尺來計算。如此之后,僅需簡單修改比例尺,圖表就能自動伸縮,所以前面的<u>矩形元素</u>與<u>矩形文字</u>的代碼都需要修改
矩形元素修改部分
.attr("x",function(d,i){
return padding.left + xScale(i); // return padding.left + i * rectStep;
})
.attr("y",function(d,i){
return height - padding.bottom - yScale(d); // return height - padding.bottom - d;
})
.attr("width",xScale.rangeBand())
.attr("height",function(d){
return yScale(d);
})
標簽文字修改部分
.attr("x",function(d,i){ //與矩形的X坐標一樣
return padding.left + xScale(i);
})
.attr("y",function(d){
return height - padding.bottom - yScale(d);
})
.attr('dx', xScale.rangeBand()/2) //x軸相對平移距離
.attr('dy', "1em") //em單位表示的是當前文字所占一行的高度
.text(function(d){ //要顯示的文字內(nèi)容
return d;
});
定義坐標軸
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
yScale.range([yAxisWidth,0]); //值域相反
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
此外還要注意,y軸坐標的值域要與原來相反,從最大值到最小值,否則最后會出現(xiàn)下面這種情況:
<img src="http://upload-images.jianshu.io/upload_images/4762054-04b43eb28412b558.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" style="width: 35px;"/>
添加坐標軸元素
//添加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 + "," + (height - padding.bottom - yAxisWidth) + ")")
.call(yAxis);
這里要小心x軸、y軸平移到目標位置的距離,以及你設(shè)置padding前后左右的寬度,防止坐標軸跑到外面去(又是血與淚的教訓)。
最后效果圖:

完整源代碼
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<style>
.axis path,
.axis line{
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text{
font-family: sans-serif;
font-size: 11px;
}
</style>
</head>
<body>
<script >
// 添加SVG畫布
var dataset = [50, 43, 120, 87, 99, 167, 142];
var width = 600; // SVG的寬度
var height = 600; // SVG的長度
var svg = d3.select("body")
.append('svg') // body中添加SVG
.attr('width', width)
.attr('height', height);
var padding = {top: 20, right: 20, bottom: 20, left: 30};
// 定義數(shù)據(jù)與比例尺
var xAxisWidth = 300; //x軸寬度
var yAxisWidth = 300; //y軸寬度
var xScale = d3.scale.ordinal() //x軸比例尺(序數(shù)比例尺)
.domain(d3.range(dataset.length))
.rangeRoundBands([0,xAxisWidth],0.2);
var yScale = d3.scale.linear() //y軸比例尺(線性比例尺)
.domain([0,d3.max(dataset)])
.range([0,yAxisWidth]);
// 添加矩形和文字元素
var rect = svg.selectAll("rect")
.data(dataset)
.enter() //獲取enter部分
.append("rect") //添加rect元素,使其與綁定數(shù)組的長度一致
.attr("fill","steelblue")
.attr("x",function(d,i){ //設(shè)置X坐標
// return padding.left + i * rectStep;
return padding.left + xScale(i);
})
.attr("y",function(d,i){ //設(shè)置Y坐標
// return height - padding.bottom - d;
return height - padding.bottom - yScale(d);
})
.attr("width",xScale.rangeBand()) //設(shè)置矩形寬度
.attr("height",function(d){
return yScale(d);
})
var text = svg.selectAll(text)
.data(dataset)
.enter()
.append("text")
.attr("fill","white")
.attr("font-size","14px")
.attr("text-anchor","middle")
.attr("x",function(d,i){ //與矩形的X坐標一樣
return padding.left + xScale(i);
})
.attr("y",function(d){
return height - padding.bottom - yScale(d);
})
.attr('dx', xScale.rangeBand()/2) //x軸相對平移距離
.attr('dy', "1em") //em單位表示的是當前文字所占一行的高度
.text(function(d){ //要顯示的文字內(nèi)容
return d;
});
// 定義坐標軸
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
yScale.range([yAxisWidth,0]);
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
// 添加坐標軸
svg.append("g")
.attr("class","axis")
.attr("transform","translate(" + padding.left + "," + (height - padding.bottom) + ")")
.call(xAxis);
svg.append("g")
.attr("class","axis")
.attr("transform","translate(" + padding.left + "," + (height - padding.bottom - yAxisWidth) + ")")
.call(yAxis);
</script>
</body>
</html>
「參考資料」
Learning D3.JS
D3.js:Update、Enter、Exit
D3.js - 初體驗
D3.js數(shù)據(jù)可視化系列教程