WebDesign Dackel

d3.jsでレスポンシブな円グラフを描く

d3.jsでレスポンシブな円グラフを描く

Hatena0
Google+0
Pocket0
Feedly0

タイトルの通り、データのビジュアル化にとっても便利なd3.jsを使ってレスポンシブな円グラフを描いてみたいと思います。
ただ円グラフを書くだけでは物足りない感があるので、最終的にアニメーションを付けるところまでやってみたいと思います。
それぞれのセクション毎にサンプルを用意しているので、実際の動作はそちらで確認してみてください。
またサンプル用のファイルはGitHubに上げましたので興味のある方はそちらで確認できます。

サンプルファイル

※正しくは、「レスポンシブな」というよりも単純に「リサイズに対応した」といった方が正しいかもしれませんが、そこら辺はあまり気にしないでいきます。

基本的な円グラフの描画

まずは基本的な円グラフを描画させてみたいと思います。

サンプル1

HTML

<svg id="chart"></svg>

単純にsvgを配置しているだけです。特に特別なことはしていません。

JavaScript

// 表示サイズを設定
var size = {
  width: 600,
  height:600
};

// 円グラフの表示データ
var data = [
  60,
  30,
  10
];

// d3用の変数
var svg   = d3.select("#chart"),
    pie   = d3.layout.pie().value(function(d){ return d; }),
    arc   = d3.svg.arc().innerRadius(0).outerRadius(size.width / 2);

// グループの作成
var g = svg.selectAll(".arc")
  .data(pie(data))
  .enter()
  .append("g")
    .attr("transform", "translate(" + (size.width / 2) + "," + (size.height / 2) + ")")
    .attr("class", "arc");

// 円弧の作成
g.append("path")
  .attr("d", arc)
  .attr("stroke", "white");

基本的な描画に関しては、下記の記事が大変参考になりました。

D3.js – 円グラフのポイント | Developers.IO

リサイズに対応する

基本的なところは抑えたので、次はウィンドウのリサイズに対応してレスポンシブに対応してみます。
下記のサンプルを開きブラウザのウィンドウをリサイズすると、先ほどとは違って円グラフのサイズもどんどん変わっていくかと思います。

サンプル2

CSS

HTMLは先ほどと同じものを使いまわしますが、CSSの追加が必要です。

svg {
  width:100%;
}

svgの横幅をいっぱいに広げておきます。高さは横幅に応じて動的に変更するのでここでは設定しません。

JavaScript

// サイズを設定
// ウィンドウサイズによって可変する
var size = {
  width : 600,
  height: 600
};

// 円グラフの表示データ
var data = [
  60,
  30,
  10
];

// d3用の変数
var win   = d3.select(window), //←リサイズイベントの設定に使用します
    svg   = d3.select("#chart"),
    pie   = d3.layout.pie().value(function(d){ return d; }),
    arc   = d3.svg.arc().innerRadius(0);


// グラフの描画
// 描画処理に徹して、サイズに関する情報はupdate()内で調整する。
function render(){

  // グループの作成
  var g = svg.selectAll(".arc")
    .data(pie(data))
    .enter()
    .append("g")
      .attr("class", "arc");

  // 円弧の作成
  g.append("path").attr("stroke", "white");
}


// グラフのサイズを更新
function update(){

  // 自身のサイズを取得する
  size.width = parseInt(svg.style("width"));
  size.height = parseInt(svg.style("height")); //←取得はしていますが、使用していません...

  // 円グラフの外径を更新
  arc.outerRadius(size.width / 2);

  // 取得したサイズを元に拡大・縮小させる
  svg
    .attr("width", size.width)
    .attr("height", size.width);

  // それぞれのグループの位置を調整
  var g = svg.selectAll(".arc")
    .attr("transform", "translate(" + (size.width / 2) + "," + (size.width / 2) + ")");

  // パスのサイズを調整
  g.selectAll("path").attr("d", arc);
}


// 初期化
render();
update();
win.on("resize", update); // ウィンドウのリサイズイベントにハンドラを設定

それぞれの処理についてはコメントアウトに書いていますが、基本的な描画と比べて大きく違う点は主に下記です。

  • サイズを設定していた変数sizeに対して、svg自身のサイズを代入している
  • 描画とサイズの設定を分けた
  • windowに対してリサイズイベントの設定

ウィンドウがリサイズされる度にupdate()が呼び出されてサイズを更新していくという流れになっています。

データをテキストで乗せてみる

