12/25/2013

多次元解析のためのチャートライブラリ DC.js

d3.js Advent Calendar 2013の25日目です。皆様のおかげで全部埋めることができて良かったです。
今日はDC.jsという多次元解析のためのチャートライブラリをご紹介します。ちなみにこれはD3の情報ソースという5日目の記事で紹介したDashing D3.jsで最近紹介されていて知りました。
DC.jsによるダッシュボードサンプル

DC.jsはサイトを見ていただければどういうものか分かると思いますが、 複数のチャートにまたがって、データのフィルタが可能なチャートライブラリです。デモサイトのそれぞれのチャートを操作すると他のチャートもそれに従ってダイナミックに表示が更新されます。

DC.jsはCrossfilterという配列のフィルタ処理を行うライブラリとD3.jsに依存しています。CrossfilterはSquareというiPhoneをカードリーダーにするハードウェア、サービスを提供している会社が提供しているオープンソースのライブラリで自社のSquare Registerのために開発されました。
上記のCrossfilterのサイトでもDC.jsのサイトのようなデモが見れます。DC.jsの作者はこのCrossfilterのデモに感銘を受けて、これを手軽に扱えるライブラリとしてDC.jsを開発したとのことです。

Dashing D3.jsで紹介されていたDC.jsのチュートリアルがとてもわかり易かったのでそれを元にして紹介する形をとりたいと思います。分かりやすくするため、若干コードを変更しているところもありますが基本的には一緒です。
Making Dashboards with Dc.js - Part 1: Using Crossfilter.js
Making Dashboards with Dc.js - Part 2: Graphing

1.Crossfilter

まずCrossfilterがどのように動作するかCrossfilterのAPIドキュメントにあるにある データを使って見てみましょう。
  1. var data = [
  2. {date: "2011-11-14T16:17:54Z", quantity: 2, total: 190, tip: 100, type: "tab"},
  3. {date: "2011-11-14T16:20:19Z", quantity: 2, total: 190, tip: 100, type: "tab"},
  4. {date: "2011-11-14T16:28:54Z", quantity: 1, total: 300, tip: 200, type: "visa"},
  5. {date: "2011-11-14T16:30:43Z", quantity: 2, total: 90, tip: 0, type: "tab"},
  6. {date: "2011-11-14T16:48:46Z", quantity: 2, total: 90, tip: 0, type: "tab"},
  7. {date: "2011-11-14T16:53:41Z", quantity: 2, total: 90, tip: 0, type: "tab"},
  8. {date: "2011-11-14T16:54:06Z", quantity: 1, total: 100, tip: 0, type: "cash"},
  9. {date: "2011-11-14T16:58:03Z", quantity: 2, total: 90, tip: 0, type: "tab"},
  10. {date: "2011-11-14T17:07:21Z", quantity: 2, total: 90, tip: 0, type: "tab"},
  11. {date: "2011-11-14T17:22:59Z", quantity: 2, total: 90, tip: 0, type: "tab"},
  12. {date: "2011-11-14T17:25:45Z", quantity: 2, total: 200, tip: 0, type: "cash"},
  13. {date: "2011-11-14T17:29:52Z", quantity: 1, total: 200, tip: 100, type: "visa"}
  14. ];
crossfilterのインスタンスを作成します。
  1. var ndx = crossfilter(data);
totalを軸としたdimensionを作成します。
  1. var totalDim = ndx.dimension(function(d) { return d.total; });
その中からtotal=90のものを抽出します。
  1. var total_90 = totalDim.filter(90);
以下のjsdoitのコードで動作が確認できるので見てください。total=90のものだけ抽出されているのがわかると思います。


では次にstringでやってみましょう。
  1. var typeDim = ndx.dimension(function(d) {return d.type;});
  2. var visa_filter = typeDim.filter("visa");
  3. var cash_filter = typeDim.filter("cash");
以下のjsdoitのコードで動作が確認できるので見てください。visa、 cashでそれぞれ抽出できているのがわかると思います。


