Analytics APIとHighchartsでグラフを作ろう

この記事はForce.com Advend Calender 2013 9日目エントリーとなります。
http://atnd.org/events/45110

今年のネタを考えるにあたって、去年は何を書いたのかなーと調べてみたら「項目セット」と「Visualforce Charting」について書いていました。
グラフネタと言えば、Winter'14からはAnalytics API(日本語名:分析 API)が公開されているのを思い出しました。
ということで、今年はAnalytics APIを使ってグラフを作成してみます。
グラフはJavaScriptのライブラリであるHighchartsを使う事にします。

まず先にVisualforceで作った完成系のイメージです。

左上にプルダウンを作って、集計値を動的に変更できるようにしてみました。

Salesforceの設定でレポートを作る

さて、ここから作り方です。
まずはベースとなるレポートを作る必要があります。
今回は商談のマトリックスレポートで、行を取引先名、列をフェーズにし、金額、期待収益、レコード数を集計するレポートにしました。

Visualforceで画面を作る

Visualforceの全体は最後にして、ここからはポイントとなるところを見ていきましょう。
今回使ったのはjQueryHighchartsForce.com-JavaScript-REST-Toolkitのライブラリです。
Force.com-JavaScript-REST-Toolkit 通称ForceTKはRESTベースであるAnalytics APIを使う時に便利です。
jQuery以外は静的リソースに置いて、それを読み込んでいます。

<apex:includeScript value="//code.jquery.com/jquery-1.10.1.min.js" />
<apex:includeScript value="{!$Resource.highcharts}" />
<apex:includeScript value="{!$Resource.forcetk}" />

Analytics APIで対象とするレポートIDですが、今回はベタで書いています。実際はURLパラメータで受け取ったり、カスタム表示ラベルに持たせる等するのが良いでしょう。
続いてREST APIでアクセスするためにForceTKを使ってセッション情報をセットします。

<script type="text/javascript">
  $j = jQuery.noConflict();
  var repot = null;

  $j(document).ready(function(){
    var reportId = '00OA00000058IIx'; //マトリックスレポートID
    var client = new forcetk.Client();
    client.setSessionToken('{!$Api.Session_ID}');

ForceTKのclientからAnalytics APIのエンドポイントを指定してJSON形式のレスポンスを受け取ります。
Analytics APIにはいくつかエンドポイントがあり、describeを指定してレポートのメタデータを受け取ったり、サマリーレベルの集計を受け取ったり、rowレベルのデータを受け取ったり、あるいは同期/非同期を選択することも可能です。

    client.ajax("/v29.0/analytics/reports/"+reportId+"?includeDetails=true", function(response){
      report = response;

実際に作ってみる時にはAnalytics APIの開発者ガイドを参照してみてください。
そのうち日本語化されるかもしれませんが、今日時点では本家USのdeveloperforceサイトに英語版が置いてあります。

次に左上に表示する集計値の選択リストを表示するために、aggregatesから集計項目を取ってきます。
また、選択リストのonChangeイベントとしてグラフ作成用のfunctionをセットしておきます。

      $j.each(report.reportMetadata.aggregates, function(index, agg) {
        $j("#selectAgg").append('<option value="'+index+'">' +
          report.reportExtendedMetadata.aggregateColumnInfo[agg].label +
          '</option>');
      });
      $j("#selectAgg").change(createReport);

      createReport();
    });
  });

