>[info] 原文鏈接:https://bost.ocks.org/mike/bar/
假如你有一些數據,比如是一組數字:
~~~
var data = [4, 8, 15, 16, 23, 42];
~~~
條形圖是一種簡單而且能[準確感知](http://flowingdata.com/2010/03/20/graphical-perception-learn-the-fundamentals-first/)的方式,來可視化這種數據。這個教程涵蓋了如何使用[D3 JavaScript庫](http://d3js.org/)制作一個條形圖。首先,我們創建一個基本的HTML版本,然后是一個基本完成的SVG[[Scalable Vector Graphics](http://www.w3.org/Graphics/SVG/)]圖表,最后在視圖中加入動畫轉換。
這個教程假設你懂得一些web開發:如何編譯一個web頁面,并能夠在瀏覽器中預覽它,如何加載[D3庫](http://d3js.org/)等等。
你也可以便捷的fork這個[CodePen template](http://codepen.io/mbostock/pen/Jaemg)來開始上路。
#### **選擇一個元素**
在純粹的JavaScript【vanilla JavaScript】中,你基本上都是一次處理一個元素。比如,為了創建一個`div`元素,設置它的內容,然后將它添加到`body`中:
~~~
var div = document.createElement("div");
div.innerHTML = "Hello, world";
document.body.appendChild(div);
~~~
使用D3(或者jQuery以及其它的庫),你可以調用`selections`來處理一組相關的元素。一并處理元素給予了`selections`強大的功能;你可以處理單獨一個元素,或者同時操作多個元素,并不需要大幅重構你的代碼。雖然這看上去是一個很小的改變,但消除循環和其它的控制流可以使你的代碼更加簡潔。
一個`selection`可以通過多種方式創建。通常你會調用一個[selector](http://www.w3.org/TR/selectors-api/)創建一個,這是一個特殊字符串通過屬性來識別想要的元素,比如通過名稱或類名("div"或者".foo"),你也可以為一個單獨的元素創建一個selection:
~~~
var body = d3.select("body");
var div = body.append("div");
div.html("Hello, world!");
~~~
你也可以很容易的在多個元素上執行同樣的操作:
~~~
var section = d3.selectAll("section");
var div = section.append("div");
div.html("Hello, world!");
~~~
#### **鏈方法【Chaining Methods】**
selections的另一個方便之處就是方法鏈【method chaining】:選擇器方法,比如[`selection.attr`](https://github.com/mbostock/d3/wiki/Selections#wiki-attr),返回當前選擇器。這可以讓你很容易在相同元素上執行多種操作。比如為了設置body的文本顏色,和背景色,不使用方法鏈的情況下,你也許是這樣寫的:
~~~
var body = d3.select("body");
body.style("color", "black");
body.style("background-color", "white");
~~~
"Body, body, body!"這種方式和下面的方法鏈相比,去除了復讀機式的重復:
~~~
d3.select("body")
.style("color", "black")
.style("background-color", "white");
~~~
你會發現我們甚至沒有用到`var`來聲明選中的body元素。執行任意操作后,這個選擇器就可以被丟棄掉。方法鏈允許你書寫很短的代碼(同時不需要再花費時間,苦思冥想變量的名字)。
然而方法鏈還有一個小技巧:大多數操作返回的還是相同的選擇器,但有些方法返回的則是一個新的選擇器!比如,[`selection.append`](https://github.com/mbostock/d3/wiki/Selections#wiki-append)會返回一個新的選擇器,包含了新添加的元素。這方便你對新增的元素執行鏈操作。
~~~
d3.selectAll("section")
.attr("class", "special")
.append("div")
.html("Hello, world!");
~~~
>[info] 方法鏈的推薦縮進模式是:保留當前選擇器的方法為4個空格縮進,而改變當前選擇器的方法為2個空格縮進
由于方法鏈只能在文檔層級中向下傳遞,所以可以使用`var`來保存選擇器的引用,以便后續回溯用到。
~~~
var section = d3.selectAll("section");
section.append("div")
.html("First!");
section.append("div")
.html("Second");
~~~
#### **手動編寫一個圖表**
現在考慮下如何不使用JavaScript創建一個條形圖。畢竟,這里只有6個數字,所以直接手寫一些div元素并不困難,將它們的寬度設置為數據的倍數,就做出了一個圖表。
~~~
<!DOCTYPE html>
<style>
.chart div {
font: 10px sans-serif;
background-color: steelblue;
text-align: right;
padding: 3px;
margin: 1px;
color: white;
}
</style>
<div class="chart">
<div style="width: 40px;">4</div>
<div style="width: 80px;">8</div>
<div style="width: 150px;">15</div>
<div style="width: 160px;">16</div>
<div style="width: 230px;">23</div>
<div style="width: 420px;">42</div>
</div>
~~~
看上去效果如下:

這個圖表有一個`div`作為容器,然后每一個條帶是一個子`div`。這些子div有一個藍色的背景色,和一個白色的前景色,于是創建出了數值右對齊的條帶標簽。你可以通過移除掉容器`div`,更加簡化這個實現。但是通常情況下,你的頁面除了圖表還要包含其他內容,所以有一個圖表容器,你就可以在不影響頁面其他部分的情況下,設置圖表的位置和樣式。
#### **自動生成一個圖表**
顯然硬編碼對于大多數數據集都是不可行的,而且這個教程的目的是教會你,如何自動從數據中生成圖表。所以現在,讓我們使用D3創建同樣的結構,從一個空頁面開始,這個頁面僅包含一個`div`,其類名為“chart”。下面的代碼選中了圖表容器,然后對于每一個條帶,添加了一個有指定寬度的子div:
~~~
d3.select(".chart")
.selectAll("div")
.data(data)
.enter().append("div")
.style("width", function(d) { return d * 10 + "px"; })
.text(function(d) { return d; });
~~~
盡管有一部分熟悉了,但這個代碼介紹了一個新的重要概念 -- 數據加載【data join】。讓我們來拆析一下,把上面簡潔的代碼拆解重寫一遍,看看它是如何工作的。
首先,我們使用一個類選擇器,選中了圖表容器。
~~~
var chart = d3.select(".chart");
~~~
然后我們通過定義把數據加入到哪個選擇器里,來初始化數據加載。
~~~
var bar = chart.selectAll("div");
~~~
數據加載是一個常用的模式,可以用來數據改變時,創建,更新,或者銷毀元素。可能看上去會比較奇怪,但是這個方式的好處就是你只需要學習一種單一的模式,就可以管理頁面。所以無論你要構造一個靜態的圖表,還是動態的,具有流體轉換的,甚至[對象持久化](https://bost.ocks.org/mike/constancy/)的圖表,你的代碼基本上都是一樣的。**可以把初始化選擇器考慮為聲明你想要創建的元素(參見“[Thinking with Joins](https://bost.ocks.org/mike/join/)”)**
下一步我們加載數據到之前定義的選擇器中,使用[selection.data](https://github.com/mbostock/d3/wiki/Selections#wiki-data)方法。
~~~
var barUpdate = bar.data(data);
~~~
因為我們知道選擇器是空的,返回的update和exit選擇器也是空的,我們只需要處理enter選擇器,它代表了沒有存在元素的新數據。我們通過添加到enter選擇器中來初始化這些不存在的元素。
~~~
var barEnter = barUpdate.enter().append("div");
~~~
現在我們設置每一個新條帶的寬度,設置為相關聯的數據值,d的倍數。
~~~
barEnter.style("width", function(d) { return d * 10 + "px"; });
~~~
因為這些元素是通過數據加載創建的,每一個條帶已經綁定了數據。我們基于它的數據設置了每個條帶的尺寸,通過傳遞一個方法來計算寬度樣式屬性。
最后,我們使用一個方法來設置每一個條帶的文本內容,生成一個標簽。
~~~
barEnter.text(function(d) { return d; });
~~~
D3的選擇器操作比如[attr](https://github.com/mbostock/d3/wiki/Selections#wiki-attr),[style](https://github.com/mbostock/d3/wiki/Selections#wiki-style)和[property](https://github.com/mbostock/d3/wiki/Selections#wiki-property),允許你指定數值,可以指定為一個常量(所有選中的元素值相同),也可以指定為一個方法(對于每一個元素分別計算)。如果一個特殊屬性的值應當依賴于元素相關的數據,那就使用一個方法來計算它;否則,如果對于所有的元素值都一樣,那就可以使用一個字符串,或者數字就足夠了。
#### **縮放到適合的大小**
這段代碼的一個缺點是[魔數](http://en.wikipedia.org/wiki/Magic_number_(programming)#Unnamed_numerical_constants)10,這個數用來縮放數據值到合適的像素寬度。這個數字依賴于數據域(數據最小值和最大值,這里分別是0, 42),以及圖表的期望寬度(420),但是顯然這些依賴都隱含于數字10中。
我們可以使這些依賴更加明確,并且使用一個線性縮放【[linear scale](https://github.com/mbostock/d3/wiki/Quantitative-Scales)】來省略魔數。D3的縮放指定了從數據空間(域【domain】)到顯示空間(范圍【range】)的一個映射:
~~~
var x = d3.scale.linear() //v4中API變更為d3.scaleLinear()
.domain([0, d3.max(data)])
.range([0, 420]);
~~~
雖然這里的`x`看上去像一個對象,但它其實也是一個方法返回縮放后的顯示值,這個顯示值處于域中一個給定數據值的范圍內。比如,輸入一個4則返回40,輸入一個16則返回160。為了使用這種新的縮放方式,只需要簡單將硬編碼乘法替換為調用這個縮放函數:
~~~
d3.select(".chart")
.selectAll("div")
.data(data)
.enter().append("div")
.style("width", function(d) { return x(d) + "px"; })
.text(function(d) { return d; });
~~~
#### **下一步:第2節**
這里展現的基本條形圖是很容易實現的,當然也有明顯的局限性。一個條形圖應該有網格線用來比較數值,而且可能你會更喜歡垂直的條形圖。或者你會想要一個不同的圖表類型,比如餅狀圖或流體圖。為了更好地視覺體驗,你將需要SVG。SVG將在下一節介紹。