ちなみにコメントアウトしてるvar total_90 = totalDim.filter(90);を有効にするとfilterの結果は0になります。totalが90のものにvisa、 cashのものがないからです。このようにちゃんとcrossfilterされていることが確認できます。では次に実際にDC.jsを使ってチャートを描画してみましょう。

2.DC.jsでチャートの描画

2.1:ラインチャート

まずデータを用意します。以下ははデイリーのwebサーバのhit数を示した配列データです。
  1. var data = [
  2. {date: "12/27/2012", http_404: 2, http_200: 190, http_302: 100},
  3. {date: "12/28/2012", http_404: 2, http_200: 10, http_302: 100},
  4. {date: "12/29/2012", http_404: 1, http_200: 300, http_302: 200},
  5. {date: "12/30/2012", http_404: 2, http_200: 90, http_302: 0},
  6. {date: "12/31/2012", http_404: 20, http_200: 90, http_302: 0},
  7. {date: "01/01/2013", http_404: 2, http_200: 90, http_302: 0},
  8. {date: "01/02/2013", http_404: 1, http_200: 10, http_302: 1},
  9. {date: "01/03/2013", http_404: 2, http_200: 90, http_302: 0},
  10. {date: "01/04/2013", http_404: 2, http_200: 90, http_302: 0},
  11. {date: "01/05/2013", http_404: 2, http_200: 90, http_302: 0},
  12. {date: "01/06/2013", http_404: 2, http_200: 200, http_302: 1},
  13. {date: "01/07/2013", http_404: 1, http_200: 200, http_302: 100}
  14. ];
日付データをd3のtime.formatクラスを使って、d3で扱えるデータにdateを変更します。またトータルのhit数を示すtotal属性も追加します。
  1. var parseDate = d3.time.format("%m/%d/%Y").parse;
  2. data.forEach(function(d) {
  3. d.date = parseDate(d.date);
  4. d.total = d.http_404 + d.http_200 + d.http_302;
  5. });
crossfilterのインスタンスを作成し、X軸をtimelineにするためdateのdimensionを作成、またY軸用のkey-valueデータをgroup()のreduceSumを使って作成します。 
  1. //dataからcrossfilterのインスタンスを作成
  2. var ndx = crossfilter(data);
  3. //X軸をtimelineにするためdateのdimensionを作成
  4. var dateDim = ndx.dimension(function(d) {
  5. return d.date;
  6. });
  7. //Y軸にtotalを表示するためのkey-valueデータをdateDimから作成
  8. var hits = dateDim.group().reduceSum(function(d) {
  9. return d.total;
  10. });
dateの最古値と最新値を取得します。
  1. var minDate = dateDim.bottom(1)[0].date;
  2. var maxDate = dateDim.top(1)[0].date;
<div id="chart-line-hitsperday"></div>というdivを定義し、そこにラインチャートを表示させてみましょう。
dc.lineChartでインスタンスを作成し、parameterを設定して最後にdc.renderAll()で描画します。 
  1. //dcのlineChartインスタンスを作成
  2. var hitslineChart = dc.lineChart("#chart-line-hitsperday");
  3. //parameter設定
  4. hitslineChart
  5. .width(450).height(200)
  6. .dimension(dateDim)
  7. .group(hits)
  8. .x(d3.time.scale().domain([minDate, maxDate]));
  9. //チャートを描画
  10. dc.renderAll();
以下のjsdoitのコードで動作が確認できます。チャートがひとつなので 複数のチャートにまたがっての動作はないですがチャートが描画されているのがわかると思います。またこのラインチャートはレンジセレクタが出来るようになっています。


2.2:パイチャートの追加 

yearの属性を追加する処理を追加します。 
  1. var parseDate = d3.time.format("%m/%d/%Y").parse;
  2. data.forEach(function(d) {
  3. d.date = parseDate(d.date);
  4. d.total = d.http_404 + d.http_200 + d.http_302;
  5. d.Year=d.date.getFullYear(); //yearの属性を追加
  6. });
