【JavaScript】ポップアップをChart.jsのラベルのホバーで表示させる方法

JavaScript

JavaScriptのChart.jsはcanvasタグを使ってグラフを表示させるプラグインです。単純なグラフ作成であれば比較的簡単に導入できるため大変便利ですが、ちょっと複雑なことをしようとすると工夫が必要になります。

今回はX軸のラベルにカーソルがホバーした時にポップアップを表示させる機能を実装したため、その方法をご紹介します。

完成系

以下が今回の完成系です。JSのコードを表示するとラベルのホバーでツールチップが表示されるかと思います。

See the Pen Untitled by marukaty (@marukaty) on CodePen.

事前準備

chart.jsとannotationを読み込んでおいてください。今回は v3.0.1を使っています。
annotationの公式はこちらです。

chartjs-plugin-annotation
Annotations for Chart.js
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation@3.0.1/dist/chartjs-plugin-annotation.min.js"></script>

Nodeなどの場合はこちら

import Chart from 'chart.js/auto';
import annotationPlugin from 'chartjs-plugin-annotation';
Chart.register(annotationPlugin);

コード

Chart.jsにはデフォルトでラベルのクリックイベントなどをキャッチする機能がないので、annotationでラベルの上にイベントをキャッチできる領域を被せる感じになります。

<div class="chart_wrapper">
  <canvas id="myChart"></canvas>
  <div class="tooltips" data-target="2024/3/1">
    <p>Value: 12</p>
  </div>
  <div class="tooltips" data-target="2024/3/2">
    <p>Value: 19</p>
  </div>
  <div class="tooltips" data-target="2024/3/3">
    <p>Value: 3</p>
  </div>
  <div class="tooltips" data-target="2024/3/4">
    <p>Value: 5</p>
  </div>
  <div class="tooltips" data-target="2024/3/5">
    <p>Value: 2</p>
  </div>
  <div class="tooltips" data-target="2024/3/6">
    <p>Value: 3</p>
  </div>
</div>
const ctx = document.getElementById('myChart');
const dateArray = ['2024/3/1', '2024/3/2', '2024/3/3', '2024/3/4', '2024/3/5', '2024/3/6'];
const tooltips = document.querySelectorAll('.tooltips');
const annotationsItems = [];
for (let i = 0; i < dateArray.length; i++) {
  if (dateArray[i] <= 0 ) { continue; }
  let annotationsItem = {
                          type: 'label',
                          width: 50,
                          height: 20,
                          xValue: dateArray[i],
                          xScaleID: 'x',
                          yValue: 0,
                          yAdjust: 14,
                          yScaleID: 'y',
                          enter: (e) => {
                            if (tooltips.length > 0) {
                              tooltips.forEach(function (tooltip) {
                                if (tooltip.dataset.target === e.element.options.xValue) {
                                   tooltip.classList.add('active');
                                   const rect = e.chart.canvas.getBoundingClientRect();
                                   tooltip.style.left = `${rect.left + e.element.x - 30}px`;
                                   tooltip.style.top = `${rect.top + e.element.y - 60}px`;
                                }
                              });
                            }
                          },
                          leave: (e) => {
                            if (tooltips.length > 0) {
                              tooltips.forEach(function (tooltip) {
                                if (tooltip.dataset.target === e.element.options.xValue) {
                                  tooltip.classList.remove('active');
                                }
                              });
                            }
                          }
                        }
	annotationsItems.push(annotationsItem);
}

new Chart(ctx, {
  type: 'line',
  data: {
    labels: dateArray,
    datasets: [{
      label: '# of Votes',
      data: [12, 19, 3, 5, 2, 3],
      borderWidth: 1
    }]
  },
  options: {
    plugins: {
      annotation: {
        clip: false,
        annotations: annotationsItems
      } 
    },
    scales: {
      y: {
        beginAtZero: true
      }
    }
  }
});
.chart_wrapper {
  position: relative;
}
.tooltips {
  display: none;
  position: absolute;
  margin: 0;
  padding: 0 6px;
  height: fit-content;
  background: skyblue;
  border-radius: 6px;
  font-wite: 600;
  color: white;
  z-index: 10000;
}

.tooltips.active {
  display: block;
}

コード解説

HTMLの方に表示させるツールチップを記述しておき、CSSで非表示にしています。それをJSで表示用クラスのON/OFFで切り替えています。

HTML

以下の部分がツールチップです。

<div class="tooltips" data-target="2024/3/1">
  <p>Value: 12</p>
