明日から使えるD3.js

@tenten0213

明日から使えるD3.js

D3.jsとは?

  • 任意のデータをDOMと結合させ、データ駆動によりHTMLやSVGを作成することができるJavaScriptライブラリ
  • http://d3js.org/
D3.js

特徴

  • 修正BSDライセンス(商用利用可能)
  • 豊富なサンプル、ドキュメント
  • インタラクティブなグラフの作成や、アニメーションを付けたい場合にオススメ
  • jQueryライクなセレクタ(ドットによるメソッドチェイン)
    • jQueryを使い慣れている人には取っ付き易い

前提知識

  • HTML5
    • DOM構造
  • CSS3
  • JavaScript
    • jQuery
  • SVG
    まったく知らないという状態じゃなければ大丈夫!だと思います…

Galleryを見てみよう!

gallery

例えば、Line Chart

Line Chart

コードの説明

Line Chartを描くにはこんな感じです。

// marginの設定
var margin = {top: 20, right: 20, bottom: 30, left: 50},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

// 日付の表示形式の設定
var parseDate = d3.time.format("%d-%b-%y").parse;

コードの説明

// X軸、Y軸の設定
var x = d3.time.scale()
  .range([0, width]);

var y = d3.scale.linear()
  .range([height, 0]);

var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");

var yAxis = d3.svg.axis()
  .scale(y)
  .orient("left");

コードの説明

// Line要素の生成
var line = d3.svg.line()
  .x(function(d) { return x(d.date); })
  .y(function(d) { return y(d.close); });

// SVG領域の生成
var svg = d3.select("body").append("svg")
  .attr("width", width + margin.left + margin.right)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform",
    "translate(" + margin.left + "," + margin.top + ")");

コードの説明

// データ取得
d3.tsv("data.tsv", function(error, data) {
  data.forEach(function(d) {
    d.date = parseDate(d.date);
    d.close = +d.close;
  });

  // X軸、Y軸の範囲指定
  x.domain(d3.extent(data, function(d) { return d.date; }));
  y.domain(d3.extent(data, function(d) { return d.close; }));

  // X軸の描画
  svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

コードの説明

  // Y軸の描画
  svg.append("g")
    .attr("class", "y axis")
    .call(yAxis)
  .append("text")
    .attr("transform", "rotate(-90)")
    .attr("y", 6)
    .attr("dy", ".71em")
    .style("text-anchor", "end")
    .text("Price ($)");

  // Lineの描画
  svg.append("path")
    .datum(data)
    .attr("class", "line")
    .attr("d", line);
});

意外と大変

