[Chart.js]グラフのクリックを検知したい

## やりたいこと

- 下図のようなグラフでラベル部分(Januaryなど)をクリックすると詳細ページに遷移するといった動きを実現したかった

![スクリーンショット 2020-09-01 23.33.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/760956a3-4533-385d-e302-e7fa608e2e53.png)

## 結論

- きれいに実装するのは無理だった
- グラフのクリックを検知してクリックしたのがどの項目か取得することはできた

## やり方

### Chart.jsをそのまま使うパターン

- こんなグラフがあったとする

```html
<html>
  <head>
    <meta charset="utf-8" />
    <title>chartjs sample</title>
  </head>
  <canvas id="myChart"></canvas>
  <script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script>
  <script>
    const ctx = document.getElementById('myChart').getContext('2d');
    const chart = new Chart(ctx, {
      type: 'horizontalBar',
      data: {
        labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
        datasets: [
          {
            label: 'My First dataset',
            data: [3, 10, 5, 2, 20, 30, 45],
          },
        ],
      },
    });
  </script>
</html>
```

- 見た目はこんな感じ
    - 棒グラフの部分にホバーするとツールチップが出る

![スクリーンショット 2020-09-01 23.58.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/10989365-50c3-5e3f-70d1-794f47623158.png)


- クリックイベントを検知

```html
<html>
  <head>
    <meta charset="utf-8" />
    <title>chartjs sample</title>
  </head>
  <canvas id="myChart"></canvas>
  <script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script>
  <script>
    const ctx = document.getElementById('myChart').getContext('2d');
    const chart = new Chart(ctx, {
      type: 'horizontalBar',
      data: {
        labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
        datasets: [
          {
            label: 'My First dataset',
            data: [3, 10, 5, 2, 20, 30, 45],
          },
        ],
      },
    });
    // ここから追加
    document.getElementById('myChart').addEventListener('click', e => {
      const elements = chart.getElementAtEvent(e);
      if (elements.length) {
        alert(`${elements[0]._index}: ${elements[0]._model.label}`);
      }
    });
  </script>
</html>
```

- 最後の方に処理を追加しました
- chart.jsのcanvas全体に対するクリックを検知して`getElementAtEvent`を使うことでいろいろ情報がとれます
- `_index`で何番目の項目`_model.label`でラベルの値などとることができます
    - このindexなどを使うことで冒頭に書いたやりたいことで言う遷移先などを判別することができます

![スクリーンショット 2020-09-01 23.59.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/a1c19ce1-9dc6-b985-5e75-2149e40f273d.png)

- これではラベルのクリックは検知できず棒グラフの部分だけ検知できます
- ただ、軸の最小値を1以上にするとラベルのクリックも反応するようになりました
    - この辺はたまたまこれでいける要件ならって感じですね

```html
<html>
  <head>
    <meta charset="utf-8" />
    <title>chartjs sample</title>
  </head>
  <canvas id="myChart"></canvas>
  <script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script>
  <script>
    const ctx = document.getElementById('myChart').getContext('2d');
    const chart = new Chart(ctx, {
      type: 'horizontalBar',
      data: {
        labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
        datasets: [
          {
            label: 'My First dataset',
            data: [3, 10, 5, 2, 20, 30, 45],
          },
        ],
      },
      // 追加した部分
      options: {
        scales: {
          xAxes: [
            {
              ticks: {
                min: 1,
              },
            },
          ],
        },
      },
    });
    document.getElementById('myChart').addEventListener('click', e => {
      const elements = chart.getElementAtEvent(e);
      if (elements.length) {
        alert(`${elements[0]._index}: ${elements[0]._model.label}`);
      }
    });
  </script>
</html>
```

## Reactを使ったパターン

- react-chartjs-2を使った場合のやり方です
- 以下のコマンドでライブラリを事前に追加しておく

```sh
yarn add chart.js react-chartjs-2
```

- まずはグラフを出すだけだとこんな感じ

```jsx:App.js
import React from 'react';
import { HorizontalBar } from 'react-chartjs-2';

function App() {
  const data = {
    labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
    datasets: [
      {
        label: 'My First dataset',
        data: [3, 10, 5, 2, 20, 30, 45],
      },
    ],
  };

  const onElementsClick = elements => {
    console.log(elements);
    if (elements.length) {
        alert(`${elements[0]._index}: ${elements[0]._model.label}`);
    }
  };

  return (
    <HorizontalBar data={data} onElementsClick={onElementsClick} />
  );
}

export default App;
```

- 見た目はReactじゃない時と同じ
- クリックの検知を入れるとこんな感じ

```jsx:App.js
import React from 'react';
import { HorizontalBar } from 'react-chartjs-2';

function App() {
  const data = {
    labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
    datasets: [
      {
        label: 'My First dataset',
        data: [3, 10, 5, 2, 20, 30, 45],
      },
    ],
  };
  const onElementsClick = elements => {
    console.log('onElementsClick');
    console.log(elements);
    if (elements.length) {
      alert(`${elements[0]._index}: ${elements[0]._model.label}`);
    }
  };
  return <HorizontalBar data={data} onElementsClick={onElementsClick} />
}

export default App;
```

- これでどの項目をクリックしたか検知することができる


## 最後に

- Chart.jsがcanvasで描画されるのでこまかいことやろうとすると大変ですね
- 最低限やりたいことはできそう