2020年9月15日 星期二

基於 Canvas 的 Chart.js 圖表函式庫

最近在一本 2014 的 jQuery 書上看到一個 jQuery 繪圖函式庫 jqPlot.js, 稍微閱讀之後發現蠻好用的, 但在 2018 年就因為缺乏開發人員投入而停止開發了, 參考 :

https://groups.google.com/g/jqplot-dev/c/tDkY2z0q2t4

作者建議使用者改用 Chart.js 這個函式庫, 此乃以 Canvas 為基礎的開源 Javascript 圖表函式庫, 據說 Angular Chart 也是以 Chart.js 為基礎建構的, 相關網站參考 :

https://www.chartjs.org/
https://github.com/chartjs/Chart.js

教學文件與範例參考 :

https://www.chartjs.org/docs/latest/
https://www.chartjs.org/samples/latest/
[Day 30]Chart.js - 輕鬆完成資料視覺化
[十分鐘學習] Chart.js - 圖表繪製

Chart.js 最新版為 v2.9.3, 可從 GitHub 下載, 原始檔約 420 KB, 壓縮檔約 170 KB :

https://github.com/chartjs/Chart.js/releases/tag/v2.9.3

將 Chart.js 放在專案目錄下就可以馬上用它來繪製 line (折線圖), bar (柱狀圖), radar (雷達圖), polarArea, pie (圓餅圖), doughnut, bubble (泡泡圖) 等七種圖表, 網頁模板如下:

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <meta charset="utf-8">
    <meta http-equiv="cache-control" content="no-cache">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script src="js/Chart.min.js"></script>
  </head>
  <body>
    <script>
      //your codes
    </script>
  </body>
</html>

或者也可以使用 CDN, 例如 cdn.js.com 或 jsdelivr.com :

https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js
https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js

模板如下 :

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <meta charset="utf-8">
    <meta http-equiv="cache-control" content="no-cache">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js"></script>
  </head>
  <body>
    <script>
      //your codes
    </script>
  </body>
</html>

Chart.js 是基於 Canvas 的圖表函式庫, 其 HTML 部分使用 canvas 元素做為畫布, 依照官網教學文件中的範例, 此 canvas 元素可用 width 與 height 屬性設定畫布大小 :

<canvas id="myChart" width="400p" height="300"></canvas>

接著呼叫 canvas 元素的 getContext('2d') 方法取得畫布的 Context 物件 :

var ctx=document.getElementById('myChart').getContext('2d');

最後用 new Chart(ctx, options) 建立圖表物件 :

var myChart=new Chart(ctx, options)

其中第一參數 ctx 為上面呼叫 getContext() 所傳回之 Context 物件, 第二參數 options 為選項物件, 其 labels 屬性可設定 X 軸刻度標籤; dataset 屬性可設定資料集之標題 label 與資料 data 等屬性. 下面參考官網教學範例改寫如下 :


測試 1 : 繪製柱狀圖 (1) [看原始碼]

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Chart.js test</title>
  <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js"></script>
</head>
<body>
  <canvas id="myChart" width="400" height="300"></canvas>
  <script>
    var ctx=document.getElementById('myChart').getContext('2d');
    var myChart=new Chart(ctx, {
      type: 'bar',
      data: {
        labels: ['一月', '二月', '三月'],
        datasets: [{
          label: '銷售業績(百萬)',
          data: [60, 49, 72]
          }]
        }
      });
  </script>
</body>
</html>

此例顯示 1~3 月氏銷售業績柱狀圖, 但是奇怪的是, canvas 中的 width 與 height 屬性設定沒有作用, 畫布佔據了整個頁面, 我找到下面這篇文章 :

https://stackoverflow.com/questions/37621020/setting-width-and-height

試了全部方法只有其中編號 48 回應的做法有效, 亦即在 canvas 外再包覆一層 div 元素, 然後設定此 div 之尺寸即可 :