パイチャート用のdimensionを作成、またパイチャートののkey-valueデータを作成します。
  1. //パイチャートのdimensionを作成
  2. var yearDim = ndx.dimension(function(d) {return +d.Year;});
  3. //パイチャートののkey-valueデータをyearDimから作成
  4. var year_total = yearDim.group().reduceSum(function(d) {return d.total;});
<div id="chart-ring-year"></div>というdivを定義し、そこにパイチャートを表示させてみましょう。
基本的にラインチャートと同じ手続きです。 
  1. //dcのパイチャートインスタンスを作成
  2. var yearRingChart = dc.pieChart("#chart-ring-year");
  3. yearRingChart
  4. .width(200).height(200)
  5. .dimension(yearDim)
  6. .group(year_total)
  7. .innerRadius(30);
以下のjsdoitのコードで動作が確認できます。ラインチャートのレンジセレクタで範囲を変更すると動的にパイチャートが変更されるが分かるかと思います。またパイチャートをクリックするとラインチャートが変化します。このように複数のチャートにまたがって、容易にフィルタすることができます。面白いですね。


2.3:ラインチャートにhttpステータス毎のデータを表示

httpステータス毎にgroupデータを作成します。 
  1. //Y軸にtotalを表示するためのkey-valueデータをhttpステータス毎に作成
  2. var status_200=dateDim.group().reduceSum(function(d) {return d.http_200;});
  3. var status_302=dateDim.group().reduceSum(function(d) {return d.http_302;});
  4. var status_404=dateDim.group().reduceSum(function(d) {return d.http_404;});
status_302、 status_404はstackとすることでgroupで設定されたものに対して積み上げて表示することができます。
  1. //lineChartの各種parameter設定
  2. hitslineChart
  3. .width(450).height(200)
  4. .dimension(dateDim)
  5. .group(status_200,"200")
  6. .stack(status_302,"302")
  7. .stack(status_404,"404")
  8. .renderArea(true)
  9. .x(d3.time.scale().domain([minDate,maxDate]))
  10. .legend(dc.legend().x(50).y(10).itemHeight(13).gap(5))
  11. .yAxisLabel("Hits per day");
ではjsdoitで確認してみましょう。ラインチャートがhttpステータス毎に表示されているのが分かるかと思います。


2.4:データテーブルの追加

最後にデータテーブルを追加してみましょう。
以下のデータテーブル用のhtmlを追加します。
  1. <div style='font-size:11px; width:270px; margin-left:180px;'>
  2. <table id="dc-data-table">
  3. <thead>
  4. <tr class="header">
  5. <th>Day</th>
  6. <th>TPS 200</th>
  7. <th>TPS 302</th>
  8. <th>TPS Total</th>
  9. </tr>
  10. </thead>
  11. </table>
  12. </div>
データテーブルのインスタンスを作成し、parameterを設定します。基本的に他のチャートと同じ手順です。
  1. //dcのデータテーブルインスタンスを作成
  2. var datatable = dc.dataTable("#dc-data-table");
  3. datatable
  4. .dimension(dateDim)
  5. .group(function(d) {return d.Year;})
  6. .columns([
  7. function(d) { return d.date.getDate() + "/" + (d.date.getMonth() + 1) + "/" + d.date.getFullYear(); },
  8. function(d) {return d.http_200;},
  9. function(d) {return d.http_302;},
  10. function(d) {return d.http_404;},
  11. function(d) {return d.total;}
  12. ]);
ではjsdoitで確認してみましょう。データテーブルも動的に変更されているのが確認できると思います。


いかがでしょうか?かなり少量のコードでこのような複数のチャートにまたがったフィルタ処理ができるのはすごいですね。DashBoard等のUI開発にかなり役に立ちそうです。

ではd3.js Advent Calendar 2013はこれでおしまいです。投稿していただいた方、読んでいただいた方、本当にありがとうございました!
Happy D3 life and Happy Holiday, そして良いお年を!

0 件のコメント:

コメントを投稿