Create a gist now

Instantly share code, notes, and snippets.

再利用可能なチャートに向けて

原文: "Towards Reusable Charts"

d3で再利用可能なチャートを作るためのパターンを提供したい。

(注:表示するのに最適なチャートの枠のサイズをある程度計算することは可能だが、多くの場合はパラメターとしてあらかじめ設定しておく場合が多い。)

function chart() {
  // generate chart here
}

関数(ファンクション)です。コードを再利用するための固まりの一単位です。

##設定

全ての関数であれば良いという訳ではなく、設定可能な関数が必要です。なぜなら多くのチャートがその見た目や動きをカスタマイズする必要があるからです。 例えば、高さや幅、色などを指定する必要があります。設定を渡すための一番簡単な方法は関数に引数を与えることでしょう。

function chart(width, height) {
  // generate chart here, using `width` and `height`
}

でもこれって関数を呼ぶ側にとっては結構類雑です。チャートにまつわる引数をどこかに格納し、更新が必要な時に常に渡してやる必要があるからです。 多くの設定を必要とするチャートにとって簡単な関数は不十分です。それに変わるものとして、多くのチャートライブラリーで行なわれているように、設定用のオブジェクトを渡してやることも出来るでしょう。

function chart(config) {
  // generate chart here, using `config.width` and `config.height`
}

しかしながら関数の呼び手はチャート関数とチャートの設定オブジェクトの2つを管理する必要がでてきます。チャート関数にチャート設定を結びつける(バインドする)にはクロージャーが必要です。

昔から使われるオブジェクト指向的なアプローチとしてChart.​proto­type.​renderを使っても良いのですが、関数を呼び出す時にthisコンテクストを管理する必要がでてきます。

function chart(config) {
  return function() {
    // generate chart here, using `config.width` and `config.height`
  };
}

そして呼び手はこうすれば良いだけです。

var myChart = chart({width: 720, height: 80});

更新をする時には myChart() を呼ぶだけなので簡単ですよね。

#再設定

でももしすでに作り上げた設定を変えたい時や既存のチャートの設定を確認したい時ってどうしましょう。 設定オブジェクトがクロージャの中にあるため外の世界からは何があるか分かりません。幸運にもJavascriptの関数はオブジェクトなので関数そのものに設定プロパティを格納することが出来ます。

var myChart = chart();
myChart.width = 720;
myChart.height = 80;

設定に参照するためにはチャートの実装を少し変更する必要があります。

(注:内部関数 (my)は関数内部でしか見えないので、どういう風に命名してもらってもかまいません。 チャートの名前そのものを使ってしまっても構いません。)

function chart() {
  return function my() {
    // generate chart here, using `my.width` and `my.height`
  };
}

少しばかりの糖衣構文で、生のプロパティをメソッドチェーンの可能なゲッター、セッターメソッドに置き換えることができます。こうすると関数の呼び手がもっとエレガントな方法でチャートを作り、なおかつ設定パラメターが変わった時の副作用を管理することが出来るようになります。

そしてチャートに設定値のデフォールトを提供してくれます。

以下が新しいチャートに2つのプロパティを設定する例です。

var myChart = chart().width(720).height(80);

既存のチャートをへんんこうするのも同様に簡単です。 Modifying an existing chart is similarly easy:

myChart.height(500);

そして中身を見てみることも

myChart.height(); // 500

内部ではチャートの実装はゲッター、セッターメソッドをサポートするために少し複雑になりますが、ユーザーにとっての利便さがあるのでやる価値はあるでしょう(そしてしばらくこの方法で書き続けているとなれてきます)。

function chart() {
  var width = 720, // default width
      height = 80; // default height

  function my() {
    // generate chart here, using `width` and `height`
  }

  my.width = function(value) {
    if (!arguments.length) return width;
    width = value;
    return my;
  };

  my.height = function(value) {
    if (!arguments.length) return height;
    height = value;
    return my;
  };

  return my;
}

このパターンの要旨はチャートをクロージャとゲッター、セッターメソッドを使って実装するということです。実はこのパターンはD3で使われているscales, layouts, shapes, axes, などと同様のパターンなのです。