<div style="width: 400px; height: 300px">
  <canvas id="myChart"></canvas>
</div>


測試 2 : 繪製柱狀圖 (2) : 設定畫布尺寸 [看原始碼]

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Chart.js test</title>
  <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js"></script>
</head>
<body>
  <div style="width: 400px; height: 300px">
    <canvas id="myChart"></canvas>
  </div>
  <script>
    var ctx=document.getElementById('myChart').getContext('2d');
    var myChart=new Chart(ctx, {
      type: 'bar',
      data: {
        labels: ['一月', '二月', '三月'],
        datasets: [{
          label: '銷售業績(百萬)',
          data: [60, 49, 72]
          }]
        }
      });
  </script>
</body>
</html>

結果如下, 畫布大小與所指定之尺寸相同 :




另外一篇文章也提供了一個解決辦法 : 


其中編號 2 的回應是先取得 canvas 的父元素 (即 div), 然後設定其 width 與 height 屬性 :

ctx.canvas.parentNode.style.width="300px";
ctx.canvas.parentNode.style.height="500px";

事實上官方教學文件也有提到這個方法 : 


例如 : 



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Chart.js test</title>
  <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js"></script>
</head>
<body>
  <div>
    <canvas id="myChart"></canvas>
  </div>
  <script>
    var ctx=document.getElementById('myChart').getContext('2d');
    ctx.canvas.parentNode.style.width="400px";
    ctx.canvas.parentNode.style.height="300px";
    var myChart=new Chart(ctx, {
      type: 'bar',
      data: {
        labels: ['一月', '二月', '三月'],
        datasets: [{
          label: '銷售業績(百萬)',
          data: [60, 49, 72]
          }]
        }
      });
  </script>
</body>
</html>

此例 div 元素沒有指定大小, 而是用 parentNode 屬性取得 canvas 元素之父元素 (div) 後設定其尺寸, 結果與上面範例 2 相同. 

如果配合使用 jQuery, 可以用選擇器取得 canvas 物件後直接當作 Context 物件傳給 new Chart() , 不可呼叫 getContext('2d'), 因為 jQuery 物件並無此方法. 如果要取得 Context 物件, 應先取出 jQuery 物件中的 DOM 元素, 這有兩個方法 :

$('#myChart')[0].getContext("2d");

或者 :

$('#myChart').get(0).getContext("2d");

用索引 [0] 或呼叫 get(0) 就能從 jQuery 物件中取出 DOM 物件了, 例如 :


測試 4 : 繪製柱狀圖 (4) : 搭配 jQuery [看原始碼]

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Chart.js test</title>
  <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js"></script>
</head>
<body>
  <div style="width: 400px; height: 300px">
    <canvas id="myChart"></canvas>
  </div>
  <script>
    var ctx=$('#myChart');
    //var ctx=$('#myChart')[0].getContext("2d");
    var myChart=new Chart(ctx, {
      type: 'bar',
      data: {
        labels: ['一月', '二月', '三月'],
        datasets: [{
          label: '銷售業績(百萬)',
          data: [60, 49, 72]
          }]
        }
      });
  </script>
</body>
</html>

此例中的 ctx 可以是 canvas 元素之 jQuery 物件, 也可以是 DOM 物件.

上面範例中的圖形預設背景與外框均為灰色, 可以用下列屬性設定 :
  • backgroundColor : 設定背景色 
  • borderWith : 設定邊框寬度 (單位 px) 
  • borderColor : 設定邊框顏色 
backgroundColor 與 borderColor 的值為陣列, 其元素為對應各資料之顏色字串 "#FF0000", 例如 :


測試 5 : 繪製柱狀圖 (5) : 設定框邊與背景色 [看原始碼]

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Chart.js test</title>
  <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js"></script>