ムズカシイ(´・ω・`)

グラフを描くための大体の流れや、コード量を感じ取ってもらえばと思います。
もっと簡単にグラフを書きたい方は、最後にオススメのライブラリを紹介しますのでそちらをご参照ください。

D3.jsを始めよう

Galleryにあるサンプルは綺麗で参考にはなるのですが、
複雑です。
まずは基礎から見ていきましょう。

D3.jsを利用するには

  • D3.jsのサイトからzipファイルを取得
  • Github から取得
  • 以下を記述
  

DOMの操作 - セレクション

  • D3.jsはjQueryの様なセレクタを提供している
  • 操作対象の任意のノードセットをセレクションと呼ぶ
  • メソッドチェインが可能
  • 通常のDOM APIを用いた操作と異なり容易
  • jQueryだとこんな感じ
    • ulタグの子要素にあるhogeクラスの要素を選択
$("ul").children(".hoge")

DOM APIだと

var paragraphs = document.getElementsByTagName("p");

for (var i = 0; i < paragraphs.length; i++) {
  var paragraph = paragraphs.item(i);
  paragraph.style.setProperty("color", "white", null);
}

http://d3js.org/

D3セレクタだと

d3.selectAll("p").style("color", "white");

http://d3js.org/

楽ちんですね!

要素の選択(select)

Hello! D3.js\(^o^)/

  

Hello! D3.js\(^o^)/

  d3.select('#TEXT1').style('background-color', 'black');

全ての要素を選択する(selectAll)

Hello! D3.js\(^o^)/

(๑・ิω・ิ๑)yー~

  d3.select('#TEXT2').selectAll('p').style('color', 'red');

要素の追加と削除(append,remove)

⇧に以下のdivタグがあります。

  
  d3.select('#appendAndRemove').append('p').text('キタ━(゚∀゚)━');
  d3.select('#appendAndRemove').selectAll('p').node().remove();

データバインディング(update)

  

リンゴ

オレンジ

バナナ

    var fruits = ["Apple", "Orange", "Banana"];

全てのp要素を配列fruitsの内容で上書きします。

データバインディング(update)

リンゴ

オレンジ

バナナ

    var fruits = ["Apple", "Orange", "Banana"];
    var p = d3.select('#fruits').selectAll('p');
    p.data(fruits).text(function(d) {return d;});

データバインディング(enter)

  

リンゴ

オレンジ

バナナ

    var fruits = ["Apple", "Orange", "Banana", "strowberry"];

p要素の数より配列fruitsの要素数が多いので、多い分は追加します。

データバインディング(enter)

リンゴ

オレンジ

バナナ

var fruits = ["Apple", "Orange", "Banana", "strowberry"];
var p = d3.select('#fruits').selectAll('p');
var update = p.data(fruits);
var enter = update.enter();
update.text(function(d) {return d;});
enter.append('p').text(function(d) {return d;});

データバインディング(enter)

要素がまだ無い場合

  
var fruits = ["Apple", "Orange", "Banana"];
var p = d3.select('#empty').selectAll('p');
var enter = p.data(fruits).enter();
enter.append('p').text(function(d) {return d;});

データバインディング(exit)

  

リンゴ

オレンジ

バナナ

イチゴ

    var fruits = ["Apple", "Orange", "Banana"];

p要素の数の方が配列fruitsの要素数より多いので、余るp要素を削除する

データバインディング(exit)

リンゴ

オレンジ

バナナ

イチゴ

    var fruits = ["Apple", "Orange", "Banana"];
    var p = d3.select('#fruits').selectAll('p');
    var update = p.data(fruits);
    var exit = update.exit();
    update.text(function(d) {return d;});
    exit.remove();

SVGの生成

お待たせしましたm(__)m
やっと描画のお話です。


SVG要素の生成は以下のように行います。

var svg = d3.select("#graph").append("svg");

以下のように描画するサイズを指定して生成することも出来ます。

var svg = d3.select("#graph").append("svg").attr("width", 200).attr("height", 100);

線を描いてみる(line)

線を描いてみる(line)

var data = [
  { "x": 100,   "y": 50},  { "x": 100,  "y": 400},
  { "x": 700,  "y": 400},{ "x": 100,   "y": 50}];

var line = d3.svg.line()
  .x(function(d) { return d.x; })
  .y(function(d) { return d.y; });

svg.append("path")
  .attr("d", line(data))
  .attr("stroke", "#e74c3c")
  .attr("stroke-width", 2)
  .attr("fill", "none");

棒を描いてみる(bar)

棒を描いてみる(bar)

var data = [ 2, 3, 5, 7, 11, 13, 17, 23, 29,
             31, 37, 41, 43, 47, 53, 59, 61 ];

d3.select("#bar").selectAll("div")
  .data(data)
  .enter()
  .append("div")
    .style("display", "inline-block")
    .style("width", "20px")
    .style("margin-right", "2px")
    .style("background-color", "#9b59b6")
    .style("height", function(d) {
      var barHeight = d * 5;
      return barHeight + "px";
    });

円を描いてみる(circle)

円を描いてみる(circle)

var svg = d3.select("#circle")
  .append("svg").attr("width",700).attr("height",400);
var circle = svg.append("circle")
  .attr("cx",300)
  .attr("cy",200)
  .attr("r",180)
  .attr("fill","#28cca3")
  .attr("stroke","#2efac7")
  .attr("stroke-width",10);

円を動かしてみる(transition)

円を動かしてみる(transition)

circle
  // 円の遷移(transition)を設定する
  .transition()
  // どれくらいの時間をかけて行うか
  .duration(800)
  // ランダムに移動
  .attr("cx", function() { return Math.random() * w; })
  .attr("cy", function() { return Math.random() * h; })
  // イージング(モーションの加速、減速)のオプション
  .ease("bounce")

要素生成時のアニメーション(transition)

要素生成時のアニメーション(transition)

var w = 700;
var h = 300;

var radiuses = [11, 13, 17, 19, 23, 29, 31, 37, 41, 43];
var color =
  ["#bdc3c7", "#f39c12", "#2ecc71", "#e74c3c", "#2c3e50",
   "#3498db", "#8e44ad", "#e67e22", "#1abc9c", "#f1c40f"];

var svg = d3.select("#transition").append("svg")
  .attr("width", w).attr("height", h);

要素生成時のアニメーション(transition)

svg.selectAll("circle")
  .data(radiuses)
  .enter()
  .append("circle")
    .attr("cx", function(d) { return Math.random() * w; })
    .attr("cy", function(d) { return Math.random() * h; })
    .attr("fill", function(d, i) { return color[i]; })
    // 半径を0に設定しておく(ease以下のattrで上書き)
    .attr("r", 0)
  .transition()
  // 円の生成タイミングをずらす
  .delay(function(d, i) { return i * 100; })
  .duration(1000)
  .ease("bounce")
    .attr("r", function(d) { return d; });

イベント設定(on)

イベント設定(on)

svg.selectAll("circle")
  .data(radiuses).enter().append("circle")
    .attr("cx", function(d, i) { return 30 + ( i * 100); })
    .attr("cy", function(d) { return h / 2; })
    .attr("fill", function(d, i) { return color[i]; })
    .attr("r", function(d) { return d; })

  // マウスオーバー時に円の色を変更
  .on("mouseover", function(d) {
    d3.select(this).attr("fill", "#2c3e50"); })

  // マウスアウト時に円の色を元の色に変更
  .on("mouseout", function(d, i) {
    d3.select(this).attr("fill", color2[i]); })

  // クリックされた円を削除
  .on("click", function(d) {
    d3.select(this).remove(); });

表示比率(scale)

表示比率(scale)

// linear:スケールの仕方
var xScale = d3.scale.linear()
  // 表示するデータの範囲を設定
  .domain([0, d3.max(data)])
  // 表示する領域を設定
  .range([0, w])
  // キリの良い数字にして表示
  .nice();

svg.selectAll("rect")
  .data(data)
  .enter()
  .append("rect")
    .attr("x", 0)
    .attr("y", function(d, i) { return i * 30; })
    // 表示領域の幅とデータから表示する棒の幅を設定
    .attr("width", function(d) { return xScale(d); })
    .attr("height", 20)
    .attr("fill", "#e67e22");

軸の作成(axis)

軸の作成(axis)

// 軸の両サイドのラベル文字が切れないようにpaddingを設定
var padding = 20;

var xAxis = d3.svg.axis()
  .scale(xScale)
  // 目盛を出す方向
  .orient("bottom");

svgAxis.append("g")
  .attr("class", "axis")
  // 軸を高さ-20pxに移動
  .attr("transform", "translate(0," + (h-20) + " )")
  .attr("font-size", "14px")
  .attr("fill", "none")
  .attr("stroke", "black")
  .call(xAxis);

scaleの種類(scale)

scaleの種類(scale)

  // 均等目盛
  var xScale = d3.scale.linear()
  // 開平目盛
  // var xScale = d3.scale.sqrt()
    .domain([0, d3.max(data)])
    .range([padding, w - padding])
    .nice();

D3.jsのレイアウト

Galleryにあったように、D3.jsでは複雑なグラフを作成することが出来ます。
D3.jsでは複雑なグラフのレイアウトをあらかじめ提供していますので、紹介します。

それぞれ使い方が異なりますので、APIリファレンスやサンプルから使い方を理解してください。

D3.jsのレイアウト(Bundle)

bundle

https://github.com/mbostock/d3/wiki/Bundle-Layout

D3.jsのレイアウト(Chord)

chord

https://github.com/mbostock/d3/wiki/Chord-Layout

D3.jsのレイアウト(Cluster)

cluster

https://github.com/mbostock/d3/wiki/Cluster-Layout

D3.jsのレイアウト(Force)

force

https://github.com/mbostock/d3/wiki/Force-Layout

D3.jsのレイアウト(Histogram)

histogram

https://github.com/mbostock/d3/wiki/Histogram-Layout

D3.jsのレイアウト(Pack)

pack

https://github.com/mbostock/d3/wiki/Pack-Layout

D3.jsのレイアウト(Partition)

partition

https://github.com/mbostock/d3/wiki/Partition-Layout

D3.jsのレイアウト(Pie)

pie

https://github.com/mbostock/d3/wiki/Pie-Layout

D3.jsのレイアウト(Stack)

stack

https://github.com/mbostock/d3/wiki/Stack-Layout

D3.jsのレイアウト(Tree)

tree

https://github.com/mbostock/d3/wiki/Tree-Layout

D3.jsのレイアウト(Treemap)

treemap

https://github.com/mbostock/d3/wiki/Treemap-Layout

Demo - 説明

  • CSVダウンロード機能を持った既存の画面に対し、グラフ表示機能を追加するシーンを想定

Demo - CSVダウンロード画面

csv_app

Demo - データ

csv

Demo(D3.js)

d3

Demo(D3.js) - Code

// 描画領域の設定
var margin = {top: 20, right: 120, bottom: 80, left: 60},
  width = 1000 - margin.left - margin.right,
  height = 700 - margin.top - margin.bottom;

// 利用する色のカテゴリー
var color = d3.scale.category10();

// X軸の範囲
var x = d3.time.scale()
  .range([0, width]);

// Y軸の範囲
var y = d3.scale.linear()
  .range([height, 0]);

Demo(D3.js) - Code


// X軸のラベル位置
var xAxis = d3.svg.axis()
  .scale(x)
  .orient("bottom");

// Y軸のラベル位置
var yAxis = d3.svg.axis()
  .scale(y)
  .orient("left");

var line = d3.svg.line()
  .x(function(d) { return x(d.year); })
  .y(function(d) { return y(d.GDP); });

var svg = d3.select("#graph").append("svg")
  .attr("width", width + margin.left + margin.right)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", 
      "translate(" + margin.left + "," + margin.top + ")");

Demo(D3.js) - Code


d3.csv("http://example.com/sales/csv", function(error, data) {
  // ドメイン毎の色の設定
  // filterでheaderのkeyが"年"のカラムを取り除く
  color.domain(d3.keys(data[0])
    .filter(function(key) { return key !== "年"; }));

  data.forEach(function(d) {
    // Date形式にparse
    d.year = d3.time.format("%Y").parse(d["年"]); });

  var industries = color.domain().map(function(name) {
    return {
      name: name,
      values: data.map(function(d) {
        return {year: d.year, GDP: +d[name]}; })
    }; });

Demo(D3.js) - Code

  // X軸、Y軸のラベルの範囲を最小、最大値から設定する
  x.domain(d3.extent(data, function(d) { return d.year; }));

  y.domain([
    d3.min(industries, function(j) {
      return d3.min(j.values, function(v) { return v.GDP; }); }),
    d3.max(industries, function(j) {
      return d3.max(j.values, function(v) { return v.GDP; }); })
  ]);

Demo(D3.js) - Code


  // X, Y軸に単位を追加
  svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis)
      .selectAll("text")
        .style("text-anchor", "end")
        .attr("dx", "-.8em")
        .attr("dy", ".15em")
        .attr("transform", "rotate(-65)");

  svg.append("g")
      .attr("class", "y axis")
      .call(yAxis)
    .append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", 6)
      .attr("dy", ".71em")
      .style("text-anchor", "end")
      .text("10億円");

Demo(D3.js) - Code

var industry = svg.selectAll(".industry")
  .data(industries)
  .enter().append("g")
    .attr("class", "industry");

// 各業種毎のライン
industry.append("path")
  .attr("class", "line")
  .attr("d", function(d) { return line(d.values); })
  .style("stroke", function(d) { return color(d.name); });

Demo(D3.js) - Code

// 各ラインの業種
industry.append("text")
  .datum(function(d) {
    return {name: d.name, value: d.values[d.values.length - 1]}; })
  .attr("transform", function(d) {
     return "translate(" + x(d.value.year) + "," + y(d.value.GDP) + ")"; })
  .attr("x", 3)
  .attr("dy", ".35em")
  .text(function(d) { return d.name; });
});

ナガイ(´・ω・`)
まだアニメーションとかつけてない…

