Visualforce Chartingで動的なグラフを作ってみる

Visualforceの標準コンポーネントを使ってグラフを作成できるVisualforce ChartingがWinter'13で正式リリースされました。
VFChartを使えば標準レポートやダッシュボードで作成できないグラフが自由自在?に作成できます。
ということで、今日のお題はVFChartです。

例えば「予実管理」なんかはよくある話で、予算に対して実績がどのくらい達成されているのかグラフやゲージで見たいという要件はよくあります。
ただ、これを標準ダッシュボードのゲージでやろうとすると、予算の数値はダッシュボードのコンポーネントに直接入力しなければならず、オブジェクトに登録されている値を予算として集計することは出来ません。
また、棒グラフにしても、標準レポートで作るとしたら、同じオブジェクトに予算と実績の値を持つ必要があります。
(厳密に言えば参照関係があれば大丈夫ですが、実質同じオブジェクトでやるのが現実的ですよね)

これをVFChartならば、予算の値をゲージに持ってくることもできるし、予算と実績の値が別オブジェクトだろうが関係ありません。
ApexやJavaScriptで集計した値を各グラフの縦軸や横軸プロットすることができるので、様々なグラフが作成できます。

こんなゲージとか

エリアチャートも思いのまま

動的なグラフ

さて、本題です。
ここでいう動的なグラフとは、ユーザーが入力した値に基づいて、その場でグラフの値がビヨーンと変わるやつです。
せっかくなのでSitesで公開してみました。
イメージとしてはユーザーが選択した年の月別売上を見るグラフです。
http://isanuki02-terrasky-developer-edition.na14.force.com/

サンプル画像

プルダウンのonchangeでクエリーを投げているせいかちょっとレスポンスが悪いですが、
まぁ、その辺はご愛嬌といったところで。

以下がコードです。
Visualforce

<apex:page controller="DynamicChartController"  showHeader="false" sidebar="false" readOnly="true" title="Dynamic Chart Sample">
<apex:form >
  <apex:selectList value="{!selectYear}" size="1" style="font-size:18px;">
    <apex:selectOptions value="{!yearList}"/>
    <apex:actionSupport event="onchange" action="{!change}" rerender="myChart"/>
  </apex:selectList>
</apex:form>

  <apex:chart height="400" width="700" data="{!Data}" resizable="true" id="myChart" theme="Purple">
    <apex:axis type="Numeric" position="left" fields="amount" title="金額" grid="true" maximum="1000000" />
    <apex:axis type="Category" position="bottom" fields="month" title="月">
    </apex:axis>
    <apex:barSeries orientation="vertical" axis="left" xField="month" yField="amount"/>
  </apex:chart>
</apex:page>

Controller

public with sharing class DynamicChartController {

    public static final Integer MONTHS_OF_YEAR = 12;
    public String selectYear {get; set;}
    public List<SelectOption> yearList {get; set;}

    public DynamicChartController(){
        yearList = createYearList();
        selectYear = yearList.get(0).getValue();
    }
    
    public List<SelectOption> createYearList(){
        List<SelectOption> options = new List<SelectOption>();
        // 商談から年のリストを生成
        for(AggregateResult result : [SELECT CALENDAR_YEAR(CloseDate) year 
                                      FROM Opportunity 
                                      WHERE IsWon = true 
                                      GROUP BY CALENDAR_YEAR(CloseDate)
                                      ORDER BY CALENDAR_YEAR(CloseDate)]){
            options.add(new SelectOption(String.valueOf(result.get('year')),
                                         result.get('year')+'年'));
        }
        return options;
    }

    public List<ChartData> getData(){
        List<ChartData> dataList = new List<ChartData>();
        Map<Integer, Double>resultMap = new Map<Integer, Double>();
        
        // 勝ち商談の月ごとの合計値を月=>金額のMapに入れる
        for(AggregateResult result : [SELECT CALENDAR_MONTH(CloseDate) month, SUM(Amount) sumAmount 
                                      FROM Opportunity 
                                      WHERE CALENDAR_YEAR(CloseDate) =: Integer.valueOf(selectYear) 
                                            AND IsWon = true
                                      GROUP BY CALENDAR_MONTH(CloseDate)
                                      ORDER BY CALENDAR_MONTH(CloseDate)]){
            resultMap.put(Integer.valueOf(result.get('month')), 
                          Double.valueOf(result.get('sumAmount')));
        }
        
        // グラフ表示用のデータを12ヵ月分作成することで、商談に売上がない月でもグラフを出す
        for(Integer m=1; m <= MONTHS_OF_YEAR; m++){
            ChartData tmpData = new ChartData(m);
            if(null != resultMap.get(m)){
                tmpData.amount = resultMap.get(m);
            } else {
                tmpData.amount = 0;
            }
            dataList.add(tmpData);
        }
        return dataList;
    }
    
    public PageReference change(){
        return null;
    }
    
    public class ChartData{
        public Integer month{get; set;}
        public Double amount{get; set;}
        public ChartData(Integer month){
            this.month = month;
        }
    }
}

また、標準レポートを使ってグラフを作った時に、「レコードが存在しない集計値の列はグラフが出ない」という問題があります。
例えば月別の売上グラフを出した時に、12月の売上がないとそもそも12月という集計値が存在しないので、標準レポートでは1月〜11月のグラフとして表示されます。
これを標準カスタマイズのみで解決する時は、苦肉の策として1月〜12月までの空データを作っておいたりして、難を逃れます。

これに対してVFChartであればController側の作り次第なので、12月の実績データがなくてもグラフにすることが可能です。
今回のサンプルでも2012年12月のデータは存在しませんが、グラフ表示用の空データを内部的に作っているので、きちんとグラフに0で表示されます。

そんなVisualforce Charting、ダッシュボードに表示することもできますので、標準では難しい要望がある時はぜひ使ってみましょう。

ちなみにこの記事はForce.com Advent Calendarに参加しています。
http://atnd.org/events/33649

他の方のエントリーもぜひ見てみてください。