グラフ生成用functionではまずX軸を生成します。今回で言うと取引先名ですね。
レポートの行となる集計はgroupingsDownから取得できます。
今回は1階層となるマトリックスですが、複数階層のサマリー形式レポートの場合も、groupingsDownを辿って行くことで情報を取得できます。

  var createReport = function(){
      var series = [];
      var category =[];
      
      // X軸のカテゴリを生成
      $j.each(report.groupingsDown.groupings, function(index, grouping){
        category.push(grouping.label);
      });

画面左上の集計値選択プルダウンから、ユーザが選択した集計値を取得します。
集計値の名称等の情報はreportExtendedMetadata.aggregateColumnInfoに入っています。

      // 選択値から集計値情報を取得
      var aggIndex = $j('#selectAgg').val();
      var columnInfo = report.reportExtendedMetadata.aggregateColumnInfo[report.reportMetadata.aggregates[aggIndex]];

次に実際の集計値が入っているfactMapから値を取り出します。
factMapは縦横1階層のマトリックスレポートで言うと、左上が"0!0"、そこから下に"0!1","0!2"..というキーで値を持っています。
左上から右に行くと"1!0","2!0"...となり、合計行は"T!T"となります。
途中端折っていますが、イメージ的には以下の感じですね。

グルーピングがネストすると、"0_1!T"とか"2_2!3-1"のようになるようです。
そのへんの詳細は開発者ガイドをご確認ください。

ということで、Highcharts用のdataを生成するために、縦横をループで回してデータを作っています。

      // マトリックスの縦と横を回しながら集計値を取得
      $j.each(report.groupingsAcross.groupings, function(i1, g1){
  
        series.push(new Object());
        series[i1].name = g1.value;
        series[i1].data = [];

        $j.each(report.groupingsDown.groupings, function(i2, g2){
          series[i1].data.push(report.factMap[g2.key.toString()+"!"+g1.key.toString()].aggregates[aggIndex].value);
        });
      });

後はグラフを生成して終わりです。
デザイン部分はHighchartsのDemoサイトからパクってきたのをコピペして、titleとかxAxis、yAxisのtitle、seriesあたりに作っておいた値をセットしています。

      //グラフ生成
      $j('#container').highcharts({
        chart: {
          type: 'column'
        },
        title: {
          text: columnInfo.label + 'のグラフ'
        },
        xAxis: {
          categories: category
        },
        yAxis: {
                min: 0,
                title: {
                  text: columnInfo.label
                },
                stackLabels: {
                  enabled: true,
                  style: {
                      fontWeight: 'bold',
                      color: (Highcharts.theme && Highcharts.theme.textColor) || 'gray'
                  }
              }
            },
            legend: {
              align: 'right',
              x: -70,
              verticalAlign: 'top',
              y: 20,
              floating: true,
              backgroundColor: (Highcharts.theme && Highcharts.theme.legendBackgroundColorSolid) || 'white',
              borderColor: '#CCC',
              borderWidth: 1,
              shadow: false
            },
            tooltip: {
              formatter: function() {
                  return '<b>'+ this.x +'</b><br/>'+
                      this.series.name +': '+ this.y +'<br/>'+
                      'Total: '+ this.point.stackTotal;
              }
            },
            plotOptions: {
              column: {
                  stacking: 'normal',
                  dataLabels: {
                      enabled: true,
                      color: (Highcharts.theme && Highcharts.theme.dataLabelsColor) || 'white'
                  }
              }
            },
        series: series,
      });
  }

全体のソースコードはこちらに置いてあります。
https://gist.github.com/isanuki/7839374

補足

Analytics APIはレポートから結果を取得するので、通常のAPI制限に加えてレポート系の独自な制限がかかります。

  • 最近参照した 200 個までのレポートを取得できます。
  • 詳細レコードデータを含む、レポートの最初の 2000 行の結果を取得できます。条件を使用して結果を絞り込み、最も有益な結果を取得できます。
  • レポートの実行時に20 個までのカスタム項目条件を追加できます。これには、ソースレポートに保存された既存のカスタム項目検索条件が含まれます。
  • 列として含まれる項目数が 100 個までのレポートの要求を送信できます。
  • 非同期レポート結果へのアクセスは、24 時間あたり、任意の回数実行できます。
  • データを絞り込む場合、クロス条件と標準レポート条件は使用できません。

※Winter'14 リリースノートより抜粋

開発者ガイドにはもっと細かく記載されています。
これら制限もそうですが、当然ながら組織のAPIコール数の制限にもカウントされるので、Analytics APIを使う場合はこの辺も頭に入れて開発しましょう。
APIコール数の制限緩和を期待しつつ・・)

あと、今回使ったHighchartsライブラリは商用利用する場合はライセンスを購入する必要があります。
この辺、詳しくは公式サイトをご確認ください。

まとめ

Analytics APIを使うことでアプリケーション側で分析する部分を作らないで済むのは便利ですし、モバイルアプリケーションでグラフを作る際にもRESTベースのAnalytics APIでレポートから結果を受け取り、グラフを表示することが簡単にできます。
また、様々なJavaScriptのライブラリを使うことで、よりリッチなグラフにすることも可能です。
一昔前は「Salesforceは分析系が弱い」という声もありましたが、これらAPIを使うことで、より柔軟に開発が出来そうですね。
ぜひ、使ってみてください。