サイカでエンジニアをしております田中です。
今回は弊社の統計分析ツール adelie でも採用している C3 というチャートライブラリを紹介したいと思います。
C3 とは
C3 はチャートに特化したビジュアライゼーションライブラリです。D3 のラッパーライブラリで、D3 の見た目の美しさやインタラクション性などの特徴を残しながら、チャートライブラリとして分かりやすいインターフェースを提供するという方針で開発を進めています。
開発のきっかけ
現在弊社の adelie では UI の一部として C3 を使ったチャートを搭載していますが、初期バージョンでは D3 を直接使い自前で実装していました。またちょうど同じ頃、私の個人的なプロジェクトでも D3 を使ったチャートを実装しており、それらで得たノウハウをもとに再利用可能なライブラリとして個人的に開発を始めました。その後は基本的には上述の方針のもと、様々なところからいただく様々な要望(issues@github)に応えるかたちで開発を進め、今に至っています。
そもそも D3 って何だっけ?
すでに何度か名前が出てきていますが、D3 というライブラリはご存知でしょうか? D3はMike Bostock氏を中心に開発されている JavaScript ベースのライブラリで、主にデータのビジュアライゼーションの文脈で利用されています。詳細は割愛しますが、柔軟かつスマートなデータ構造、豊かな表現力と美しい見た目など非常に洗練された特徴を持っており、円や線などの単純なものから構造や動きを伴った複雑ものまでこのライブラリ1つで描けてしまいます。基本的にはやりたいと思ったことほぼすべてこの D3 で実現できるのではと思うほど、高い汎用性と表現力を持っていると思います。
しかしながら D3 だと辛いところが少々…
そんな D3 ですが、汎用性が高いことの裏返しとして、特定のビジュアライゼーション向けに極めて特化した機能を提供していないという側面もあります。要は何かをする際に書かなければいけないコードが意外と多いということです。データを与えたらすべてをよしなにビジュアライズしてくれるわけではなく、ある程度自分で組み立てる必要があります。チャートに関していえば、例えば折れ線グラフを書く場合、x 軸と y 軸のスケールを決め、設置し、データを読み込み、折れ線を描く、ということをしなくてはいけなかったりします。 また初期学習コストも若干高いように思います。理由は Selection や Enter/Exit など D3 に独特の概念を理解する必要があるからです。そのためちょっと使ってみる際の敷居も高く、すでにそのような(ある目的に特化してもろもろよしなに処理してくれる)ライブラリが存在する場合には、あえて D3 を選択するということにはなかなかならないように思います。
そこで C3
このように D3 にはビジュアライゼーションライブラリとしてとても魅力的な面があるものの、一方で D3 特有のデータ構造・手続きにより取っ付きにくい面もあり、ある目的に特化して使うにはそのままでは少々使い勝手が悪いです。特にチャートを描く場合には、すでに巷にあふれるチャートライブラリと比べて使いやすさの点でどうしても見劣りします。元々チャートライブラリでないのでしょうがないのですが、D3 が持つ可能性を考えるとそれが理由で使われないのはちょっともったいないです。 そのような問題意識のもと、C3 はあくまでチャートライブラリという立ち位置でできるだけ D3 の概念を隠蔽しつつ、それでいて D3 の良さを生かすというアプローチを取りました。これによって D3 の良さである高い汎用性と表現力は若干失われましたが、代わりにボイラープレートの類いが隠蔽され、インターフェースもより直感的になるなど、トレードオフに勝る恩恵を得ることができ、使い勝手も大分向上したと思います。
C3 の主な特徴
現在 C3 には様々な特徴がありますが、他のチャートライブラリではあまり見かけない特徴として以下が挙げられます。
動的な更新
C3 にはチャートを操作する API がいくつか用意してあり、それを用いてチャートを動的に更新することができます。例えば、データを更新したり、ある領域に色をつけたり、特定のデータにフォーカスしたりすることが可能です。また D3 の特徴の1つでもあるアニメーションを取り入れているため、更新の際のチャートの変化は基本的にとてもスムースです。
さまざまなインタラクション
イベントハンドラもいくつか実装されています。例えばデータの座標点に対する click/mouseover/mouseout 、チャート領域への mouseover/mouseout 、凡例の click/mouseover/mouseout などがあります。これらのイベントハンドラと上記の動的な更新を組み合わせることで、チャートとアプリケーションをより密に連携させることができます(クリックされたらデータを更新する、マウスがチャートに乗ったら何か処理を実行する、など)。
CSS によるスタイル指定
C3 が生成する各エレメントには、あらかじめ決められたクラスを割り当てるようにしてあるので、そのクラスに対してスタイルを指定することで見栄えをカスタマイズすることができます。
例
では実際に簡単なチャートを描いてみます。
コードは以下になります。
====================================
var chart = c3.generate({
// チャート(svg)をはめ込む要素をセレクタで指定
bindto: '#c3-chart',
// 描写するデータを指定
data: {
columns: [
["data1", 80, 150, 120, 180, 200],
["data2", 230, 190, 240, 280, 240],
["data3", 60, 90, 180, 110, 170],
]
}
});
====================================
D3で直接描くの(例えばこちら)と比べてシンプルなインターフェースで描けているのが分かります。また今回は割愛しますが、軸にラベルを追加したり、凡例の位置を変えたりなど、必要になりそうな機能は一通り実装されており、同じようにシンプルなインターフェースで利用することができます。
次に API を使って描写後にチャートを操作してみます。以下のボタンでそれぞれチャートが更新されると思います。
チャートの更新は c3.generate() で生成されたオブジェクトに対して API を呼ぶことで実行できます。コードは以下になります。
====================================
d3.select('#c3-transform-bar').on('click', function () {
// 棒グラフに変形
chart.transform('bar');
});
d3.select('#c3-transform-line').on('click', function () {
// 折れ線グラフに変形
chart.transform('line');
});
d3.select('#c3-data-reload').on('click', function () {
// 更新用のデータ生成
var columns = [['data1'], ['data2'], ['data3']], i;
columns.forEach(function (c) {
for (i = 0; i < 10; i++) {
c.push(Math.random() * 100);
}
});
// 更新
chart.load({
columns: columns
});
});
d3.select('#c3-data-flow').on('click', function () {
// 追加データの生成
var columns = [['data1'], ['data2'], ['data3']], i;
columns.forEach(function (c) {
for (i = 0; i < 3; i++) {
c.push(Math.random() * 100);
}
});
// データを追加し、同じ長さ分左へ流す
chart2.flow({
columns: columns
});
});
====================================
このように C3 を使うことで D3 ベースのチャートを簡単に描くことができ、そのチャートに対する操作も API を通じて容易に行うことができます。
ドキュメントなど
今回は簡単な例のみ紹介させていただきましたが、他にもさまざまな機能があります。是非以下のドキュメントを見ていただき、チャートとアプリケーションのより密な統合について可能性を探っていただけたら幸いです。