</div>

JavaScript

annotationの基本は以下の部分になります。 options/plugins/annotaion の中に設定を入れていきます。さらに annotations[] の中にannotation一つ一つの設定を入れていきます。

options: {
  plugins: {
    annotation: {
      clip: false,
      annotations: annotationsItems
    } 
  },

今回の個別の設定部分がこちら。ちょっと長いですが次で一つ一つ解説していきます。

const annotationsItems = [];
for (let i = 0; i < dateArray.length; i++) {
  let annotationsItem = {
                          type: 'label',
                          width: 50,
                          height: 20,
                          xValue: dateArray[i],
                          xScaleID: 'x',
                          yValue: 0,
                          yAdjust: 14,
                          yScaleID: 'y',
                          enter: (e) => {
                            if (tooltips.length > 0) {
                              tooltips.forEach(function (tooltip) {
                                if (tooltip.dataset.target === e.element.options.xValue) {
                                   tooltip.classList.add('active');
                                   const rect = e.chart.canvas.getBoundingClientRect();
                                   tooltip.style.left = `${rect.left + e.element.x - 30}px`;
                                   tooltip.style.top = `${rect.top + e.element.y - 60}px`;
                                }
                              });
                            }
                          },
                          leave: (e) => {
                            if (tooltips.length > 0) {
                              tooltips.forEach(function (tooltip) {
                                if (tooltip.dataset.target === e.element.options.xValue) {
                                  tooltip.classList.remove('active');
                                }
                              });
                            }
                          }
                        }
	annotationsItems.push(annotationsItem);
}

annotationの設定

  1. 空の配列を定義し、ラベルの数だけ設定をして配列に追加していく
const annotationsItems = [];
for (let i = 0; i < dateArray.length; i++) {
	...
	annotationsItems.push(annotationsItem);
}
  1. 設定値
let annotationsItem = {
                        type: 'label',
                        xValue: dateArray[i],
                        xScaleID: 'x',
                        yValue: 0,
                        yAdjust: 14,
                        yScaleID: 'y',

type:
annotationの種類を指定するもので、今回は’label’を使うことでラベルとしてannotationを表示しています

xValue:
ラベルを表示するX軸上の値を指定します。dateArray[i]とすることで、事前に定義済みのラベル配列から目的の値を指定しています。

xScaleID:
xValueの値がどのスケールIDに関連づけられているかを指定しています。スケールIDはChart.jsの方で軸を複数使用する際の識別子で、デフォルトではX軸には’x’が割り当てられています。

yValue:
ラベルを表示するY軸上の値を指定します。ここを0にすることでグラフの一番下にannotationを表示できます。

yAdjust:
Y軸方向の位置調整です。正の値は下に、負の値は上にラベルを調整するので、今回は0のラインから14ピクセル下にずらしています。

yScaleID:
yValueの値がどのスケールIDに関連づけられているかを指定しています。

  1. ポップアップ表示の設定
enter: (e) => {
  if (tooltips.length > 0) {
    tooltips.forEach(function (tooltip) {
      if (tooltip.dataset.target === e.element.options.xValue) {
         tooltip.classList.add('active');
         const rect = e.chart.canvas.getBoundingClientRect();
         tooltip.style.left = `${rect.left + e.element.x - 30}px`;
         tooltip.style.top = `${rect.top + e.element.y - 60}px`;
      }
    });
  }
},
leave: (e) => {
  if (tooltips.length > 0) {
    tooltips.forEach(function (tooltip) {
      if (tooltip.dataset.target === e.element.options.xValue) {
        tooltip.classList.remove('active');
      }
    });
  }
}

enter:
マウスオーバー時の設定です。以前のバージョンでは onMouseenteronMouseover に当たるものです。

leave:
マウスアウト時の設定です。以前のバージョンでは onMouseleaveonMouseout に当たるものです。

それぞれの中でツールチップをループし、 data-target 属性に設定している日付と、ラベルの日付が一致したら表示用の .active クラスを着脱する様にしています。

まとめ

いかがでしたでしょうか?最新の公式情報が英語しかないので英語苦手な自分は欲しい情報に辿り着くのに少々苦労しました。。。

今回のポイントとしては

  • annotationを使用して正規のX軸ラベルの上にホバーイベント用のラベルを被せた
  • このラベルに対して enter leave のオプションでマウスオーバーとマウスアウトの挙動を設定した

以上となります。

コメント

タイトルとURLをコピーしました