chart.js を angular の component の中で使う

## はじめに

WEBアプリケーションでフロントエンドの開発をしていると、グラフ表現 <small>（※離散数学のグラフではない、線グラフや棒グラフを指しています）</small> を組み込みたいことが出てきます。
しかし、グラフは一般的にスクラッチで作成すると大変なので（軸の設定とか線の見やすさとか、考えることが多いです）、既存のライブラリを使用して、設定や css で少しいじるくらいで上手いこと綺麗に表示されて欲しいです。
そこで、 [chart.js](https://github.com/chartjs/Chart.js) を angular で作られたプロダクトに組み込む方法を考えます。

### 書いてあることと書いていないこと

#### 書いてあること

- angular（>= 6.0.0） で作られたプロダクトに chart.js のグラフを組み込む方法
- （おまけ）angular（>= 6.0.0） で作られたプロダクトに vis.js のグラフ（離散）を組み込む方法

#### 書いてないこと

- chart.js や vis.js などのライブラリの使用方法（それぞれの公式ドキュメントを参照してください）

### 前提

- angular（>= 6.0.0） を採用したプロダクトが既にエラーなく稼働すること
- chart.js （や、おまけのセクションでは vis.js）の使い方を知っていること
- es6 の記法や systemjs/webpack などの基本的な知識を有していること
- typescript を採用していること
- DOM操作が（推奨、非推奨は別として）行える環境であること
- angular で DOM操作を行うプログラムを書いたことがない、または知識を有さないこと

## 設定方法

### インストール

chart.js を npm経由でインストールします。

```bash
# 本体
$ npm install --save chart.js

# typescript用の型定義
$ npm install --save-dev @types/chart.js
```

### コンポーネントの作成

chart.js は `Chart` という class を export しているので、これをコンポーネント中で import してあげます。

```ts
import {Component} from '@angular/core';

import { Chart, ChartData, ChartOptions } from 'chart.js'; // <-- 追加

@Component({
  ...
```

これにより、コンポーネント中の関数の中で `Chart` という class が使用可能になります。
また、 `ChartData`、`ChartOptions` も同時に import しています。
これらは、`Chart`のコンストラクタに渡すデータの型（class）です。
外部のライブラリはよくどういうオプションを指定するのか忘れてしまいがちですが、typescript と vscode などを使うとヒントが表示されて非常に便利ですね。

ちなみに、この記事を書いた当初は polyfils.ts に書いて別途 declare していましたが、不要になりました。

さて、コンポーネントの定義ファイル全体は以下のようになります：

```ts
import { Component, AfterViewInit, ViewChild, ElementRef, Input } from '@angular/core';

// Chartオブジェクトを使うために宣言する
import { Chart, ChartData, ChartOptions } from 'chart.js';

@Component({
  selector: 'app-chart',
  template: `
    <canvas #canvas width="450" height="450"></canvas>
  `
})

export class ChartComponent implements AfterViewInit {

  @ViewChild('canvas')
  ref: ElementRef;

  @Input()
  data: ChartData;

  @Input()
  options: ChartOptions;

  context: CanvasRenderingContext2D;
  chart: Chart;

  constructor(private _elementRef:ElementRef) {}

  // View が初期化された後でないと DOM の取得に失敗した
  ngAfterViewInit() {

    // canvasを取得
    this.context = this.ref.nativeElement.getContext('2d');

    // チャートの作成
    this.chart = new Chart(this.context, {
      type: 'doughnut',     // とりあえず doughnutチャートを表示
      data: this.data,      // データをプロパティとして渡す
      options: this.options // オプションをプロパティとして渡す
    })

  }

}
```

`Input`として渡している `data` と `options` はそれぞれ例えば以下のような構造のデータです。

```typescript
    this.data = {
      labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
      datasets: [{
        label: '# of Votes',
        data: [12, 19, 3, 5, 2, 3],
        backgroundColor: [
            'rgba(255, 99, 132, 0.2)',
            'rgba(54, 162, 235, 0.2)',
            'rgba(255, 206, 86, 0.2)',
            'rgba(75, 192, 192, 0.2)',
            'rgba(153, 102, 255, 0.2)',
            'rgba(255, 159, 64, 0.2)'
        ],
        borderColor: [
            'rgba(255,99,132,1)',
            'rgba(54, 162, 235, 1)',
            'rgba(255, 206, 86, 1)',
            'rgba(75, 192, 192, 1)',
            'rgba(153, 102, 255, 1)',
            'rgba(255, 159, 64, 1)'
        ],
        borderWidth: 1
      }]
    };

    this.options = {
      scales: {
        yAxes: [{
          ticks: {
            beginAtZero:true
          }
        }]
      }
    };
```

※ elementRef について
https://angular.io/docs/ts/latest/api/core/index/ElementRef-class.html

つまり、ここでの実装は nativeElement が取得できるという点に依存しているため、それが取得できない web worker などでは機能しません。
その場合どうすればよいのかは（僕の中では）今のところ結論が出ていないので、直接DOM操作を行わないようなライブラリを探してきた方が早い気がします。

## おまけ

僕はグラフと言えば離散数学のグラフを思い浮かべるタイプの人間なので、その可視化を行う [vis.js](http://visjs.org/) というライブラリについても触れます。

基本的には chart.js と同じような手順で上手くいくはずですが、 polyfills.ts に import文を書いても `vis`オブジェクトが使用できませんでした。
該当箇所をソースコードから引っ張ってこれなかったのですが、グローバル領域に `vis`オブジェクトが定義される形になっていない気がします。
Network や DataSet などのオブジェクトは export されているので、直接これらのオブジェクトを呼び出せば良いかもしれません。

取り急ぎ、`vis`オブジェクトを呼び出すための解決策として、`index.html` に

```html
<script src="/assets/vis/dist/vis.min.js"></script>
```

を追加し、コンポーネントの定義ファイルで `declare const vis` としたら呼べました。
後は DOM操作でグラフ（ネットワーク）を埋め込んでいるので、上記のように nativeElement を呼び出して描画できました。

## まとめ

angular(>= 6.0.0) で chart.js（や、vis.js）のグラフを描画するための方法を考えました。