Laravel上でVue.jsとChart.jsを利用してグラフを描いてみる

# はじめに
もう一度、自分なりに最初っから`vue`の環境を構築する手順をまとめてみようと思いました。
ついでに`chart.js`の導入と動くサンプルを乗っけてます。
素の`chart.js`を触ったことはありましたが、コンポーネント化することでいろいろと使い勝手が上がりそうな予感。
Laravelプロジェクトとして作成したけど、ぶっちゃけLaravel要素ほぼないですよね...まあ今後なにか使うときのベースになればなって感じです。

# 動くやつ
面倒が嫌いな人のために先に [**GitHub**](https://github.com/tosite0345/vue-chartjs-sample) を乗っけておきます。
今回はプロジェクト内に`Homestead`をインストールしているため、簡単にう、動きますよ。

## 動かすまでの手順
先に`Homestead`とか`composer`とか、`node.js`とかの環境は整っていることとします。

```bash:console
git clone https://github.com/tosite0345/vue-chartjs-sample.git

# PHP側パッケージファイルのインストール
composer install

# VM作成
vendor/bin/homestead make

# js側パッケージファイルのインストール
npm install
# 場合によってはこっち
# npm install --no-bin-links
# それでもうまくいかない場合は「導入までの完全な手順」からキャッシュ消去を試す

vagrant up
```

ちなみに`npm install`をローカルマシン側でやってるのは主に処理速度的観点からです。

### vagrant起動時にエラーが発生した場合
私の場合、`vagrant`起動時に次のようなエラーが出ました。

```
The following SSH command responded with a non-zero exit status.
Vagrant assumes that this means the command failed!

/sbin/ifdown 'eth1' || true
/sbin/ip addr flush dev 'eth1'
# Remove any previous network modifications from the interfaces file
sed -e '/^#VAGRANT-BEGIN/,$ d' /etc/network/interfaces > /tmp/vagrant-network-interfaces.pre
sed -ne '/^#VAGRANT-END/,$ p' /etc/network/interfaces | tac | sed -e '/^#VAGRANT-END/,$ d' | tac > /tmp/vagrant-network-interfaces.post
cat \
  /tmp/vagrant-network-interfaces.pre \
  /tmp/vagrant-network-entry \
  /tmp/vagrant-network-interfaces.post \
  > /etc/network/interfaces
rm -f /tmp/vagrant-network-interfaces.pre
rm -f /tmp/vagrant-network-entry
rm -f /tmp/vagrant-network-interfaces.post

/sbin/ifup 'eth1'

Stdout from the command:



Stderr from the command:

bash: line 5: /sbin/ifdown: No such file or directory
bash: line 19: /sbin/ifup: No such file or directory
```

```bash:console
vagrant ssh
# 以降の作業はVM内で行う
sudo apt-get install ifupdown
exit

# ローカルマシン側で作業
vagrant reload
```

[**こちら**](https://github.com/cilium/cilium/issues/1918) を参考にさせていただきました。


# 簡単な説明
## ルーティング
[**こ↑こ↓**](https://qiita.com/tosite0345/items/0f8aec4d987389c55766) をベースに`routes/web.php`を修正しました。
基本的にルーティングは`Laravel`ではなく`Vue`側に丸投げする感じで。
余談ですが恐らく`Vue`ルーティングより前に`Laravel`側のルーティングを記述すれば、例えばログインやランディングページだけは`blade`ファイルを利用して、その他のアプリケーション部分は`Vue`で処理して、的なことができるはずです。

## SPAのテンプレート画面作成
基本的には`resources/views/layouts/app.blade.php`をテンプレートとするが、あくまでも入り口だけ。
内部的なコンポーネントは全てVue側で捌く。

## Vue側

```
resources
+--assets
   +--js
   |   `--*chart                # Chart Templates
   |      +--*DoughnutChart.js
   |      `--*LineChart.js
   +--components                # Vue components
   |  +--*SampleChart.vue
   |  `--*Chart.vue
   +--*app.js
   +--*bootstrap.js             # 起動時の設定とか諸々？
   `--*routes.js                # 今回作成。ルートを記述
```

`*`つけてるのが変更したファイルです。

## Sample-1
![2018-05-08_17h02_09.png](https://qiita-image-store.s3.amazonaws.com/0/165355/caf4eabb-6b26-fb87-79e6-8fb748c01578.png)

まずは簡単な構成から。
`chart.js`でできることを無理やり`vue-chartjs`にさせてる感あるけどお茶を濁しておきます。
基本的なチャートを描く場合、他の`Vue`ファイルと違って`template`の記述が不要とのこと。ここで躓きました。
また、`chart.js`であれば`canvas`を用意してそこにデータを流し込んで...って手続きが必要だったのが、それも必要ないとのこと。
最終的にはグラフテンプレートを作って後からAPIやら何やらでデータを流し込む、って形になります。

```vuejs:resources/assets/js/components/SampleChart.vue
<script>
// ここでチャートの種類を選択して...
import { Bar } from 'vue-chartjs'
/**
 * Bar       : 棒グラフ
 * Line      : 折れ線グラフ
 * Doughnut  : ドーナツグラフ
 * Pie       : 円グラフ
 * Radar     : レーダーグラフ
 * Polararea : 極域グラフ
 * Bubble    : バブルグラフ
 * Scatter   : 散布図（mixinでは未対応とのこと）
 **/
export default {
  extends: Bar,
  mounted () {
    // データを流し込むだけ
    this.renderChart({
      labels: ['data-1','data-2','data-3'],
      datasets: [{
        label: 'sample',
        backgroundColor: ['#ddd', '#bbb', "#999S"],
        data: [300, 400, 150],
      }]
    })
  }
}
</script>
```

ちなみにデータセットについては [**公式**](https://misc.0o0o.org/chartjs-doc-ja/getting-started/usage.html) のほうが詳しいのでそちらをご参照ください。

## Sample-2
![2018-05-08_17h38_27.gif](https://qiita-image-store.s3.amazonaws.com/0/165355/dbea7814-2278-5213-b790-78667b22fa5b.gif)


いよいよここからVueの恩恵を感じていきます。
まずは`resources/assets/js/chart`内にチャートのテンプレートを作成します。
ここはjsのみで記述するため、拡張子は`.js`で大丈夫です。

その後、`resources/assets/js/components/Chart.vue`から呼び出してチャートを利用します。

```vuejs:resources/assets/js/components/Chart.vue
<template>
  <div class="small">
    <h1>vue-chartjs</h1>
    <div class="half">
      <!-- :chart-dataでバインディングを行う。多分オプションとかもバインディングできる？ -->
      <doughnut-chart :chart-data="doughnutcollection"></doughnut-chart>
    </div>
    <div class="half">
      <line-chart :chart-data="datacollection"></line-chart>
    </div>
    <div>
      <input type="text" v-model="lines">
      <button @click="fillData()">Repaint</button>
      <button @click="randomize()">Randomize</button>
    </div>
  </div>
</template>

<script>
import LineChart from   '../chart/LineChart.js'
import DoughnutChart from '../chart/DoughnutChart.js'

export default {
  components: {
    // ここで読んだコンポーネントをケバブケースにしたら普通に使えるっぽい
    LineChart,     /* <line-chart></line-chart> */
    DoughnutChart, /* <doughnut-chart></doughnut-chart> */

  },
  data () {
    return {
      datacollection: null,
      doughnutcollection: null,
      lines: 3,
    }
  },
  mounted () {
    this.fillData()
  },
  methods: {
    /**
     * 入力されたデータの数に応じてランダムなチャートデータを作成する
     */
    fillData () {
      var datasets = []
      var dDatasets = []
      var labels = []
      var val = 0
      var dLabels = []
      var dColors = []
      var min = 0
      var max = 0
      var loopCnt = this.lines
      for (var i = 0; i < loopCnt;i++) {
        labels = [this.getRandomInt(), this.getRandomInt()]

        if (max < labels[0]) {
          max = labels[0]
        }
        if (max < labels[1]) {
          max = labels[1]
        }

        datasets.push({
          label: 'data-' + (i + 1),
          backgroundColor: 'rgba(255,100,100,0.1)',
          data: labels,
        })

        dLabels.push('data-' + (i + 1))
        dDatasets.push(this.getRandomInt())
        var code = i * 20
        dColors.push('rgba(255,'+code+','+code+',0.4)')
      }
      this.datacollection = {
        labels: [min, max],
        datasets: datasets
      }
      this.doughnutcollection = {
        labels: dLabels,
        datasets: [
        {
          data: dDatasets,
          backgroundColor: dColors
        }
        ]
      }
    },
    /**
     * 1～10の間で適当にグラフデータ数を設定する
     */
    randomize () {
      this.lines = Math.floor(Math.random() * 10) + 1;
      this.fillData();
    },
    getRandomInt () {
      return Math.floor(Math.random() * (50 - 5 + 1)) + 5
    }
  }
}
</script>

<style>
.small {
  margin:  100px auto;
}
.half {
  width: 45%;
  display: inline-block;
}
</style>
```

ここ辺りで力尽きましたが、こんな感じでリアクティブにチャートを描画できる素晴らしさが伝わったんじゃないかと思います。


# 備忘録：環境構築までの完全な手順

```bash:console
composer create-project laravel/laravel chart "5.5.*"
cd chart

composer require laravel/homestead --dev
vendor/bin/homestead make
# Mac / Linux
# php vendor/bin/homestead make

npm install
# 環境によっては以下のオプションを付ける必要がある場合もある
# npm install --no-bin-links

# もしうまくいかない場合は次のコマンドを実行する
# フォルダーとキャッシュ消してnpmのバージョン上げてる
# rm -rf node_modules
# npm cache clean --force
# npm install -g npm

# その他パッケージのインストールなど
npm i -g cross-env
npm i vue-cli
npm i chart.js     # 基本的にはvue-chartjsをインストールしたらついてくるはず
npm i vue-chartjs

vagrant up
```

# 参考
+ [**vue-chartjs**](http://vue-chartjs.org/#/home)
+ [**Chart.js 日本語ドキュメント**](https://misc.0o0o.org/chartjs-doc-ja/)
+ [**Chart.js - doughnutサンプル**](https://qiita.com/K3_NoFish/items/5d2247ea08f96e59fb69)