WonderPlanet Tech Blog

アドバンストテクノロジー部やCTO室のメンバーを中心に最新の技術情報を発信しています。

D3.js v5で積み上げ折れ線グラフ(表示切り替え機能付き)を作成する方法

こんにちはAT部の@y-matsushitaです。
前回はD3.js v5でツールチップ付きのグラフを作成してみました。
今回は少し発展させて積み上げ折れ線グラフを作成してみたのでご紹介いたします。
また値が全体に対してどれぐらいの比率なのか視認できるように比率での表示に切り替えられるようにもしましたので併せてご紹介いたします。
本投稿ではD3.jsのv5で動作するコードを記載していきます。

前回の記事はこちら

tech.wonderpla.net

積み上げ折れ線グラフ

以下のページに作成した積み上げ折れ線グラフのデモとコードを載せています。
前回同様コードが長いのでコード全体は以下のデモページからご参照ください。
前回と異なる要所だけピックアップして説明していきます。

デモとソースコード

f:id:y-matsushita:20180606165606p:plain:w600

データの複製

読み込んだデータを複製し合計値を表示するデータと比率を表示するデータに分けます。
比率用のデータに対して行った変更を元のデータに影響が出ないようにしたいので、一旦JSONを経由して複製を行います。
合計で表示するデータは取得時のまま保管、比率で表示するデータは後ほど0〜1で全体に占める割合に変換します。

stacked_line_chart.js 23~28行目

d3.json("data.json").then(
  function(data) {

    // 比率表示用にデータを複製
    var showData = data;
    var ratioData = JSON.parse(JSON.stringify(data));

データを比率に変換

ここで複製しておいたデータを通常の数値データから比率のデータに変換します。
まずは階層全ての値の合計値を求め、そこから各階層ごとに占める比率を算出しデータを入れ替えます。

stacked_line_chart.js 45~54行目

ratioData.forEach(function(d, i) {
  // 階層全ての合計値を求める
  for (j = 0, t = 0; j < keys.length; ++j){
    t += parseInt(d[keys[j]]);
  }
  // 合計値における比率を求める
  for (j = 0; j < keys.length; ++j){
    ratioData[i][keys[j]] = d[keys[j]] / t;
  }
});

積み上げ折れ線グラフの描画

d3.area()で描画する面エリアの指定を行います。
xに日付、y0に下側の座標、y1に上側の座標が入ります。
実際に値が入っていくのはその下のd3.stack()からで、layer作成のループ時にキーごとに下の階層から順番にデータが入っていきます。

stacked_line_chart.js 65~78行目

var area = d3.area()
    .x(function(d, i) {
      return x(d.data.day); })
    .y0(function(d) { return y(d[0]); })
    .y1(function(d) { return y(d[1]); });

var layer = g.selectAll(".layer")
    .data(d3.stack().keys(keys)(data))
    .enter().append("g")
    .attr("class", "layer")
    .append("path")
    .attr("class", "area")
    .style("fill", function(d) { return z(d.key); })
    .attr("d", area);

ボタン押下時のデータの差し替え

ボタンの取得を行い、表示非表示の切り替えフラグを初期化します。

stacked_line_chart.js 210~213行目

// 合計⇄比率変換用ボタン
var changeBtn = d3.select("#changeBtn");
// 比率表示状態かどうか
var isRatio = false;

取得したボタンが押下されたら現在の表示状態のフラグisRatioを反転させ現在の表示状態を切り替えます。

stacked_line_chart.js 215~216行目

changeBtn.on("click", function() {
  isRatio = !isRatio;

比率で表示(isRatioがtrue)であれば全体における各層の比率で表示します。
表示させるデータは先ほど変換したratioDataに差し替えます。
ratioDataに入っている値の範囲が0〜1のため、domainの範囲も0〜1に変更します。
またy軸の目盛り表記がこのままだと合計の表示のままなため、%表記の軸に変更します。
もし合計で表示(isRatioがfalse)だった場合は上記の処理の逆で比率のデータを合計のデータに差し替えてdomainとy軸の目盛り表記も元に戻します。

stacked_line_chart.js 217~252行目

if(isRatio){
  // 比率表示
  y.domain([0, 1]);
  showData = ratioData;

  d3.selectAll(".axis--y")
  .call(
    d3.axisLeft(y)
    .ticks(6)
    .tickSizeInner(-cWidth)
    .tickFormat(function(d) { return d*100 + "%"; })
  );
  d3.select(".axis-title")
  .text("比率");

}else{
  // 合計表示
  y.domain([0, d3.max(data, function(d) {
    for (i = 0, t = 0; i < keys.length; ++i){
      t += parseInt(d[keys[i]]);
    }
     return t;
   }
  )]).nice();
  showData = data;

  d3.selectAll(".axis--y")
  .call(
    d3.axisLeft(y)
    .ticks(6)
    .tickSizeInner(-cWidth)
    .tickFormat(function(d) { return d/1000 + "k"; })
  );
  d3.select(".axis-title")
  .text("合計");
}

描画の切り替えアニメーション

先ほど差し替えたデータで再度積み上げ折れ線グラフの内容を描画し直します。
描画はtransition()duration(500)で徐々に変化させることができます。
この場合数値はミリ秒なので0.5秒で変化させています。

stacked_line_chart.js 254~261行目

d3.selectAll(".layer")
    .data(d3.stack().keys(keys)(showData))
    .select("path")
    .attr("class", "area")
    .transition()
    .duration(500)
    .ease(d3.easeLinear)
    .attr("d", area);

最後に

今回は前回に続きD3.jsのv5で積み上げ折れ線グラフを作成しました。
前回の記事と重複する箇所は省略しているため、もし気になる箇所があれば前回の記事も参考にしてみてください。 tech.wonderpla.net