# D3.js 資料圖表
在開始之前,先來比較一下這兩者的差異:
**C3.js (D3-based reusable chart library)**
* 基於 D3.js 的簡易繪圖框架
* 雖然 D3.js 很強大,但是卻並不容易使用
* 如果只是要畫一些簡易的圖形,可以採用延伸自 D3.js 的 C3.js
官網:[C3.js](https://c3js.org/)
**D3.js (Data-Driven Documents 資料驅動文件)**
* D3.js 富有高度的自由性
* 可透過 D3.js 的 API 繪製出各種點線面的圖
* 缺點在於高度自由性,需要多花一點時間來熟悉概念與文件
推薦文章:[SVG D3 - D3.js 初體驗](https://www.oxxostudio.tw/articles/201410/svg-d3-js.html)
官網:[D3.js](https://d3js.org/)
---
## 1. 準備資料
因為資料較多,部份省略可以參考實作 [CodePen](https://codepen.io/kaoru44689/pen/NWxYZYx/)
* 首先載入 CDN: `https://d3js.org/d3.v3.min.js`
D3.js 的重點在於將資料透過 `svg` 繪製成圖表,所以一開始要準備好要繪製的資料
這次我想做的是兩份資料的比對曲線,因為偷懶所以月份的部分就直接寫進去
```javascript=
// 總公司KPI
const kpiData = [{...}]
// 黑道大哥業績
const broKPI = [{...}]
```
資料準備好後,另外一個不可或缺的元素就是 `svg`,所以:
* 準備好 `<div id="chart"></div>`
* 使用 D3.js 語法時和 jQuery 加上 `$` 類似,前面需要加上 `d3`
* 然後再宣告變數 `box`、`chart`
```javascript=
const box = d3.select('#chart')
let chart
```
這邊不直接把 `#chart` 宣告成 chart 是因為:之後清除圖表時,需要再重新宣告一次,如果直接宣告成 chart 會把整個 `svg` 清除無法繪製
* 宣告完後就可以 `append` 一個 `svg` 到 box
* 並且設定尺寸
```javascript=
const width = 500,
height = 250
chart = box
.append('svg')
.attr({
'width': width,
'height': height
})
```
## 2. 繪製線段
* 使用 `line().x()` 和 `line().y()`,讓座標由 data 長出來
曲線樣式在這篇文章中有詳細的介紹:[SVG D3.js - 繪製線段](https://www.oxxostudio.tw/articles/201411/svg-d3-02-line.html)
```javascript=
const line = d3.svg.line()
.x( (d) => { return d.month } )
.y( (d) => { return d.money } )
.interpolate('basis') // 曲線樣式
```
* 接著用 `append()` 把 chart 加上 `path`
```javascript=
chart.append('path')
.attr({
'stroke': '#E5E5E5',
'd': line(kpiData),
'y': 0
})
chart.append('path')
.attr({
'stroke': 'yellowgreen',
'd': line(broKPI),
'y': 0
})
```
樣式寫法這邊就不另外贅述,可以直接參考 [CodePen](https://codepen.io/kaoru44689/pen/NWxYZYx/)
繪製完後我們會得到一張這樣,長得相當詭異的圖

接下來就要繪製比例尺,把圖表展開到適當尺寸
## 3. 繪製比例尺
* 設定比例 最大值 / 最小值
```javascript=
minX = d3.min( data, (d) => {return d.month} )
maxX = d3.max( data, (d) => {return d.month} )
minY = d3.min( data, (d) => {return d.money} )
maxY = d3.max( data, (d) => {return d.money} )
```
**定義比例**
* **linear.domain([numbers])**:至少要有兩個數字以上,代表原始的資料範圍
* **inear.range([values])**:原本 `domain` 內容陣列的範圍
* **linear.rangeRound(values)**:將 `range` 取整數
* **linear.nice([count])**:`nice` 會根據整體 `range` 的狀況,改變函數的 `domain`,使 `domain` 內的範圍值返回最接近的數
**<font color="red">*</font>**`svg` 的座標系統是越往下數值越大,`rangeRound()` 要反過來才會上下顛倒
```javascript=
const scaleX = d3.scale.linear()
.rangeRound([0,width]) // range 取整數
.domain([ minX, maxX ]) // 原本內容陣列範圍
.nice() // domain 取最接近數
const scaleY = d3.scale.linear()
.rangeRound([height, 0])
.domain([0, maxY])
.nice()
```
* 把需要套用這個 scale 方法的數值加上 scaleX(d.x) 或 scaleY(d.y),數值就會根據 scale 的定義進行縮放,接著就會得到一張符合比例的曲線圖
```javascript=
const line = d3.svg.line()
.x( (d) => { return scaleX(d.month) } )
.y( (d) => { return scaleY(d.money) } )
.interpolate('basis') // 曲線樣式
```

## 4. 座標軸
座標軸與 scale 幾乎是如影隨形地出現,既然是要用來顯示座標,就一定會出現在畫面裡,也因此必須要用 scale 才能夠讓座標軸按照比例大小擺放
* **orient()**:座標軸顯示的位置
* **ticks()**:按照設定的數字進行對應的區隔
* **tickFormat()**:在後方加上 n 或 % ,就會產生有單位的座標軸,不過如果 `tickFormat("")`,就會清空座標軸數值
* **tickSize()**:座標軸上刻度線條的尺寸,數字負值往座標內縮,正值往座標外長出去
* **tickPadding()**:座標軸之間的間距
```javascript=
const axisX = d3.svg.axis()
.scale(scaleX)
.orient('bottom')
.ticks(data.length)
.tickFormat( (d) => { return d + '月'})
.tickSize(-height,0)
.tickPadding(15)
const axisY = d3.svg.axis()
.scale(scaleY)
.orient('left')
.ticks(data.length)
.tickFormat( (d) => { return d + '萬'})
.tickSize(-width,0)
.tickPadding(15)
```
* 在 `svg` 加上 `g`,然後 call axisX 方法與 axisY 方法,就可以產生座標軸
```javascript=
chart.append('g')
.call(axisX)
.attr({
'transform': 'translate(0,' + height + ')'
})
chart.append('g')
.call(axisY)
.attr({
'transform': 'translate(0, 0)'
})
```

以上就是整個圖表繪製的過程,D3.js 的 API 很多,有空再來多試試
## 5. 重新繪製
需要特別說明的是重新繪製的部分:
當執行 render 的時候,如果沒有清除原本的 `svg`,畫面上就會不斷繪製新的圖表疊加上去
所以必須在繪製之前,先把舊的 `svg` 清除,並且重新賦予一個新的 `svg`
這就是為什麼一開始要先預留 box 的原因
```javascript=
function update (){
if ( ... ) {
return
} else {
chart.remove() // 清除 svg
chart = box // 重新宣告
.append('svg')
.attr({
'width': width,
'height': height
})
}
}
```
推薦閱讀:
* [OxxoStudio - SVG D3.js 系列](https://www.oxxostudio.tw/articles/201410/svg-d3-info.html)
* [小白也能輕鬆瞭解的 Vue.js 與 D3.js 系列](https://ithelp.ithome.com.tw/users/20119062/ironman/2242)
---
DEMO:[D3.js 黑道大哥業績表](https://codepen.io/kaoru44689/pen/NWxYZYx/)