/home/by-natures/dev*

ソフトウェア開発者として働く人の技術的なメモ

ExtJS: チャートでは複数ストアは扱えない

ブログは記事数が100を超えると意思を持ち始める…というそうですが、先ほどこのブログの記事数を数えたら100を優に超えていました。閲覧数上位が趣味エントリーで占めているのも頷けます(^_^; 本当はジャンルごとにブログを分けたほうがよいのですが…複数ブログを運営できるほど器用でもないのでしばらくはこのままの予定です。

さて、そんな中 ExtJS ネタ第3段です。前回と同様、ExtJS 4.2.0 を想定しています。

ExtJS はチャートが魅力的

ExtJS を使い始めるきっかけとして、動きが滑らかでカッコいいチャートに惹かれた方も多いのではないでしょうか。円グラフ・線グラフなど様々な種類があり、公式サンプルを眺めているだけで楽しくなります。

もちろん機能も豊富です。色や凡例の指定から、軸の細かい設定、各点をオンマウスした際のチップス制御など、かなり細かい制御が可能です。

使うには一癖も二癖もある

しかし機能豊富な半面、実際に利用するにはチャート機能の癖を理解しなければなりません。Ext JS はそれぞれの機能が独自の世界感をもっています。他の機能を使いこなせてもチャートを使うには1から出直す気分で挑みましょう。

ここではプロジェクトを通じて得た知見から、ExtJS のチャートを利用する上で注意すべき点として、チャートに割り当てるストアの注意点をご紹介します。

題材として、ExtJS 本家のサンプルを少し修正したものを掲載しておきます:

extjs_chart_line (2)

Ext.define('WeatherPoint', {
    extend: 'Ext.data.Model',
    fields: ['temperature', 'date']
});

var weatherStore = Ext.create('Ext.data.Store', {
    model: 'WeatherPoint',
    data: [
        { temperature: 21, date: new Date(2013, 9, 1) },
        { temperature: 18, date: new Date(2013, 9, 2) },
        { temperature: 19, date: new Date(2013, 9, 3) },
        { temperature: 18, date: new Date(2013, 9, 4) },
        { temperature: 15, date: new Date(2013, 9, 5) },
        { temperature: 13, date: new Date(2013, 9, 6) },
        { temperature: 21, date: new Date(2013, 9, 7) }
    ]
});

Ext.application({
  name: 'Chart sample',
  launch: function() {
    Ext.create('Ext.chart.Chart', {
      renderTo : Ext.getBody(),
      width    : 400,
      height   : 300,
      store    : weatherStore,
      axes: [{
        title    : 'Temperature',
        type     : 'Numeric',
        position : 'left',
        fields   : ['temperature'],
        minimum  : 0,
        maximum  : 35
      },{
        title      : 'Time',
        type       : 'Time',
        position   : 'bottom',
        fields     : ['date'],
        dateFormat : 'M d'
      }],
      series: [{
        type   : 'line',
        xField : 'date',
        yField : 'temperature'
      }]
    });
  }
});

チャート1つにストアは1つ

線グラフを利用する場合はこれが一番辛いです。

REST API で、各地域の日ごとの気温をサーバから値を取得する場合を考えます。普通は1地域1リクエストで取得するような API になるかと思いますので、地域ごとにリクエストを行うことになります。東京と大阪の気温をリクエストした結果、以下の2つのストアが得られたとしましょう(静的なストアですが、REST 経由でデータが得られたと考えてください):

var weatherTokyo = Ext.create('Ext.data.Store', {
    model: 'WeatherPoint',
    data: [
        { temperature: 21, date: new Date(2013, 9, 1) },
        { temperature: 18, date: new Date(2013, 9, 2) },
        { temperature: 19, date: new Date(2013, 9, 3) },
        ...
    ]
});

var weatherOsaka = Ext.create('Ext.data.Store', {
    model: 'WeatherPoint',
    data: [
        { temperature: 19, date: new Date(2013, 9, 1) },
        { temperature: 16, date: new Date(2013, 9, 2) },
        { temperature: 15, date: new Date(2013, 9, 3) },
        ...
    ]
});

ExtJS では、この2つのストアを直接チャートに指定することはできません。

モデルを結合して1つのストアにする

ExtJS のチャートには1つのストアしか割り当てられないため、以下のようにモデルを結合する必要があります。

[
  { temperatureTokyo: 21, temperatureOsaka: 19, date: new Date(2013, 9, 1) },
  { temperatureTokyo: 18, temperatureOsaka: 16, date: new Date(2013, 9, 2) },
  { temperatureTokyo: 19, temperatureOsaka: 15, date: new Date(2013, 9, 3) },
  ...
]

この状態でようやくチャートにストアが指定でき、片方の series の yField に temperatureTokyo, もう片方の yField に temperatureOsaka を指定することで、東京と大阪のそれぞれの気温を2つの棒グラフに表すことができます。

extjs_chart_lines

東京と大阪ではなく、任意の2地点の気温をグラフにしたいと思う場合、次のようなストア形式にする必要があります:

[
  { area1: 'Tokyo', area2: 'Osaka', temperature1: 21, temperature2: 19, date: new Date(2013, 9, 1) },
  { area1: 'Tokyo', area2: 'Osaka', temperature1: 18, temperature2: 16, date: new Date(2013, 9, 2) },
  { area1: 'Tokyo', area2: 'Osaka', temperature1: 19, temperature2: 15, date: new Date(2013, 9, 3) },
  ...
]

axes と series の指定はこんな感じです:

      axes: [{
        title    : 'Temperature',
        type     : 'Numeric',
        position : 'left',
        fields   : ['temperature1', 'temperature2'],
        minimum  : 0,
        maximum  : 35
      },{
        title      : 'Time',
        type       : 'Time',
        position   : 'bottom',
        fields     : ['date'],
        dateFormat : 'M d'
      }],
      series: [{
        type   : 'line',
        xField : 'date',
        yField : 'temperature1',
        title  : weatherStore.getAt(0).get('area1')
      },{
        type   : 'line',
        xField : 'date',
        yField : 'temperature2',
        title  : weatherStore.getAt(0).get('area2')
      }]

series の title に動的に値が入るところがポイントです。本来はAPI 経由でデータを取得する場合は title が分かりませんから、データ取得が終わったタイミングで series の title を書き変える処理が必要となります。

冗長な部分を改善

データがかなり冗長なので、これが嫌な場合は Ext.data.Model クラスの hasMany コンフィグを用いて、以下のように入れ子にしてもよいでしょう。ただしチャートに指定できるのは直接の値を保持するモデルだけなので、temperatures の中身だけしか指定できません。チャートには temperatures の中身だけ渡しつつ、凡例などには地域名を渡せるような実装上の工夫が必要です。

{
  area1: 'Tokyo',
  area2: 'Osaka',
  temperatures: [ 
                  { temperature1: 21, temperature2: 19, date: new Date(2013, 9,1) },
                  { temperature1: 18, temperature2: 16, date: new Date(2013, 9,2) },
                  { temperature1: 19, temperature2: 15, date: new Date(2013, 9,3) },
                  ...
                ]
}