chart.jsで画像をツールチップに追加する方法

# はじめに
JavaScriptのChart.jsを使ってツールチップ（マウスオーバーした時に表示される補足説明）内に画像をはめ込む方法を詳細に書いてある記事がヒットしなかったため，まとめました．できるだけ分かりやすいように書いてみたので、冗長と思う方は以下のリンクなどを参考にしてください。

https://tr.you84815.space/chartjs/configuration/tooltip.html#external-custom-tooltips

今回作成するグラフは以下のようになります．
![ダウンロード.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878870/f8bad71f-dec8-1bda-aef4-a44083415b8d.gif)


# 目次
Qiitaの都合上、目次と見出しではchart.jsをchart jsとしています。

[1.はじめに](#はじめに)
[2.chart jsの読み込み](#chart-jsの読み込み)
[3.html css javascript](#html-css-javascript)
[4.おわりに](#おわりに)

# chart jsの読み込み
### CDNから読み込む
今回は簡易的に実行するためCDNからchart.jsを読み込みます．
以下にリンクを張ります．

[chart.js: ver2.9.0](https://cdnjs.com/libraries/Chart.js/2.9.0)

バージョンによっては今回のプログラムを貼り付けただけでは上手く実行できない場合があります．現時点での最新バージョンである4.3.0ではエラーが出ます．ですが，chart.js内の変数名が違うだけなので自分でそれらを読み取ってカスタムすれば動くようになります．さっさと試したい方は同じバージョンを選択してください．

# html css javascript

以下にHTML，CSS，JavaScriptを提示します．
今回は1つのHTML内にCSSとJavaScriptをまとめて書きます．
Chart.jsではデータの他に**メタデータ**（metadata, グラフには直接描写されないが，ツールチップなどに情報を載せることができるデータ）を載せることができるため，このメタデータに画像URLを載せて表示させます．

### HTML

```html
<html>
    <head>
        <!-- 以下の<script>でchart.jsを読み込む -->
        <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.0/Chart.bundle.js" integrity="sha512-Q9b0myMAI+IqHh5ldQJTh3ZXkVHwlcVF+H6+LF/QU3n0XmQp7w/kjapf+I3os0GhvJcZXCr5dAhhs5AbiZafwA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    </head>
    <body>
        <div>
            <!-- 以下の<canvas>にグラフを描写します．idは"myChart"． -->
            <canvas id="myChart"></canvas>
        </div>
    </body>
</html>
```

### CSS
```css
<style>
    #chartjs-tooltip {
        opacity: 1;
        position: absolute;
        background: rgba(255, 255, 255, 0.7); /* tooltipの背景色 */
        color: white;
        border-radius: 3px;
        -webkit-transition: all .1s ease;
        transition: all .1s ease;
        pointer-events: none;
        -webkit-transform: translate(-50%, 0);
        transform: translate(-50%, 0);
    }

    .chartjs-tooltip-key {
        display: inline-block;
        width: 10px;
        height: 10px;
        margin-right: 10px;
    }
</style>
```

### JavaScript

```html
<script>
    // グラフを描写するcanvasの取得
    var ctx = document.getElementById('myChart').getContext('2d');

    var myPieChart = new Chart(ctx, {
        type: 'pie', // 円グラフ
        data: {
            labels: ["えび", "たけのこ", "ぎょうざ"],
            datasets:[{
                data: [65, 59, 80], // 円グラフのそれぞれのデータ
                metadata: [ // メタデータ．ここに画像情報(URL)を設定する
                    'ebi.png', 
                    'takenoko.png', 
                    'gyoza.png'
                ],
                backgroundColor: [
                    "rgb(54, 162, 235)",
                    "rgb(255, 99, 132)",
                    "rgb(255, 206, 86)",
                ],
            }]
        },
        options: {
            tooltips: {
                enabled: false, // キャンバスのツールチップを無効化
                custom: function(tooltipModel) {
                    // ツールチップ要素
                    var tooltipEl = document.getElementById('chartjs-tooltip');

                    // 最初の表示時に要素を生成。
                    if (!tooltipEl) {
                        tooltipEl = document.createElement('div');
                        tooltipEl.id = 'chartjs-tooltip';
                        tooltipEl.innerHTML = "<table></table>"
                        this._chart.canvas.parentNode.appendChild(tooltipEl);
                    }

                    // ツールチップが無ければ非表示。
                    if (tooltipModel.opacity === 0) {
                        tooltipEl.style.opacity = 0;
                        return;
                    }

                    // キャレット位置をセット。
                    tooltipEl.classList.remove('above', 'below', 'no-transform');
                    if (tooltipModel.yAlign) {
                        tooltipEl.classList.add(tooltipModel.yAlign);
                    } else {
                        tooltipEl.classList.add('no-transform');
                    }

                    function getBody(bodyItem) {
                        return bodyItem.lines;
                    }

                    // メタデータ(画像のURL)の取得.
                    var index = tooltipModel.dataPoints[0].index;
                    var metadata = this._chart.data.datasets[0].metadata[index];

                    // テキストをセット。
                    if (tooltipModel.body) {
                        var titleLines = tooltipModel.title || [];
                        var bodyLines = tooltipModel.body.map(getBody);

                        var innerHtml = '<thead>';

                        titleLines.forEach(function(title) {
                            innerHtml += `<tr><th> ${title} </th></tr>`;
                        });
                        innerHtml += '</thead><tbody>';

                        bodyLines.forEach(function(body, i) {
                            var colors = tooltipModel.labelColors[i];
                            var style = `background:${colors.backgroundColor};`;
                            style += `border-color:${colors.borderColor};`;
                            style += `border-width: 2px;`;
                            var span = `<span class="chartjs-tooltip-key" style=" ${style} "></span>`;
                            // imgタグのsrcにメタデータ(画像URL)をセット
                            innerHtml += `<tr><td> ${span} <span> ${body} </span><br><img style="width:5em;" src= ${metadata} ></td></tr>`;
                        });
                        innerHtml += '</tbody>';

                        var tableRoot = tooltipEl.querySelector('table');
                        tableRoot.innerHTML = innerHtml;
                    }
                    // `this`はツールチップ全体です。
                    var positionY = this._chart.canvas.offsetTop;
                    var positionX = this._chart.canvas.offsetLeft;

                    // 表示、位置、フォントスタイル指定します。
                    tooltipEl.style.opacity = 1;
                    // ツールチップのx位置
                    tooltipEl.style.left = positionX + tooltipModel.caretX + 'px';
                    // ツールチップのy位置
                    tooltipEl.style.top = positionY + tooltipModel.caretY + 'px';
                    tooltipEl.style.fontFamily = tooltipModel._fontFamily;
                    tooltipEl.style.fontSize = tooltipModel.fontSize;
                    tooltipEl.style.fontStyle = tooltipModel._fontStyle;
                    tooltipEl.style.padding = tooltipModel.yPadding + 'px ' + tooltipModel.xPadding + 'px';
                }
            }
        }
    });
</script>
```


# おわりに

以下に上記をまとめたHTMLファイルを提示します．
```html
<html>
    <head>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.0/Chart.bundle.js" integrity="sha512-Q9b0myMAI+IqHh5ldQJTh3ZXkVHwlcVF+H6+LF/QU3n0XmQp7w/kjapf+I3os0GhvJcZXCr5dAhhs5AbiZafwA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    </head>
    <body>
        <div>
            <canvas id="myChart"></canvas>
        </div>
    </body>
</html>

<script>
    var ctx = document.getElementById('myChart').getContext('2d');
    var myPieChart = new Chart(ctx, {
        type: 'pie',
        data: {
            labels: ["えび", "たけのこ", "ぎょうざ"],
            datasets:[{
                data: [65, 59, 80],
                metadata: ['ebi.png', 'takenoko.png', 'gyoza.png'],
                backgroundColor: [
                    "rgb(54, 162, 235)",
                    "rgb(255, 99, 132)",
                    "rgb(255, 206, 86)",
                ],
            }]
        },
        options: {
            tooltips: {
                // キャンバスのツールチップを無効化します
                enabled: false,
                custom: function(tooltipModel) {
                    console.log(tooltipModel)
                    console.log(this._chart)
                    // ツールチップ要素
                    var tooltipEl = document.getElementById('chartjs-tooltip');

                    // 最初の表示時に要素を生成。
                    if (!tooltipEl) {
                        tooltipEl = document.createElement('div');
                        tooltipEl.id = 'chartjs-tooltip';
                        tooltipEl.innerHTML = "<table></table>"
                        this._chart.canvas.parentNode.appendChild(tooltipEl);
                    }

                    // ツールチップが無ければ非表示。
                    if (tooltipModel.opacity === 0) {
                        tooltipEl.style.opacity = 0;
                        return;
                    }

                    // キャレット位置をセット。
                    tooltipEl.classList.remove('above', 'below', 'no-transform');
                    // tooltipEl.classList.add('right'); // カーソルの右側に表示
                    if (tooltipModel.yAlign) {
                        tooltipEl.classList.add(tooltipModel.yAlign);
                    } else {
                        tooltipEl.classList.add('no-transform');
                    }

                    function getBody(bodyItem) {
                        return bodyItem.lines;
                    }

                    // メタデータの取得
                    var index = tooltipModel.dataPoints[0].index;
                    var metadata = this._chart.data.datasets[0].metadata[index];
                    console.log(metadata)

                    // テキストをセット。
                    if (tooltipModel.body) {
                        var titleLines = tooltipModel.title || [];
                        var bodyLines = tooltipModel.body.map(getBody);

                        var innerHtml = '<thead>';

                        titleLines.forEach(function(title) {
                            innerHtml += `<tr><th> ${title} </th></tr>`;
                        });
                        innerHtml += '</thead><tbody>';

                        bodyLines.forEach(function(body, i) {
                            var colors = tooltipModel.labelColors[i];
                            var style = `background:${colors.backgroundColor};`;
                            style += `border-color:${colors.borderColor};`;
                            style += `border-width: 2px;`;
                            var span = `<span class="chartjs-tooltip-key" style=" ${style} "></span>`;
                            innerHtml += `<tr><td> ${span} <span> ${body} </span><br><img style="width:5em;" src= ${metadata} ></td></tr>`;
                        });
                        innerHtml += '</tbody>';

                        var tableRoot = tooltipEl.querySelector('table');
                        tableRoot.innerHTML = innerHtml;
                    }
                    // `this`はツールチップ全体です。
                    var positionY = this._chart.canvas.offsetTop;
                    // var positionY = this._chart.canvas.offsetHeight;
                    var positionX = this._chart.canvas.offsetLeft;
                    // var positionX = this._chart.canvas.offsetWidth;

                    // 表示、位置、フォントスタイル指定します。
                    tooltipEl.style.opacity = 1;
                    tooltipEl.style.left = positionX + tooltipModel.caretX + 'px';
                    tooltipEl.style.top = positionY + tooltipModel.caretY + 'px';
                    tooltipEl.style.fontFamily = tooltipModel._fontFamily;
                    tooltipEl.style.fontSize = tooltipModel.fontSize;
                    tooltipEl.style.fontStyle = tooltipModel._fontStyle;
                    tooltipEl.style.padding = tooltipModel.yPadding + 'px ' + tooltipModel.xPadding + 'px';
                }
            }
        }
    });
</script>

<style>
    #chartjs-tooltip {
        opacity: 1;
        position: absolute;
        background: rgba(255, 255, 255, 0.7);
        color: white;
        border-radius: 3px;
        -webkit-transition: all .1s ease;
        transition: all .1s ease;
        pointer-events: none;
        -webkit-transform: translate(-50%, 0);
        transform: translate(-50%, 0);
    }

    .chartjs-tooltip-key {
        display: inline-block;
        width: 10px;
        height: 10px;
        margin-right: 10px;
    }
</style>
```

おわり