D3.js関連ライブラリ紹介

Demo(dimple)

dimple

Demo(dimple) - Code

 // SVGのサイズなどを設定
 var svg = dimple.newSvg("#graph", 1000, 700);
 var margin = {top: 70, right: 10, bottom: 80, left: 100},
 width = 1000 - margin.left - margin.right,
 height = 700 - margin.top - margin.bottom;

 // CSV読込
 d3.csv("http://example.com/sales/csv", function (data) {

   // データをdimple.js用に整形
   data = $.map($.map(data, function(row) {
     return $.map(d3.keys(row).filter(function(key) {
       return key !== "年"; }),
       function(key) {
       return {"年": row["年"], "業種": key,"GDP": row[key]};})}),
       function(n){ return n;})

Demo(dimple) - Code

   var myChart = new dimple.chart(svg, data);
   myChart.setBounds(margin.left, margin.top, width, height);

   // X軸の設定
   var x = myChart.addTimeAxis("x", "年", "%Y", "%Y");
   x.showGridlines = true;

   // Y軸の設定
   var y = myChart.addMeasureAxis("y", "GDP");
   y.showGridlines = true;

   // 設定しない場合は"140k"のように表示される(%などを追加したい場合に利用)
   y.tickFormat = "";

   // ラインのプロット
   myChart.addSeries("業種", dimple.plot.line);

Demo(dimple) - Code


   // 凡例の追加
   var myLegend = myChart.addLegend(100, 10, 900, 40, "left");
   myChart.draw();

   // フォントサイズの変更
   myLegend.shapes.selectAll(".legendText")
     .style("font-size", "12px");

   // X軸のテキスト(年)を斜めに回転
   x.shapes.selectAll("text").attr("transform", function (d) {
       return d3.select(this).attr("transform")
         + " translate(25, 40) rotate(-160)";
   });
});

Demo(NVD3.js)

nvd3

Demo(NVD3) - Code

// CSV読込
d3.csv("http://example.com/sales/csv", function(data) {

    nv.addGraph(function() {

      // X軸、Y軸に設定する項目と、表示範囲、色の設定
      var chart = nv.models.lineChart()
      .x(function(d) { return d["年"]; })
      .y(function(d) { return d["GDP"]; })
      .yDomain([0,140000])
      .color(d3.scale.category10().range());

Demo(NVD3) - Code


// データをNVD3用に整形
var keys = d3.keys(data[0]).filter(function(key) {
  return key !== "年"; });
data = $.map(keys, function(key){
  return {"key": key, "values": $.map(data, function(row) {
    return {"年":parseInt(row["年"]), "GDP":parseInt(row[key])}; })};})

// チャート作成
  d3.select('#graph svg')
  .datum(data)
  .transition().duration(2000)
  .call(chart);

return chart;
    });
});

まとめ

  • D3.jsを使えばリッチでインタラクティブなグラフが作れる
    • 楽しい(╹◡╹)
  • なんでも出来過ぎる
    • API把握しきれない(´・ω・`)
  • シンプルなグラフを作成したい場合はdimple.jsやNVD3.jsなどの利用を検討してみる

参考