Chart.js 2 の積み上げ棒グラフに合計値を表示するプラグインを作成

はじめに

前回の「Chart.js 2 と chartjs-plugin-datalabels を使って積み上げ棒グラフに各値を表示」では、グラフの各値は表示できたものの、合計値の表示はできませんでした。

今回は Chart.js 2.9.3 の積み上げ棒グラフで各合計値を表示する機能をプラグインとして実装したので、そのコードをご紹介します。

このプラグインは積み上げ棒グラフ(縦横)でのみ動作確認しています。
目次
  1. 積み上げ棒グラフの準備
  2. 合計表示プラグイン
  3. 縦グラフで使う場合
  4. おわりに

1. 積み上げ棒グラフの準備

まずはサンプル用の積み上げ棒グラフを準備します。

Chart.js は下記からダウンロードできます。詳細は「Chart.js 2 で積み上げ棒グラフを実装する方法」で紹介していますので、ご参照ください。

/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Sample</title>
  <link rel="stylesheet" href="Chart.min.css">
  <script src="Chart.min.js"></script>
  <script src="sample.js"></script>
</head>
<body>
  <canvas id="myChart" width="640" height="360"></canvas>
</body>
</html>
/sample.js
window.onload = function () {
  const myCanvas = document.getElementById("myChart");

  new Chart(myCanvas, {
    type: "horizontalBar",
    data: {
      labels: ["Store 1", "Store 2", "Store 3"],
      datasets: [
        {
          label: "Orange",
          data: [3, 6, 9],
          backgroundColor: "#F6AD3C",
        },
        {
          label: "Grape",
          data: [12, 8, 4],
          backgroundColor: "#A64A97",
        },
        {
          label: "Melon",
          data: [2, 6, 4],
          backgroundColor: "#AACF52",
        },
      ]
    },
    options: {
      responsive: false,
      scales: {
        xAxes: [{
          stacked: true,
          ticks: {
            // 下記のように固定値ではなく、
            // データに応じて算出するのがいいと思います
            max: 24,
            stepSize: 4,
          }
        }],
        yAxes: [{
          stacked: true
        }]
      },
    }
  });
}

2. 合計表示プラグイン

今回作った合計表示プラグインのコードです。

大まかな処理の流れとしては以下のようになっています。

  1. ラベルのフォントと書式を指定
  2. 各棒グラフの右端に表示されている項目のメタ情報を取得(テキスト表示位置の座標計算に使用)
  3. 各 Store の合計を算出
  4. メタ情報から合計値の表示座標を算出して描画
/chartjs-plugin-total-labels.js
const chartjsPluginTotalLabels = {
  /**
   * 合計値ラベルを描画
   */
  afterDatasetsDraw: function (chart) {
    ctx = chart.ctx;
    // ラベルの書式設定
    ctx = this.setTextStyle(ctx);
    // 各棒最後の項目のメタ情報を取得
    const meta = this.getLastMeta(chart);
    // 各合計値を取得
    const sums = this.calcSums(chart.data.datasets);
    // ラベル描画
    const labels = this.makeLabels(meta, sums);
    labels.forEach(function (label) {
      ctx.fillText(label.value, label.x, label.y);
    });
  },

  /**
   * 各項目の合計を取得
   */
  calcSums: function (datasets) {
    const sums = [];
    datasets.forEach(function (dataset) {
      // 非表示の項目は処理しない
      if (dataset._meta[0].hidden) {
        return;
      }
      dataset.data.forEach(function (value, i) {
        if (typeof sums[i] === "undefined") {
          sums[i] = 0;
        }
        sums[i] += value;
      });
    });

    return sums;
  },

  /**
   * 各棒最後の項目のメタ情報を取得
   * (非表示のものは除く)
   */
  getLastMeta: function (chart) {
    let i = chart.data.datasets.length - 1;
    let meta = undefined;
    do {
      meta = chart.getDatasetMeta(i);
      i--;
    } while (meta.hidden && i >= 0);

    return meta;
  },

  /**
   * ラベル情報を取得
   */
  makeLabels: function (meta, sums) {
    const labels = [];
    sums.forEach(function (sum, i) {
      const lastModel = meta.data[i]._model;
      labels.push({
        value: sum.toString(),
        x: lastModel.x + 5, // + 5 はグラフとラベルの間隔
        y: lastModel.y,
      });
    });

    return labels;
  },

  /**
   * 書式設定
   */
  setTextStyle: function (ctx) {
    const fontSize = 12;
    const fontStyle = "normal";
    const fontFamily = "'Helvetica Neue', Helvetica, Arial, sans-serif";
    ctx.font = Chart.helpers.fontString(fontSize, fontStyle, fontFamily);
    ctx.fillStyle = "#666";
    ctx.textAlign = "left";
    ctx.textBaseline = "middle";

    return ctx;
  }
};

// 上記プラグインの有効化
Chart.plugins.register(chartjsPluginTotalLabels);

下記のように js ファイルを読み込むだけで、合計値が表示されます。

/index.html
<head>
  ...
  <script src="chartjs-plugin-total-labels.js"></script> <!-- ←これを追加 -->
</head>

3. 縦グラフで使う場合

今回のプラグインに少し手を加えれば、縦グラフでも使用することができます。

変更箇所は以下の通りです。

/sample.js
new Chart(myCanvas, {
  type: "bar", // ← 変更
  ...
  options: {
    ...
    scales: {
      xAxes: [{
        stacked: true,
      }],
      yAxes: [{
        stacked: true,
        // ▼ xAxes からこちらに移動
        ticks: {
          max: 24,
          stepSize: 4,
        }
      }]
    },
  }
/chartjs-plugin-total-labels.js
makeLabels: function (meta, sums) {
  ...
  // ▼ 位置変更
  x: lastModel.x,
  y: lastModel.y - 5,
  ...
}

setTextStyle: function (ctx) {
  ...
  // ▼ 揃え変更
  ctx.textAlign = "center";
  ctx.textBaseline = "bottom";
}

4. おわりに

プラグイン作成についてはドキュメントが少なく、console.log を使いながら試行錯誤で組みました。余談ですが、今回作ったプラグインは chartjs-plugin-datalabels と併用することもできました。

システム開発では自動化だけでなく「分析」のニーズも増えてきており、蓄積したデータの最適な可視化は重要な課題の一つになっています。

無料で使える Chart.js は強い味方になりそうです。その際、プラグインを自由自在に使いこなせれば、さらに多くの表現が可能になり、お客様により最適なソリューションが提供可能になります。

今後より多くのプラグインの登場を期待しつつ、僕も使ったり作ったりしながら Chart.js のスキルを高めていこうと思います。