Chart.js 2 のドーナツチャートで中央に値や文字列を表示するプラグインを作成

はじめに

書籍「データビジュアライゼーションの教科書」(2019年 藤 俊久仁、渡部 良一)の中で、達成率や進捗率などの場合はドーナツチャートが有効であると書かれています。これらは円グラフで描かれることがよくありますが、確かにドーナツチャートの方がスッキリして見やすい印象があります。

Chart.js 2 にもドーナツチャートを描画する機能はありますが、中央に値などの文字列を表示するにはプラグインとして実装する必要があるようです。

そこで今日は、Chart.js 2 のドーナツチャートで中央に値などのテキストを表示するプラグインをつくってみたので、そのコードをご紹介します。

今回使用したのは Chart.js 2.9.3 です。

目次
  1. 下準備
  2. シンプルに <canvas> に描画
  3. HTML タグ出力 & CSS で装飾
  4. タイトルも中央部に表示
  5. おわりに

1. 下準備

今回サンプルで使用した HTML と JS です。

js/plugin.js の内容は自作プラグインの説明時にご紹介します。
/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ドーナツチャート</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
  <script src="js/app.js" defer></script>
  <script src="js/plugin.js" defer></script>
</head>
<body>
  <canvas id="chart-area" width="320" height="240"></canvas>
</body>
</html>
/js/app.js
(function() {
  var blue = 'rgb(54, 162, 235)';
  var gray = 'rgb(99, 99, 99)';

  var data = {
    datasets: [{
      data: [87, 13],
      backgroundColor: [blue, gray],
    }],
  };

  // グラフオプション
  var options = {
    // グラフの太さ(中央部分を何%切り取るか)
    cutoutPercentage: 65,
    // 凡例を表示しない
    legend: { display: false },
    // 自動サイズ変更をしない
    responsive: false,
    // タイトル
    title: {
      display: true,
      fontSize: 16,
      text: 'Progress',
    },
    // マウスオーバー時に情報を表示しない
    tooltips: { enabled: false },
  };

  // グラフ描画
  var ctx = document.getElementById('chart-area').getContext('2d');
  new Chart(ctx, {
    type: 'doughnut',
    data: data,
    options: options
  });
})();

CDN の URL は下記で確認できます。

chart.js のグラフオプションは下記公式ドキュメントが役立ちます。各チャートのページや、左メニューの「Configureation」の各ページに説明があります。

2. シンプルに <canvas> に描画

<canvas> タグに描画する場合はプラグインだけで実装可能です。

「var labelY」 の箇所では、ラベルの Y 座標がグラフタイトル分だけ中心から下方にずれるため、その分の減算をしています。chart.chartArea.top はドーナツチャート最上部が、上から何 px にあるかが格納されています。

/js/plugin.js
var chartJsPluginCenterLabel = {
  afterDatasetsDraw: function (chart) {
    // ラベルの X 座標と Y 座標
    var canvas = chart.ctx.canvas;
    var labelX = canvas.clientWidth / 2;
    var labelY = Math.round((canvas.clientHeight + chart.chartArea.top) / 2);
    // ラベルの値
    var value = chart.data.datasets[0].data[0] + '%';
    // ラベル描画
    var ctx = this.setTextStyle(chart.ctx);
    ctx.fillText(value, labelX, labelY);
  },

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

    return ctx;
  }
};

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

部分的なフォントサイズや文字色の変更、改行も可能ではあるのですが、座標計算が多くなり手間がかかりそうです。

それらを実装したい場合には、次に紹介するタグで出力方法が良さそうです。

3. HTML タグ出力 & CSS で装飾

ラベルのスタイルを CSS で制御できるように HTML タグで出力するプラグインも作りました。

plugin.js の「ラベル上部の padding」は先の例と同様に、グラフ上部にあるタイトル分のズレ調整です。

/js/plugin.js
var chartJsPluginCenterLabel = {
  labelShown: false,

  afterRender: function (chart) {
    // afterRender は何度も実行されるので、2回目以降は処理しない
    if (this.labelShown) {
      return;
    }
    this.labelShown = true;
    // ラベルの HTML
    var value = chart.data.datasets[0].data[0];
    var labelBox = document.createElement('div');
    labelBox.classList.add('label-box');
    labelBox.innerHTML = '<div class="label">'
      + value
      + '<span class="per">%</span>'
      + '</div>';
    // ラベル上部の padding
    // (タイトルを表示しない場合は不要です)
    var paddingTop = Math.round(chart.chartArea.top);
    labelBox.setAttribute('style', 'padding-top:' + paddingTop + 'px');
    // ラベル描画
    var canvas = chart.ctx.canvas;
    canvas.parentNode.insertBefore(labelBox, canvas.nextElementSibling);
  },
};

