react-chartjs-2でクリックした要素を取得する方法について

# 概要

　[React](https://reactjs.org)について細かい説明は必要ないでしょう。今流行りのWebフレームワークです。
　[Chart.js](https://www.chartjs.org/docs/latest/)も有名ですね。JavaScriptで書かれたグラフ描画用ライブラリです。
　[react-chartjs-2](https://github.com/jerairrest/react-chartjs-2)は、Chart.jsの各種グラフ要素を、ReactのComponentとして書ける便利なライブラリです。

　さて、react-chartjs-2で私は散布図を描画していました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/29802/aecc3a84-20f9-f231-0c00-aec8ac9cbc03.png)

　で、Chart.jsは「凡例の表示をクリックした時、デフォルトではそのグラフを非表示にする」挙動をします。
　その際、「どのグラフの凡例をクリックしたか」を調べたいと思いました。
(下の画像では、「Верный+バルジ4」のグラフが非表示になっています)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/29802/344d8f31-506f-0f43-fd40-de941fb27b04.png)

　[Chart.jsのドキュメント](https://www.chartjs.org/docs/latest/configuration/legend.html)によると、凡例をクリックした際の標準の処理が載っています。その際、クリックしたグラフの情報が引数に乗るので、これを読み取れれば目的が達成されるのではないかと考えました。

　しかし、 **そのコード中のthisってどうやって参照するんだよ** 問題が目の前に立ちはだかります。Reactでは「Class ComponentやFunctional Component内にJSXを書いて仮想DOMを表現する」ので、thisの意味が「class本体」や「グローバルのthis」のようになります。つまり、「JSX内のタグの参照(目的とするthis)」をどうやって取得するのかが重要なのです。

# 解決策

　react-chartjs-2において散布図は、`<Scatter>`という独自タグで表現されています。その属性(Props)の中には`ref`というものがあり、これが当該タグの参照となります。参照を保持するには、[React Hooks](https://reactjs.org/docs/hooks-intro.html)的に考えると`useRef`を使うべきです。

```tsx
const OutputGraph: React.FC<{params: IGraphParam[]}> = ({params}) => {
	// <Scatter>への参照情報
	const scatterElement = React.useRef<Scatter>(null);

	(略)

	return (
		// 参照を利用される当該タグ
		<Scatter width={450} height={450} data={graphData}
		ref={scatterElement} onElementsClick={onClickGraph}
		options={{
			elements: { line: { tension: 0 } },
			scales: {
				xAxes: [{ scaleLabel: { display: true, labelString: '最終攻撃力' }, }],
				yAxes: [{ scaleLabel: { display: true, labelString: '大破率(％)' }, }]
			},
			showLines: true
		}} />
	);
}
```

　こうしてオブジェクト自体の参照を取ってしまえば、後はそこから辿っていけば、目的とする情報を取得できます。

```tsx
	const onClickGraph = () => {
		// 参照を取得する
		const scatterObject = scatterElement.current;
		// ここで丁寧にnullチェックしているのは、scatterObjectが↑の段階だと
		// Scatter | null型なので、スマートキャストする必要があることから
		if (scatterObject == null) {
			return;
		}

		// ここで一旦anyにキャストしているのは、scatterObject.chartInstanceは
		// Chart型と判断されているのに、@types/chart.jsの定義によると、
		// なぜかlegendプロパティが生えていなかったから
		// (キャストしないとエディタがエラーを出してコンパイルできない)
		const temp: any = scatterObject.chartInstance;

		// 強引にanyにキャストしたご利益で、legendプロパティ以下を取得できている
		// temp.legend.legendItemsの中身はもっと複雑な型の配列(AoS)だが、
		// 必要な部分だけ読み取ることで記述量を減らしている
		const legendItems: Array<{text: string, hidden: boolean}> = temp.legend.legendItems;

		// 読み取りは終わったので後はデータ加工のみ
		const ignoreNames = legendItems.filter(item => item.hidden).map(item => item.text);

		// useState()から引っ張ってきた上書き用メソッドを利用する
		// (今回の記事とは無関係)
		setGraphData(createGraphData(ignoreNames));
	};
```

　ちなみに`<Scatter>`には`onElementsClick`属性があり、名前からしてクリックしたグラフの情報を引数から渡してくれる……[そんなふうに考えていた時期が私にもありました](https://dic.nicovideo.jp/a/%E3%81%9D%E3%82%93%E3%81%AA%E3%81%B5%E3%81%86%E3%81%AB%E8%80%83%E3%81%88%E3%81%A6%E3%81%84%E3%81%9F%E6%99%82%E6%9C%9F%E3%81%8C%E4%BF%BA%E3%81%AB%E3%82%82%E3%81%82%E3%82%8A%E3%81%BE%E3%81%97%E3%81%9F)。実際にはクリックしても`[]`しか引数の`e: any`に渡されないので、**「クリックした」ということしか読み取れない**というね！　**どうしてこうなった！！！**

# 備考

package.jsonによると、

- react: 16.8.2
- chart.js": 2.7.3
- react-chartjs-2: 2.7.4
- @types/chart.js: 2.7.45

といった環境でした。