# 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/)