AngularJS + Typescript で Google Chart Tools を使う (ColumnChart)

こんにちは！今回はGoogle Chart Toolsで棒グラフ(Column Chart)を書いてみます。

完成形は公式のサンプルにある、これ！

![columnChartSample.png](https://qiita-image-store.s3.amazonaws.com/0/53473/3ad3db53-4080-134c-e4c2-3ece3dcc004a.png)


# Google Chart Tools とは

(ざっくりですが)多様なチャートを簡単に描画できるようにGoogleが提供しているツール群です。

[ココ](https://developers.google.com/chart/)が公式です。
で、どんなチャートが書けるのかは[Chart Gallery](https://developers.google.com/chart/interactive/docs/gallery)を見てみてください。
これだけあればチャートに関しては困ることはないだろうと思われるほど、多様なチャートを描画することができます。

しかも簡単に！

さらにオプションも充実しているので、線の太さや色なんかはもちろんのこと
軸の設定や、折れ線グラフなんかの点(ポイント)の形や大きさ、ツールチップ表示等
本当に色々なところを自分好みにいじることができます。

英語ですがドキュメントが非常に充実していて、サンプルコード(JS)もあるので「本当に使いやすいな」という印象です。

早速ですが、今回のお題である[Column Chart](https://developers.google.com/chart/interactive/docs/gallery/columnchart?hl=ja)を書いてみましょう！

# 事前準備

* GitBash (所々出てくるコマンドを実行する為。他でもいいですが、オススメです)
* Node.js (typescriptをインストールする為にnpmを使うので)
 * npmを使わないインストール方法をとるのであれば必須ではありません。
* Typescript (Typescriptで書くので必須)
* tsd (型定義ファイルをインストールするのに使います)

これらの導入に関しては割愛させて頂きますm(_ _)m
Web検索を駆使、もしくは[このへん](http://qiita.com/kenichi_nakamura/items/e22ed3423b99b2105769#%E4%BA%8B%E5%89%8D%E6%BA%96%E5%82%99)を参照して頂ければと思います。

# 今回のプロジェクトの構成(最終形)

折角AngularJSなのでService, Controller, Directiveに分けてつくってみたいと思います。

```
google-chart-tools-sample
　┣ typings                  // tsdでインストールされた型定義ファイルたち
　┣ app.js                   // コンパイルで生成されるjs
　┣ app.ts                   // moduleの登録等を行う
　┣ ColumnChartController.ts // コントローラークラス
　┣ ColumnChartDirective.ts  // ディレクティブクラス
　┣ ColumnChartService.ts    // サービスクラス
　┣ index.html
　┗ tsd.json                 // tsdの設定ファイル。"$tsd init"で生成します。
```

# Column Chart を描く！

## 型定義ファイルのインストール

今回は```angularjs```と```google.visualization```の型定義ファイルが必要です。

* angularjsの型定義インストール

```
$ tsd query angular -rosa install
```

* google.visualizationの型定義ファイルのインストール

```
$ tsd query google.visualization -rosa install
```

これで"typings"ディレクトリが作成されて、その中に型定義ファイルたちがいるはずです。

## サービスクラスの作成

今回サービスクラスでは、チャートのオプション設定やデータ部分を書きます。
こんな感じ。

```ColumnChartService.ts
namespace app.service {
  "use strict";

  export class ColumnChartService {

    private data: google.visualization.DataTable;

    private options: any = {
      title: "Motivation and Energy Level Throughout the Day",
      focusTarget: "category",
      hAxis: {
        title: "Time of Day",
        format: "h:mm a",
        viewWindow: {
          min: [7, 30, 0],
          max: [17, 30, 0]
        },
        textStyle: {
          fontSize: 14,
          color: "#053061",
          bold: true,
          italic: false
        },
        titleTextStyle: {
          fontSize: 18,
          color: "#053061",
          bold: true,
          italic: false
        }
      },
      vAxis: {
        title: "Rating (scale of 1-10)",
        textStyle: {
          fontSize: 18,
          color: "#67001f",
          bold: false,
          italic: false
        },
        titleTextStyle: {
          fontSize: 18,
          color: "#67001f",
          bold: true,
          italic: false
        }
      }
    };

    constructor() {
      this.createData();
    }

    public getOptions(): any {
      return this.options;
    }

    public getData(): any {
      return this.data;
    }

    private createData() {
      this.data = new google.visualization.DataTable();
      this.data.addColumn("timeofday", "Time of Day");
      this.data.addColumn("number", "Motivation Level");
      this.data.addColumn("number", "Energy Level");

      this.data.addRows([
        [{v: [8, 0, 0], f: "8 am"}, 1, .25],
        [{v: [9, 0, 0], f: "9 am"}, 2, .5],
        [{v: [10, 0, 0], f: "10 am"}, 3, 1],
        [{v: [11, 0, 0], f: "11 am"}, 4, 2.25],
        [{v: [12, 0, 0], f: "12 pm"}, 5, 2.25],
        [{v: [13, 0, 0], f: "1 pm"}, 6, 3],
        [{v: [14, 0, 0], f: "2 pm"}, 7, 4],
        [{v: [15, 0, 0], f: "3 pm"}, 8, 5.25],
        [{v: [16, 0, 0], f: "4 pm"}, 9, 7.5],
        [{v: [17, 0, 0], f: "5 pm"}, 10, 10],
      ]);
    }
  }
}
```

データ(data)とかオプション(options)は、公式のサンプルからそのまま持ってきました。

## コントローラークラスの作成

サービスを使ってチャート描画部分を担当するコントローラーを作ります。

```ColumnChartController.ts
namespace app.controller {
  "use strict";

  export class ColumnChartController {

    constructor(private columnChartService: app.service.ColumnChartService) {
    }

    public draw(element: Element) {
      new google.visualization.ColumnChart(element).draw(
        this.columnChartService.getData(), this.columnChartService.getOptions());
    }

  }

}
```

先に作成したサービスクラスを使うのでコンストラクタの引数にとります。後ほど注入します。
メソッドはチャート描画をする為の"draw(element: Element)"があるのみです。

## ディレクティブクラスの作成

表示部分を担当してもらいます。

```ColumnChartDirective.ts
namespace app.directive {
  "use strict";

  export class ColumnChartDirective implements ng.IDirective {

    public restrict: string;
    public replace: boolean;
    public scope: any;
    public controller: any;
    public controllerAs: string;
    public link: (
      scope: ng.IScope,
      element: ng.IAugmentedJQuery,
      attrs: ng.IAttributes,
      ctrl: app.controller.ColumnChartController) => void;

    constructor() {
      this.init();
    }

    public static Factory(): ng.IDirectiveFactory {
      let directive = () => {
        return new ColumnChartDirective();
      };
      directive.$inject = [];
      return directive;
    }

    private init() {
      this.restrict = "A";
      this.replace = false;
      this.scope = {
      };
      this.controller = ["ColumnChartService", app.controller.ColumnChartController];
      this.controllerAs = "columnChartController";
      this.link = (
        scope: ng.IScope,
        element: ng.IAugmentedJQuery,
        attrs: ng.IAttributes,
        ctrl: app.controller.ColumnChartController) => {
          ctrl.draw(element[0]);
      };
    }
  }
}
```

今回は"restrict = "A";"を指定し、属性として使うようにしています。

"Controller"には、先に作成したColumnChartControllerクラスを指定し、
その際ColumnChartServiceを注入しています。

"link"で、そのコントローラーのdrawメソッドをコールしてチャートを描画しています。

## モジュールを定義する

必要なクラスは揃ったのでAngularのmoduleを定義します。

```app.ts
/// <reference path="./typings/angularjs/angular.d.ts" />
/// <reference path="./typings/google.visualization/google.visualization.d.ts"/>
/// <reference path="./ColumnChartService.ts" />
/// <reference path="./ColumnChartDirective.ts" />
/// <reference path="./ColumnChartController.ts" />

"use strict";

google.load("visualization", "1", {
  packages: ["corechart"]
});

angular.module("app", [])
.service("ColumnChartService", app.service.ColumnChartService)
.directive("columnChart", app.directive.ColumnChartDirective.Factory());
```

"app"という名前でモジュールを定義しています。
そのモジュールに".service"でサービスの登録、".directive"でディレクティブを、それぞれ
"ColumnChartService"、"columnChart"という名前で登録しています。

ディレクティブの名前は「先頭小文字のキャメルケース」というルールがあるので、
サービスクラスとは違いそのような名前をつけています。

"google.load～"部分ではgoogle.visualization APIをロードしています。

で、先頭部分では必要な型定義ファイルや、作成したクラスを参照する為に"\<reference path～\>"を記述しています。

## tsファイルをコンパイルする

作成したtsファイルをコンパイルして、index.htmlで読み込むjsファイルを生成します。
今回は全ての"*.ts"ファイルをまとめて"app.js"という１つのjsとして生成することにします。

```
$ tsc --out app.js ./*.ts --target es5 --module commonjs --noImplicitAny --removeComments
```

上記コマンドを実行後、同階層に"app.js"が出力されるはずです。

## index.htmlを作成する

ここまできたら準備は完了。あとはindex.htmlを作成すれば完成です。

```
<!DOCTYPE html>
<html ng-app="app">
  <head>
    <title>Column Chart Sample</title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js"></script>
    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
    <script src="./app.js"></script>
  </head>
  <body>
    <div column-chart></div>
  </body>
</html>
```

"\<head\>"内で必要なスクリプトを読み込ませます。
"\<body\>"部にはディレクティブ名を"\<div\>"の属性として書くだけです。

ディレクティブを属性としてhtml中に書くときは、キャメルケースの区切り部分を
"-"で区切って指定するという決まりになっています。
"app.ts"にてディレクティブを登録した際は、名前として"columnChart"という名前で登録しましたが、
この場合は"column-chart"と指定します。

## 動かしてみる！

ここまできたら、あとはindex.htmlをブラウザで表示させてみて下さい。

![columnChartSample.png](https://qiita-image-store.s3.amazonaws.com/0/53473/3ad3db53-4080-134c-e4c2-3ece3dcc004a.png)

これ、でましたか！？

今回のコードは、[github](https://github.com/nakamu-mu/google-chart-tools-sample)に置きました。

他のチャートも、基本的に同じ要領で描画できるはずです。
次は複合チャート(Combo Chart)をやってみようかと思います。