LoginSignup
40
41

More than 5 years have passed since last update.

Rails + D3.jsでグラフを描く

Posted at

Railsで作っているアプリケーションでグラフを描きたいと思い、今迄はcanvas( http://www.html5.jp/canvas/ )とかccchart ( http://ccchart.com/ )を使ってたが今回はせっかくなのではやりのD3.js( http://d3js.org/ )を使ってみる事にしました。

何故人気なのか?

【D3.js】人気度の動向を調べてみた( http://shimz.me/blog/d3-js/3362 )をみてみると、今尚人気は健在そうで、日に日に注目度は増しているようです。使ってみた上で何故人気なのかなーと考えてみたところ、

・圧倒的な柔軟性の高さ

過去ccchartなどグラフを使ってみた事ありますが、確かにデータをセットするだけで簡単にグラフを描画してくれます。
が、一方で与えられた種類以外のグラフを書く事はできません。
その点D3.jsは座標の変換はしてくれるものの実際の図を描画するところは、Div+css で棒グラフにしてみたりSVGで円にしてみたりと、自由にできます。
簡単なグラフを描くだけなら必要ないですが、少し凝ったグラフを作りたい時にはオススメです。
実際にD3.js( http://d3js.org/ )のサンプルを見ていただければその自由度は一目瞭然ですね。

・簡潔に書ける

実際に描いてみて思ったのが、覚えると凄く簡単。
①実際のデータよりx軸、y軸の計算・座標系に変換
②x,y軸の座標軸の設定
③styleの設定

大きくこの3つだけ。
加えてjqueryのonメソッドを使ってmouseoverやmouseoutの際に動きを見せたりなど、簡単に実装できます。

上記2点が理由としては大きいかなーと思います。

実際に使ってみる

mailの送付日とクリック数のグラフを今回は作ってみる。

①まずはd3.jsを読み込む

※外部から読み込む or ローカルに落として設定してください。

index.html.haml
%script{:src => "http://d3js.org/d3.v3.min.js"}

②jsonを返す仕様を作っておく。

結局データを渡して描画してもらうので、データをjson形式で返す仕様を作っておきます。

今回はrails4なので.rubyでjsonを返すように作成。

app/controllers/search_controller.rb
def search_mail
  @mail_logs = MailLog.title_is(params[:title])
  render layout: false 
end
config/routes.rb
get "search/search_mail"
app/views/search/search_mail.ruby
@mail_logs.map do |mail_log|
  {
    send_date: mail_log.send_date,
    click_count: mail_log.click_count
  }
end.to_json

③実際にd3.jsを設定していく

:javascript

 //まずはそれぞれのサイズを設定
  var margin = {top: 20, right: 20, bottom: 30, left: 50},
      width = 960 - margin.left - margin.right,
      height = 500 - margin.top - margin.bottom;

次にスケール設定します。

「スケールとは、入力ドメイン(領域)を出力レンジ(範囲)にマッピングする関数である」・・・Mike BostockのD3スケールの定義。
これをみるとスケールのイメージはつきますね。

簡単な例を出すと、

var scale = d3.scale.linear()
                    .domain([100, 500])
                    .range([10, 350])

こうすると入力の最小値/最大値は100~500、一方で出力の最小値/最大値は10~350。
なので例えば、100を入力すれば10が出力されます。

話を戻すと、

d3.scale.ordinal()は序数スケールを、
d3.scale.linear()は線形スケールを作っている。

※序数データ:順序のあるカテゴリデータ
e.x.) 成績の5段階評価、大学の年次

rangeRoundBandsは棒グラフを作る時に凄く役に立つメソッドです。
rangeRoundBands([0, width], .1)これだと、0~widthの範囲で出力値は入力値のもっとも近い整数値になり、スペース量を0.1%で設定したピクセル値が得られます。

  var x = d3.scale.ordinal().rangeRoundBands([0, width], .1);
  var y = d3.scale.linear().range([height, 0]);

これでスケールは設定できました。
次はグラフに重要な軸の設定をします。

d3では一般的にd3.svg.axis()によって軸関数を生成します。
あとは軸の取りうる範囲を先程のスケール関数を用いて設定し、
メモリの記入場所をorient()で設定します。

横軸・・・"top","bottom"
縦軸・・・"left","right"

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

軸関数も生成できました。
では、いよいよsvgで描画していきます。
まずはsvgのwidth,heightの設定。

そして見慣れないappend("g")というものがあります。
SVGの世界においてg要素とはグループ要素です。
グループ要素はline,rect,circleなどとは異なり、目に見えるように表示する要素を持ってません。

ここでは、
・特定のデータのグループ化
・transformによって座標変換の実施
によって使われてます。

  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 + ")");

そしていよいよjsonを使ってデータの入れ込みです。
先程用意したpathからjsonデータを入れ込みます。

data.forEach(function(d) {}はjson形式で取り出したdataをそれぞれeach文で回しています。

d.open_count = +d.open_count;
これはdataのopen_countプロパティに取ってきたd.open_countを数値データとして代入しています。

日付データとして代入したい場合は、
var parseDate = d3.time.format("フォーマット");
d.send_date = parseDate(d.date);

みたいな形で、文字列データを数値データに変換して使います。

  d3.json("/search/search_mail?title=#{params[:title]}", function(error, data) {
    data.forEach(function(d) {
      d.open_count = +d.open_count;
    });

先程のdomainを使ってデータの入力範囲を指定します。
d3.max()はd.open_countの中のmax値を出力します。

先程設定した軸関数も実際にsvgの中にセットしていきます。
g要素に関しては、上述した通りですね。
attr("transform", "rotate(-90)")は反時計周りに90度回転するという設定です。

yとdyって何が違うの?と感じたら、下記のサイトを参考にしてみてください。
(http://wisdom.sakura.ne.jp/web/xml/svg/svg4.html)

    x.domain(data.map(function(d) { return d.send_date; }));
    y.domain([0, d3.max(data, function(d) { return d.open_count; })]);

    svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis);

    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("open_count (num)");

最後に棒グラフを作っていきます。
正確には棒グラフになる座標データですがw

selectAll(".bar")としているのは、svgの中にある".bar"要素を選択していますが、今は勿論何もないので空セクションが返ります。

.data(data)でjson形式で取ったdataを入れ込み、
.enter()をする事でdataにバインドされた要素を作成します。

その要素にappend("rect")する事で要素の数(=データの数)だけrect要素が作られます。

.attr("x", function(d) { return x(d.send_date); })
.attr("y", function(d) { return y(d.open_count); })

これによってx、yにそれぞれデータを代入し、

.attr("height", function(d) { return height - y(d.open_count); })
(データ図の高さ - データ個数)によって高さを算出。

といった具合です。

    svg.selectAll(".bar")
      .data(data)
      .enter().append("rect")
      .attr("class", "bar")
      .attr("x", function(d) { return x(d.send_date); })
      .attr("width", x.rangeBand())
      .attr("y", function(d) { return y(d.open_count); })
      .attr("height", function(d) { return height - y(d.open_count); })
  });

あとはcssでスタイルを設定。

.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.bar {
  fill: orange;
}

.bar:hover {
  fill: orangered ;
}

.x.axis path {
  display: none;
}


.container_main {                                                                                                                                                            
  margin-top: 50px;
}

.bar {
  display: inline-block;
  width: 20px;
  background-color: teal;
  margin-right: 2px;
}

下記のようになりました!

スクリーンショット 2014-05-20 10.51.34.png

他にも沢山あるので、下記のExampleから選んで作って見てくださいね。
https://github.com/mbostock/d3/wiki/Gallery

40
41
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
41