実際に円グラフを描く場合、データの内容を表示するのが一般的かと思いますので試していきます。

サンプル3

HTMLCSSは共通ですので、早速JSを変更していきます。ただ、ほとんど内容は同じなので違う箇所だけピックアップします。

JavaScript

まずは描画を担当するrender()内でtext要素を追加します。

function render(){
  // ...省略

  // 円弧の作成
  g.append("path").attr("stroke", "white");

  // ↓追加
  // データの表示
  g.append("text")
    .attr("dy", ".35em")
    .style("text-anchor", "middle")
    .text(function(d){ return d.data; });
}

textの配置が出来たので、次にupdate()内で表示位置を調整します。

function update(){
  // ...省略

  // パスのサイズを調整
  g.selectAll("path").attr("d", arc);

  // ↓追加
  // テキストの位置を再調整
  g.selectAll("text").attr("transform", function(d){ return "translate(" + arc.centroid(d) + ")"; });
}

ここまででレスポンシブ(リサイズ)に対応した最低限実用的なグラフを作ることが出来ました!

ただ、少し物足りない感じがするので次はアニメーションをつけていきたいと思います。

アニメーションを付ける

アニメーションを付けたサンプルは下記です。ページの表示後グラフがクルッと回って表示されるかと思います。

サンプル4

JavaScript

では早速アニメーションを付けるための処理を書いていきたいのですが、その前にアニメーションが完了しているかどうかのフラグを設けておきます。理由は後ほど。

// d3用の変数
var win   = d3.select(window), //←リサイズイベントの設定に使用します
    svg   = d3.select("#chart"),
    pie   = d3.layout.pie().value(function(d){ return d; }),
    arc   = d3.svg.arc().innerRadius(0);

// アニメーション終了の判定フラグ
var isAnimated = false;

ここではisAnimatedなる変数を用意しました。アニメーションが完了した段階でtrueが入る想定です。
次に実際のアニメーション部分を設定していきます。

// グラフのアニメーション設定
function animate(){
  var g = svg.selectAll(".arc"),
      length = data.length,
      i = 0;

  g.selectAll("path")
    .transition()
    .ease("cubic-out")
    .delay(500)
    .duration(1000)
    .attrTween("d", function(d){
      var interpolate = d3.interpolate(
        {startAngle: 0, endAngle: 0},
        {startAngle: d.startAngle, endAngle: d.endAngle}
      );
      return function(t){
        return arc(interpolate(t));
      };
    })
    .each("end", function(transition, callback){
      i++;
      isAnimated = i === length; //最後の要素の時だけtrue
    });
}

transition()からがアニメーションの設定で、500ms経ってから1000msかけてアニメーションするように指定しています。
ease()で設定出来るイージングは下記が参考になりました。

D3.js Easing Checker

animate()の最後の方にある.each("end"~の部分は各要素のアニメーションが終わったら実行されるコールバックです。
一番最後の要素がアニメーションを終えた段階で、先ほど用意したisAnimatedフラグにtrueが入ります。
で、このフラグはどこで使うんだ?というと、サイズを更新するupdate()内で使用します。

function update(){
  // ... 省略

  // パスのサイズを調整
  // アニメーションが終了していない場合はサイズを設定しないように
  if( isAnimated ){
    g.selectAll("path").attr("d", arc);
  }

  // テキストの位置を再調整
  g.selectAll("text").attr("transform", function(d){ return "translate(" + arc.centroid(d) + ")"; });
}

アニメーション前や途中でリサイズが発生した際に、アニメーションが飛んでしまうのを防いでいます。

最後に初期化部分でanimate()を実行して完成です。

// 初期化
render();
update();
animate(); //←追加
win.on("resize", update); // ウィンドウのリサイズイベントにハンドラを設定

円グラフのアニメーションは下記が非常に参考になりました!

円グラフを角度0から360度までアニメーションさせながら描く (SVG使用) | D3.js 例文辞典

【番外編】スタイリッシュな見た目に!

やっぱり、JSでグラフを描くなら見た目もいい感じにしておきたいですね。

サンプル5

こちら興味があればサンプルファイルをご確認ください。

まとめ

リサイズに対応していこうとすると、情報が少ない気がしたので同じような事をしたいと思っている方の参考になればと思います。
次回は棒グラフのレスポンシブ対応に挑戦してみたいななんて思っています。

棒グラフではなく、折れ線グラフを書きました!

d3.jsでレスポンシブな折れ線グラフを描く