---
# System prepended metadata

title: D3.js 資料圖表

---

# 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/)
繪製完後我們會得到一張這樣，長得相當詭異的圖
![](https://i.imgur.com/ZDh9h2Z.png =500x)
接下來就要繪製比例尺，把圖表展開到適當尺寸


## 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') // 曲線樣式
```
![](https://i.imgur.com/CaFkyw5.png =500x)

## 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)'
    })
```
![](https://i.imgur.com/VHhQUz1.png =500x)
以上就是整個圖表繪製的過程，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/)