Chart.jsでグラフを更新するサンプル（ボタンをクリック）

# はじめに

Chart.jsでボタンをクリックしたときに、グラフを更新するサンプルを作ります。
最後にコピペで試せる、サンプルコードを載せます。

![0902.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/445427/94a61ee7-41f2-3044-9379-90df0e96b7fe.gif)


# グラフを描画する

## Chart.jsを読み込む

CDNを利用してChart.jsを読み込みます。
https://cdnjs.com/libraries/Chart.js

```javascript
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.bundle.js"></script>
```

## htmlを作成
グラフ描画エリアとグラフ更新ボタンを用意します。

```html
<!-- グラフ描画エリア -->
<div style="width:100%">
  <canvas id="canvas"></canvas>
</div>

<!-- グラフ更新ボタン -->
<button type="button" id="btn">グラフを更新</button>
```

- グラフを描画するためのcanvasに```id="canvas"```を指定。
- グラフを更新するためのボタンに```id="btn"```を指定。


## グラフ描画
ページ読み込み時と、ボタンクリック時にグラフを描画したいので、処理を関数にまとめておき、Chartインスタンスは```window.myChart```で、グローバル変数として生成します。
```drawChart();```で関数を呼び出して、グラフを描画します。

```javascript:drawChart()
// グラフ描画処理
function drawChart() {
  var ctx = document.getElementById('canvas').getContext('2d');
  window.myChart = new Chart(ctx, { // インスタンスをグローバル変数で生成
    type: 'bar',
    data: { // ラベルとデータセット
      labels: ['January','February','March','April','May','June','July','August','September','October','November','December'],
      datasets: [{
          data: [4, 15, 6, 3, 18, 19, 11, 9, 18, 7, 4, 8], // グラフデータ
          backgroundColor: 'rgba(0, 134, 197, 0.7)', // 棒の塗りつぶし色
          borderColor: 'rgba(0, 134, 197, 1)', // 棒の枠線の色
          borderWidth: 1, // 枠線の太さ
      }],
    },
    options: {
      legend: {
        display: false, // 凡例を非表示
      }
    }
  });
}

drawChart(); // グラフ描画処理を呼び出す
```


## グラフデータの値をランダムに生成
グラフデータはランダムに生成して、ボタンのクリック前後で、グラフが更新されているかを確認できるようにします。

```javascript
// グラフデータの値をランダムに生成
var chartVal = []; // グラフデータ（描画するデータ）

function getRandom() {
  chartVal = []; // グラフデータを初期化
  var length = 12;
  for (i = 0; i < length; i++) {
    chartVal.push(Math.floor(Math.random() * 20));
  }
}

getRandom(); // グラフデータのランダム化処理を呼び出す
```

作成済みのグラフ描画関数、```drawChart();```の```datasets: []```で、設定している```data```を```chartVal```に変更します。（グラフデータは変数を受け取るよう変更）


# グラフ更新 （ボタンをクリックで再描画）
続いて、ボタンクリック時の処理は、以下の順で書いていきます。

- ページ読み込み時に描画したグラフを破棄
- グラフデータにランダムな値を格納
- グラフを再描画

**※ グラフを再描画する前に、すでに表示しているグラフの破棄**が必要です。

## グラフを破棄 （.destroy()の使い方）
一度グラフを描画した後に、リロード以外で**同じcanvasタグにグラフを再描画すると、再描画前のグラフが表示される**現象が発生します。

この現象は、一度```new Chart()```でChartインスタンスを生成をしている状態から、再びグラフの描画を行うと、二度目の```new Chart()```で、同じインスタンスが重複して生成されるため発生します。

グラフにマウスオーバーしたときに、グラフがガタガタしたり、チカチカして見える場合はこれが原因です。

解決方法として下記を参照しました。
https://www.chartjs.org/docs/latest/developers/api.html#destroy
> .destroy()
> Use this to destroy any chart instances that are created. This will clean up any references stored to the chart object within Chart.js, along with any associated event listeners attached by Chart.js. This must be called before the canvas is reused for a new chart.

```javascript
// Destroys a specific chart instance
myLineChart.destroy();
```

これによるとChart.jsで、同じcanvasを再利用してグラフを再描画する場合に```.destroy()```を呼び出す必要がある。
```.destroy()```を呼び出して、すでに生成しているchartインスタンスを破棄する。
とあります。

実際の使い方としては、以下のように書いてしまえばOKです。

```javascript
// ボタンをクリックしたら、グラフを再描画
document.getElementById('btn').onclick = function() {
  // すでにグラフ（インスタンス）が生成されている場合は、グラフを破棄する
  if (myChart) {
    myChart.destroy();
  }

  getRandom(); // グラフデータにランダムな値を格納
  drawChart(); // グラフを再描画
}
```

if文でChartインスタンスが存在していたら```.destroy()```でインスタンスを破棄して、```drawChart();```でグラフを作り直す順序になります。


# 完成（サンプルコード）
以上、ここまでのコードをひとつのファイルにまとめて完成です。

```html

<!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Chart.jsでボタンをクリックしてグラフを更新するサンプル</title>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.bundle.js"></script>
</head>
<body>
<!-- グラフ描画エリア -->
<div style="width:100%">
  <canvas id="canvas"></canvas>
</div>

<!-- グラフ更新ボタン -->
<button type="button" id="btn">グラフを更新</button>

<script>
var chartVal = []; // グラフデータ（描画するデータ）

// ページ読み込み時にグラフを描画
getRandom(); // グラフデータにランダムな値を格納
drawChart(); // グラフ描画処理を呼び出す


// ボタンをクリックしたら、グラフを再描画
document.getElementById('btn').onclick = function() {
  // すでにグラフ（インスタンス）が生成されている場合は、グラフを破棄する
  if (myChart) {
    myChart.destroy();
  }

  getRandom(); // グラフデータにランダムな値を格納
  drawChart(); // グラフを再描画
}


// グラフデータをランダムに生成
function getRandom() {
  chartVal = []; // 配列を初期化
  var length = 12;
  for (i = 0; i < length; i++) {
    chartVal.push(Math.floor(Math.random() * 20));
  }
}


// グラフ描画処理
function drawChart() {
  var ctx = document.getElementById('canvas').getContext('2d');
  window.myChart = new Chart(ctx, { // インスタンスをグローバル変数で生成
    type: 'bar',
    data: { // ラベルとデータセット
      labels: ['January','February','March','April','May','June','July','August','September','October','November','December'],
      datasets: [{
          data: chartVal, // グラフデータ
          backgroundColor: 'rgb(0, 134, 197, 0.7)', // 棒の塗りつぶし色
          borderColor: 'rgba(0, 134, 197, 1)', // 棒の枠線の色
          borderWidth: 1, // 枠線の太さ
      }],
    },
    options: {
      legend: {
        display: false, // 凡例を非表示
      }
    }
  });
}
</script>
</body>
</html>
```

# グラフを破棄（別パターン）
グラフ描画の関数内に、インスタンス破棄の処理を組み込む場合は、以下のように書きます。

```javascript
if (typeof myChart !== 'undefined' && myChart) {
  myChart.destroy();
}
```

ポイントは、判定条件にインスタンスが```undefined```ではない場合を入れること。
この場合は、ボタンクリック時の```document.getElementById('btn').onclick = function(){...}```内で行っている```if (myChart) {...}```は不要です。


# おわりに
実際の利用シーンとしては、グラフデータをランダムにしている部分を、Ajaxなどの非同期通信でjsonやCSVデータを読み込む処理に置き換えて使うことになるかと思います。