##実装

チャートは設定可能ですが、実は二つの重要な要素がまだ抜けています。DOMとデータです。これらも設定の一部とすることもできますが D3ではselectionを使うことでもっと自然になります。

selectionをインプットとして受け取ることでチャートもフレキシブルになります。 例えばチャートを複数のエレメントに同時に設定したり、複数のエレメント間で、明示的にバインドやリバインドすることなくチャートを動かしたりできます。

データや設定が変更された時にチャートがいつ、どのように変更されるかコントロールできるようになります(例えばすぐに更新するのではなくtransitionを使ったりします)。

selectionの中でチャートを呼び出す最も簡単な方法はselection自体を引数として渡すことです。

myChart(selection);

(注:APIの説明: [call]は指定された関数を一度だけ呼び出し、それに現在のselectionを渡します。 callオペレータは関数を明示的に呼び出すのと同じですがメソードチェーンを使うのをより容易にします)

もしくはselection.callを使うことも出来ます。

selection.call(myChart);

内部ではcallを使ったチャートの実装はこのようになります。

selection.eachを使うように実装することも可能ですが、 selection.callのほうがより一般的で brush や axis コンポーネントにも対応します。

function my(selection) {
  selection.each(function(d, i) {
    // generate chart here; `d` is the data and `this` is the element
  });
}

##例

このパターンをより実践的なものにするため、シンプルでありながら普遍的な時系列データの可視化をユースケースに用いてみましょう。

時系列データは時間によって変数が変わってきます。 x軸が時間、y軸が値を表すエリアチャートとして表すことができます。

2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010

このチャートを保持するための空のp(パラグラフ)エレメントを作ります。

<p id="example">

データは外部に CSV ファイル (sp500.csv)として作ります。最初の2〜3行はこんな感じです。

date,price
Jan 2000,1394.46
Feb 2000,1366.42
Mar 2000,1498.58
Apr 2000,1452.43
May 2000,1420.6
Jun 2000,1454.6
Jul 2000,1430.83
Aug 2000,1517.68

このチャートを作るために、 timeSeriesChart関数で新しいチャートインスタンスを作り、 x (date, a Date object) と y (price, a number)のアクセサーを指定します。

var chart = timeSeriesChart()
    .x(function(d) { return formatDate.parse(d.date); })
    .y(function(d) { return +d.price; });
var formatDate = d3.time.format("%b %Y");

price は+オペレータを使って明示的にナンバーに変換しています; JavaScript 弱い型付言語なので読み込まれたデータをストリングからナンバーに換えておいた方が良いでしょう。date はd3.time.format を使って変換します( %b は月の省略形を、%Y は4桁の年を意味します)。他のオプションを設定したい場合もあるでしょうが、このかんたんな例では高さ、幅、マージンのみ設定します。

最後にd3.csvを使ってデータを読み込みます。

空のパラグラフエレメントがあればそこにデータをバインドし、 selection.callでチャートを描画します。

(注:チャートの実装に興味がある方はtime-series-chart.jsをご覧下さい。)

d3.csv("sp500.csv", function(data) {
  d3.select("#example")
      .datum(data)
      .call(chart);
});

(注:インタラクティブな動きやアニメーションをサポートするにはaxis や brush のコンポーネントをご参照下さい)

##その他の留意点

データ可視化のためのたたき台がようやくできます。しかしながら本当はもっと多くのことをカバーする必要があります。伝統的なチャートのトポロジーを使うことは最適なことでしょうか? 「Grammar of Graphics ( Polychart や ggplot2を見て下さい)」にはチャートを作り上げるためのもっとモジュール的な単位があります。もっと伝統的なチャートタイプでさえ、内部のスケールを露出させるべきでしょうか、それとも隠蔽させるべきでしょうか? できあがったチャートはインタラクティビティやアニメーションを自動的にサポートするべきでしょうか? チャートの読み手があなたのチャートの振る舞いの一部を変更できるようにするべきでしょうか?そういったことは今回のパターンをつかって実装可能なのでぜひ試してみて下さい。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment