chartjsを使って、ドーナツチャートを作った時のはなし

chartjsを使って、ドーナツチャートを作った時のはなし

chartjsでドーナツチャートを作った。

tooltipの表示調整やpluginsを使った追加情報表示を実装したので、残しておく。

確認環境

  • macOS 13.2
  • docker desktop 4.16.2
  • vue 3.2.45
  • chartjs 2.9.4
  • vue-chartjs 3.5.1

ドーナツチャートの作り方

canvasタグにドーナツチャートを描画する

data, options, pluginsをそれぞれ宣言して、new Chartに()に渡す

template

<div style="position: relative; width: 80vw; height: 50vh">
    <canvas id="canvas" />
</div>

script

import { onMounted } from "vue";
import { Chart } from "chart.js";
...
onMounted(() => {
  const ctx = document.getElementById("canvas");
  if (ctx)
    new Chart(ctx, {
      type: "doughnut",
      data,
      options,
      plugins,
    });
});

data

色・値・ラベルをそれぞれ指定する

const data = {
  datasets: [
    {
      backgroundColor: ["#d3f8e2", "#e4c1f9", "#f694c1", "#ede7b1"],
      data: [46, 43, 7, 4],
    },
  ],
  labels: ["January", "February", "March", "April"],
};

options

const options = {
  legend: { display: true },
  maintainAspectRatio: false, // サイズ変更時に元のキャンバスのアスペクト比を維持
  cutoutPercentage: 50, // ドーナツの太さ(細 > 太)
  tooltips: {
    enabled: true,
    intersect: false, // マウスの位置が要素と交差する場合にのみ表示
    filter: (context, TooltipItem) => // 値が 0 より大きいものだけを表示
      TooltipItem.datasets[0].data[context.index] > 0,
    callbacks: {
      // January: 84% のように表示
      label: (context, TooltipItem) => {
        return (
          TooltipItem.labels[context.index] +
          ":" +
          TooltipItem.datasets[0].data[context.index] +
          "%"
        );
      },
    },
  },
  layout: {
    // グラフ外枠のパッディング指定
    padding: {
      left: 20,
      right: 20,
    },
  },
};

maintainAspectRatioについて

maintainAspectRatio をtrue(デフォルト値)にしておくと、グラフが小さく描画されてしまう。

そのため、「maintainAspectRatio=false」にして、
canvasの親要素(div)に「position: relative; width: 80vw; height: 50vh」をつけて大きさを調整した。

cutoutPercentageについて

ドーナツの太さが変更できる
50はデフォルト値、0にすると円グラフになる、100にすると描画されない

tooltipsについて

intersectをfalseにしておく方が操作性が高く感じる

filterやcallbacks.labelで表示制御や表示文字列調整が可能

layoutについて

paddingだけ指定ができて、チャートを横並びにした時などで便利

px指定

plugins

それぞれの値の常時表示と中央に合計値を描画する

afterDatasetsDrawやafterDrawが、チャートを描画したときやマウスオーバー時に発火される
何度も呼ばれるので、シンプルな処理にすべき

chart.data.datasetsをループして、fillTextで文字を表示させている

中央に表示する位置は、数値でしか調整ができなくて、描画文字数分をpadding変数で調整している

const plugins = [
  {
    afterDatasetsDraw(chart) {
      const ctx = chart.ctx;
      chart.data.datasets.forEach((dataset, i) => {
        let meta = chart.getDatasetMeta(i);
        if (!meta.hidden) {
          meta.data.forEach(function (element, index) {
            let fontSize = 12;
            let fontStyle = "normal";
            let fontFamily = "Helvetica Neue";
            ctx.fillStyle = "#000";
            ctx.font = Chart.helpers.fontString(
              fontSize,
              fontStyle,
              fontFamily
            );
            ctx.textAlign = "center";
            ctx.textBaseline = "middle";

            let dataString = dataset.data[index] + "%";
            let padding = 0;
            let position = element.tooltipPosition();
            // グラフ値を常に表示
            ctx.fillText(
              dataString,
              position.x,
              position.y + fontSize / 2 - padding
            );
          });
        }
      });

      let title = "合計100%";
      let fontSize = 21;
      let padding = -10;
      ctx.fillStyle = "#000";
      ctx.font = Chart.helpers.fontString(fontSize, "normal", "sans-serif");
      ctx.textAlign = "center";
      ctx.textBaseline = "middle";
      // グラフの中心に合計を表示
      ctx.fillText(
        title,
        chart.chart.width / 2,
        chart.chart.height / 2 - padding
      );
    },
  },
];

注意点

わざわざpluginsで表示しなくても、cssで出せば良いと最初は思ったのだが、

chartJSで表示するツールチップが重なった時に、cssで表示した文字の方がz-indexが高くなってしまい、読みにくくなってしまった。

そのため、cssでの表示から変更した。

参考

Web技術カテゴリの最新記事