// 上記プラグインの有効化
Chart.plugins.register(chartJsPluginCenterLabel);
/index.html
<head>
  ...
  <title>ドーナツチャート</title>
  <link rel="stylesheet" href="css/style.css"> <!-- ←これを追加 -->
  ...
</head>
/css/style.css
.label-box {
  /* box-sizing はプラグインで padding-top を付与したときに height を変えないようにするために付けています */
  box-sizing: border-box;
  height: 240px;
  width: 320px;
  /* 縦横中央揃え */
  display: flex;
  align-items: center;
  justify-content: center;
  /* グラフ上に配置 */
  margin-top: -240px;
}

/* 値 */
.label-box .label {
  color: #636363;
  font-size: 50px;
}
/* パーセント */
.label-box .label .per {
  font-size: 20px;
}

4. タイトルも中央部に表示

先に紹介したサンプルコードではタイトルがチャート上部にありますが、これも中央に表示することで情報がまとまり見やすくなります。

<canvas> でやるのは大変そうだったので HTML タグで出力するプラグインをカスタムして、実装してみました。

/js/app.js
// グラフオプションの title 指定を削除しただけです
(function() {
  var blue = 'rgb(54, 162, 235)';
  var gray = 'rgb(99, 99, 99)';

  var data = {
    datasets: [{
      data: [87, 13],
      backgroundColor: [blue, gray],
    }],
  };

  // グラフオプション
  var options = {
    // グラフの太さ(中央部分を何%切り取るか)
    cutoutPercentage: 65,
    // 凡例を表示しない
    legend: { display: false },
    // 自動サイズ変更をしない
    responsive: false,
    // マウスオーバー時に情報を表示しない
    tooltips: { enabled: false },
  };

  // グラフ描画
  var ctx = document.getElementById('chart-area').getContext('2d');
  new Chart(ctx, {
    type: 'doughnut',
    data: data,
    options: options
  });
})();
/js/plugin.js
var chartJsPluginCenterLabel = {
  labelShown: false,

  afterRender: function (chart) {
    // afterRender は何度も実行されるので、2回目以降は処理しない
    if (this.labelShown) {
      return;
    }
    this.labelShown = true;
    // ラベルの HTML
    var value = chart.data.datasets[0].data[0];
    var labelBox = document.createElement('div');
    labelBox.classList.add('label-box');
    labelBox.innerHTML = '<div class="label">'
      + '<div class="title">Progress</div>'
      + '<div class="value">'
      + value
      + '<span class="per">%</span>'
      + '</div>';
      + '</div>';
    // ラベル描画
    var canvas = chart.ctx.canvas;
    canvas.parentNode.insertBefore(labelBox, canvas.nextElementSibling);
  },
};

// 上記プラグインの有効化
Chart.plugins.register(chartJsPluginCenterLabel);
/css/style.css
.label-box {
  /* box-sizing はプラグインで padding-top を付与したときに height を変えないようにするために付けています */
  box-sizing: border-box;
  height: 240px;
  width: 320px;
  /* 縦横中央揃え */
  display: flex;
  align-items: center;
  justify-content: center;
  /* グラフ上に配置 */
  margin-top: -240px;
}

/* タイトル、値 */
.label-box .label {
  color: #636363;
  text-align: center;
}
.label-box .label .title {
  font-size: 20px;
  letter-spacing: .05em;
  margin-bottom: 3px;
  padding-top: 13px;
}
.label-box .label .value {
  font-size: 60px;
  line-height: 1em;
}
.label-box .label .value .per {
  font-size: 20px;
}

5. おわりに

個人的には見やすさと開発効率を考慮すると HTML 出力の方がいいのかなと思います。

複数のグラフを表示する場合には改造が必要だと思います。ラッパー関数を作るのもよさそうですね。

今回ご紹介したプラグインは機能は限定的ですが、その分コード量が少なく構造もシンプルなので、用途に応じてカスタムしていただき、見やすく保守しやすいドーナツチャートを実装していただければと思っています。