Chart.js でインタラクティブなチャートを作る方法

ウェブの画面にグラフを書きたい時に便利なJSライブラリ[Chart.js](http://www.chartjs.org/)は、とても便利です。
単にチャートを書くだけでなく、チャートをクリックしたりマウスオーバーした際に、そのチャート上のデータを読み取る
ことができます。

マウスのクリックした時に、その時のカーソルの下にあるデータの取り方を紹介します。

サンプルコードはこちら。

* [Sample code for how to handle an event which a data point is clicked.](https://gist.github.com/satoryu/faadf34032907b983eda3365f2ac2daa)

## マウスオーバーは簡単にできる。けど…

グラフ上の点をマウスオーバーした際の点に対応するデータを取るのは、`options.tooltips.callback` を使うと簡単にできます。
例えば、マウスオーバーした時に表示されるツールチップのタイトルを現在時刻にするには、
下のように設定する。

```javascript
new Chart(ctx, 
  type: 'scatter',
  dataset: [{
    data: [
      { x: 1, y: 2},
      { x: 2, y: 2},
      { x: 3, y: 3}
    ]
  }],
  options: {
    tooltips: {
      callbacks: {
        title: function(tooltipItems, data) {
          return new Date().toLocaleTimeString();
        }
      }
    }
  }
);
```

コールバック関数を設定できる種類やそれに渡される引数については、[ドキュメント](http://www.chartjs.org/docs/latest/configuration/tooltip.html#tooltip-callbacks)を参照してください。

しかし、これはあくまでもtooltipを描画するためのものであり、データを取るものではない。また、現在高まっている衝動は、クリックした時に対応したデータを取りたいという欲求である。

## `getElementAtEvent` を使おう

クリック時のイベントを元に、そのイベントが発生した場所にあるデータを返してくれるAPIが`getElementAtEvent` だ。
グラフを描画しているCanvas上のクリックイベントを拾うために、イベントハンドラを登録し、その内部で呼び出す。例えば、こんな感じ。

```javascript
var myCanvas = document.getElementById('myCanvas');
var ctx = myCanvas.getContext('2d');
var myChart = new Chart(ctx, ...);

myCanvas.addEventListener('click', function(event) {
  let item = myChart.getElementAtEvent(event);

  if (item.length == 0) {
    console.log('no element found.')
    return;
  }

  item = item[0];
  let data = item._chart.config.data.datasets[item._datasetIndex].data[item._index];
  alert(`Clicked at (${data.x}, ${data.y})`);   
});
```

`getElementAtEvent`は、与えたイベントに対応するデータが無ければ空の配列を返します。ですので、データの存在の有無は、その配列の長さで判断できます。

ということで、インタラクティブなチャートを作れそうですね。

## 同一座標にある複数のデータを取得するには、

複数のデータを取得できる`getElementsAtEvent`を試してみたのですが、関係ないデータが取れてしまう問題に直面しました[^1]。

それとは別に、イベントのハンドリングを`hover`と`onClick`または`onHover`で設定できます。

`Chart`のコンストラクタに渡す`options`に、以下のように渡します。

```javascript
options: {
    hover: {
        mode: 'point'
    },
    onClick: function(event, elements) {
        console.log(`Found ${elements.length} elements`);

        elements.forEach(function(element) {
            let point = myChart.data.datasets[element._datasetIndex].data[element._index];
            console.log(point);
        })
    }
```

`hover` は、一見hover時のみの挙動を決めるように見えますが、マウスイベントに対しての挙動を決める設定です。後に設定するcallback関数をどのイベントで呼び出すかは、別途、[`options.events`プロパティ](http://www.chartjs.org/docs/latest/general/interactions/events.html#events)で設定します。デフォルトは全てのイベントで呼びだされます。
`options.hover.mode`は、マウスカーソルを基準にどういった点を選択の対象にするかを設定するオプションです。デフォルト値の`nearest`は最もカーソルに近い一点を選択します。今回は同一の座標点全てを取得したいので、`point`を指定しています。他にもオプションがあるので、ドキュメントを参照ください。

今回はクリック時のマウスカーソル下にあるデータ点を取りたいので、`onClick`を実装します。`onClick`で指定した関数に渡される引数は、発生したイベントと、そこに関連するデータ点の配列の2つです。データ点の配列は、先の`getElementAtEvent`の返り値と同じ`ChartElement`のオブジェクトの配列です。`ChartElement`のオブジェクトは、何番目のdatasetのデータなのかを指す`_datasetIndex`、さらにその中の何番目のデータ点なのかを表す`_index`を持っているので、それらを用いて、マウスカーソル下のデータ点を取得できます。

## 参考

* [Chart.js | Open source HTML5 Charts for your website](http://www.chartjs.org/)
* [Chart.js API · GitBook](http://www.chartjs.org/docs/latest/developers/api.html)
* [Tooltip · GitBook](http://www.chartjs.org/docs/latest/configuration/tooltip.html#tooltip-callbacks)
* [Events · GitBook](http://www.chartjs.org/docs/latest/general/interactions/events.html#events)
* [Sample code for how to handle an event which a data point is clicked.](https://gist.github.com/satoryu/faadf34032907b983eda3365f2ac2daa)

[^1]: 原因がまだわかっていませんが、とりあえずGitHubのChartjsのリポジトリにレポートしようと思います。