</head>
<body>
  <div style="width: 400px; height: 300px">
    <canvas id="myChart"></canvas>
  </div>
  <script>
    var ctx=$('#myChart');
    var myChart=new Chart(ctx, {
      type: 'bar',
      data: {
        labels: ['一月', '二月', '三月'],
        datasets: [{
          label: '銷售業績(百萬)',
          data: [60, 49, 72],
          backgroundColor: [
            "#FF0000",
            "#00FF00",
            "#0000FF"
          ],
          borderColor: [
            "#000000",
            "#000000",
            "#000000"
          ],
          borderWidth: 1
          }]
        }
      });
  </script>
</body>
</html>

結果如下 :




如果需要設定透明度則可呼叫 rgba(R, G, B, A) 函數, 其參數依序為紅 R, 綠 G, 藍 B, 與透明度 A, 顏色值範圍 0~255, 透明度範圍 0~1. 例如 :


測試 6 : 繪製柱狀圖 (6) : 設定框邊與背景色 [看原始碼]

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Chart.js test</title>
  <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js"></script>
</head>
<body>
  <div style="width: 400px; height: 300px">
    <canvas id="myChart"></canvas>
  </div>
  <script>
    var ctx=$('#myChart');
    var myChart=new Chart(ctx, {
      type: 'bar',
      data: {
        labels: ['一月', '二月', '三月'],
        datasets: [{
          label: '銷售業績(百萬)',
          data: [60, 49, 72],
          backgroundColor: [
            'rgba(255, 99, 132, 0.2)',
            'rgba(54, 162, 235, 0.2)',
            'rgba(255, 206, 86, 0.2)'
          ],
          borderColor: [
            'rgba(255,99,132,1)',
            'rgba(54, 162, 235, 1)',
            'rgba(255, 206, 86, 1)',
            'rgba(75, 192, 192, 1)'
          ],
          borderWidth: 1
          }]
        }
      });
  </script>
</body>
</html>

結果如下 :




可見經過設定, 外觀比預設要好看多了, 但比起 jqPlot 會自動派顏色來說, 這似乎有點麻煩.

如果要繪製寬高比為 1:1 的圖形, 可以將 canvas 的 width 與 height 都設為 1, 然後用外層包覆的 div 控制寬或高 : 

  <div style="width:400px;">
    <canvas id="myChart" width="1" height="1"></canvas>
  </div>

例如 : 



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Chart.js test</title>
  <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js"></script>
</head>
<body>
  <div style="width:400px;">
    <canvas id="myChart" width="1" height="1"></canvas>
  </div>
  <script>
    var ctx=document.getElementById('myChart').getContext('2d');
    //ctx.canvas.parentNode.style.width="300px";
    var myChart=new Chart(ctx, {
      type: 'bar',
      data: {
        labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
        datasets: [{
           label: '# of Votes',
           data: [12, 19, 3, 5, 2, 3],
           backgroundColor: [
             'rgba(255, 99, 132, 0.2)',
             'rgba(54, 162, 235, 0.2)',
             'rgba(255, 206, 86, 0.2)',
             'rgba(75, 192, 192, 0.2)',
             'rgba(153, 102, 255, 0.2)',
             'rgba(255, 159, 64, 0.2)'
             ],
           borderColor: [
             'rgba(255, 99, 132, 1)',
             'rgba(54, 162, 235, 1)',
             'rgba(255, 206, 86, 1)',
             'rgba(75, 192, 192, 1)',
             'rgba(153, 102, 255, 1)',
             'rgba(255, 159, 64, 1)'
             ],
           borderWidth: 1
           }]
        },
      options: {
        scales: {
          yAxes: [{
            ticks: {
            beginAtZero: true
            }
          }]
        }
      }
    });
  </script>
</body>
</html>

因為畫布寬高比已經設定為 1:1, 所以只須設定 width 或 height 其中一個即可, 此例是在 canvas 父元素 div 中設定 width (也可以在程式中用 parentNode 設定), 結果如下 : 




只要用 rgba() 善加設定, Chart.js 的繪圖效果真的很棒. 

沒有留言 :