vue-chart.jsでオークションの折れ線グラフを描画する


![スクリーンショット 2019-04-10 9.12.05.png](https://qiita-image-store.s3.amazonaws.com/0/224974/1e432424-d07a-ca23-aa1f-d0f331b05601.png)

entershareでWEBエンジニアをやってるナツキです。

現在開発中のサービスで、オークションシステムを実装しています。
完成度の高いグラフを一から作成するのは、結構辛いので、ライブラリを探していたところ、chart.jsを見つけました。

今回の記事では、
・vue-chartjsの導入方法
・折れ線グラフを描画するのに必要なパラメーター
・知ってると便利な小技
をメインに説明していきます。


↓　こんなグラフを作れます。（chart.js公式ページから引用）
![スクリーンショット 2019-04-10 9.12.14.png](https://qiita-image-store.s3.amazonaws.com/0/224974/1d13b138-b88a-612b-6df5-6a216d1eb649.png)


#概要
###メインで使用するライブラリ
chart.js
vue-chartjs

###プロジェクトについて
Rails 5系にwebpackerを入れてvueを動かしています。

###この記事でカバーしないこと
・API側の実装
・vue.js/Axiosの挙動
・折れ線グラフ以外の機能の細かい説明

###参考記事

・にほんご。
https://tr.you84815.space/
いろんな英語のドキュメントが記載されているウェブサイト
とてもわかりやすい。（ウェブサイトもかわいい）

・Kapibara Tech Blog
https://kapibara-sos.net/archives/597
Chart.jsで作成する折れ線グラフの実例
カスタマイズの方法が詳しく乗っています。
とても丁寧です。

・github
https://github.com/chartjs/Chart.js

・vue-chart.jsのドキュメント
https://vue-chartjs.org/guide/#introduction

#chart.jsとは
chart.jsは、チャートやグラフを描画することに特化されたJavaScriptのライブラリです。
githubでは、2019年４月の時点で42000個星がついています。
このライブラリを使うと、簡単に＆綺麗にウェブサイトに円グラフや棒グラフのような図を挿入することができます。

samples
https://www.chartjs.org/samples/latest/

###vue-chartjsとは
vueのコンポーネントでchart.jsを利用するためのラッパーです。
vueアプリの中でより直感的に、チャートを作成できます。

あくまでもラッパーなので、導入が終わって実際にカスタマイズしていくときは、chart.jsの資料を読むことの方が多いです。

###Chart.jsに似ているライブラリ
20 best JavaScript charting libraries
https://thenextweb.com/dd/2015/06/12/20-best-javascript-chart-libraries/

この中でも、Chartist.jsというライブラリは、デザイン性も高く、パッと見た感じchart.jsよりもシンプルにかけそうです。

#chart.jsの導入方法
*vue-chart.jsのドキュメントからの引用。

1. パッケージのインストール

```
yarn add vue-chartjs chart.js
```
または、

```
npm install vue-chartjs chart.js --save
```

サクッとCDN経由で導入して見たい方は、

```
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.min.js"></script>
<script src="https://unpkg.com/vue-chartjs/dist/vue-chartjs.min.js"></script>
```

#vue-chartjsの例
*vue-chart.jsのドキュメントからの引用。

まず適当な折れ線グラフを作ってみます。

LineChart.js

```
import { Line, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins

export default {
  extends: Line,
  mixins: [reactiveProp],
  props: ['options'],
  mounted () {
    // this.chartData is created in the mixin.
    // If you want to pass options please create a local options object
    this.renderChart(this.chartData, this.options)
  }
}
```


RandomChart.vue 

```
<template>
  <div class="small">
    <line-chart :chart-data="datacollection"></line-chart>
    <button @click="fillData()">Randomize</button>
  </div>
</template>

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

  export default {
    components: {
      LineChart
    },
    data () {
      return {
        datacollection: null
      }
    },
    mounted () {
      this.fillData()
    },
    methods: {
      fillData () {
        this.datacollection = {
          labels: [this.getRandomInt(), this.getRandomInt()],
          datasets: [
            {
              label: 'Data One',
              backgroundColor: '#f87979',
              data: [this.getRandomInt(), this.getRandomInt()]
            }, {
              label: 'Data One',
              backgroundColor: '#f87979',
              data: [this.getRandomInt(), this.getRandomInt()]
            }
          ]
        }
      },
      getRandomInt () {
        return Math.floor(Math.random() * (50 - 5 + 1)) + 5
      }
    }
  }
</script>

<style>
  .small {
    max-width: 600px;
    margin:  150px auto;
  }
</style>

```

ピンクの折れ線グラフが２つ表示されたら成功です。
Randomizeを押すと、リアルタイムでチャートが変化するのも確認してください。

datasetsというところで、折れ線グラフのデータを挿入してます。
dataのセットが２つあるように、グラフも２つできます。

#基礎知識

chart.jsに渡す重要なパラメーターは主に3つです。

type: 何グラフなのか
data:　グラフ内のデータ
options:　グラフの仕様

この３つを理解すれば、chart.jsは攻略したと言っても過言ではないです。
vue-chartjsだと、typeがコンポーネントとして最初から用意されているので、パラメーターとして渡すという感覚はないかもしれません。


#応用事例
僕のコードを解説しながら、もう少し深掘りしていきます。
間違っていたら申し訳ありません。

line_chart.js

```
import { Line, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins

export default {
  extends: Line,
  mixins: [reactiveProp],
  props: ['options'],
  mounted () {
    this.renderChart(this.chartData, this.options)
  }
}

```


auction.vue

```
<template>
  <div class="container">
     <div class="row">
       <div class="col-12 col-sm-4">
          <line-chart
              v-if="loaded"
              :chart-data="datacollection"
              :options="options"
            />
          </line-chart>
       </div>
     </div>
  </div>
</template>

<script>
  import axios from 'packs/axios'
  import LineChart from 'packs/plugins/line_chart.js'
  import AuctionData from 'packs/data/chart.json'

  export default {
    components: {
      LineChart
    },
    data () {
      return {
        loaded: false,
        datacollection: null,
        options: null,
        item: null,
      }
    },
    created () {
      this.loaded = false
      // item_idの情報をrails経由で取得
      const element = document.getElementById("auction")
      const data = JSON.parse(element.getAttribute('data'))

      axios.get(`../api/v1/auctions_test/${data.itemId}.json`)
      .then(res => {
        this.fillData(res.data)
        this.fillOption(res.data)
        this.item = res.data.item
        this.loaded = true
      }).catch(error => {
        // エラーハンドリングを挿入予定
        this.loaded = true
      });
    },
    methods: {
      fillData (AuctionData) {
        let marks = AuctionData.marks
        let labels = []
        let values = []
        let predicted_values = []

        marks.forEach(function(el, index){
          labels.push(el.time)
          values.push(el.value)
          if((marks.length - 1) == index){
            predicted_values.push(el.value)
          }else{
            predicted_values.push(null)
          }
        })

        values.push(null)
        labels.push(AuctionData.predicted_mark.time)
        predicted_values.push(AuctionData.predicted_mark.value)

        this.datacollection = {
          labels: labels,
          datasets: [
            {
              lineTension: 0,
              label: AuctionData.content.label,
              borderColor: "orange",
              backgroundColor:"rgba(255,165,0,0.2)",
              data: values
            },
            {
              lineTension: 0,
              label: AuctionData.content.label,
              backgroundColor: "transparent",
              borderColor: "orange",
              borderDash: [5,5],
              data: predicted_values
            }
          ]
        }
      },
      fillOption (AuctionData) {
        this.options = {
          legend: {
            display: false,
          },
          tooltips: {
            callbacks: {
              title: function(tooltipItem, data){
                return tooltipItem[0].xLabel + "yo"
              },
              label: function(tooltipItem, data){
                return [tooltipItem.yLabel + "huga"]
              }
            }
          },
          scales: {
            bounds: "ticks",
            xAxes: [{
                type: 'time',
                time: {
                    min: AuctionData.marks[0].time,
                    max: AuctionData.predicted_mark.time,
                    unit: 'hour'
                },
                distribution: 'series',
                ticks: {
                    source: "auto",
                    autoSkip: false
                }
            }],
            yAxes: [{
                ticks: {
                    beginAtZero:true
                }
            }]
          }
        }
      }
    }
  }
</script>

<style scoped>
  .small {
    display: block;
    width: 600px;
    height: 600px;
    background-color: rgba(0,0,0,.1);
    padding: 30px;
    border-radius: 16px;
  }
</style>
```



ダミーのjsonファイルです。これをaxios経由でrailsから投げてもらうように設定してます。

```
{
  "marks": [
    {
      "time": "Wed Apr 10 2019 07:17:25 GMT+0900",
      "value": 6000
    },
    {
      "time": "Wed Apr 10 2019 08:17:25 GMT+0900",
      "value": 8000
    },
    {
      "time": "Wed Apr 10 2019 10:17:25 GMT+0900",
      "value": 5000
    },
    {
      "time": "Wed Apr 10 2019 12:17:25 GMT+0900",
      "value": 3000
    },
    {
      "time": "Wed Apr 10 2019 16:17:25 GMT+0900",
      "value": 2000
    }
  ],
  "predicted_mark": {
    "time": "Wed Apr 10 2019 20:17:25 GMT+0900",
    "value": 2000
  },
  "content": {
    "backgroundColor": "transparent"
  }
}
```


こんな感じの図ができます。　
詳しい解説はしませんが、ドキュメントと照らし合わせながら参考にしていただければと思います。

![スクリーンショット 2019-04-10 8.48.34.png](https://qiita-image-store.s3.amazonaws.com/0/224974/42dff9fd-034a-7ee4-ac72-2c67b1d67d2b.png)


#小技集
僕が手こずったところと参考にした記事をいくつか紹介します。
上のコードにはすでに反映済みです。

##曲線をカクカクさせたい
普通に折れ線グラフを作ると、綺麗な曲線になります。
しかしカクカクさせたいときもありますよね。

そんなときは、 lineTension: 0 をdatasetsに設定してあげましょう。

```
datasets : [{
  lineTension: 0,
  data:[90,58,2,98,3,89]
}]
```

参考記事
https://qiita.com/serv-platong/items/ff42761373302f2d472e

##ラベルを消したい
勝手に表示されるラベルが邪魔な時があります。
そんな時は、

```
options = {
   legend: {
      display: false,
   }
}
```


##軸のラベルに書いてある文字以外のテキストをtooltipに表示したい
グラフの点にホバーすると、いろんな情報が出てきますが、
これもoptionで設定できます。


*参考記事からの引用
https://qiita.com/protein_wasshoi/items/cc20760032767961700f


```
  options: {
    tooltips: {
      callbacks: {
        title: (tooltipItem, data) ->
          # title内ではtooltipItemの値（カーソルが重なった箇所のデータ）を表示する場合は`tooltip[0]`のよう要素を指定することに注意!!
          return tooltipItem[0].xLabel + "hoge"
        label: (tooltipItem, data) ->
          return [tooltipItem.yLabel + "huga"] # リストで記述すると改行される
       }
    }
  })
```

##Y軸の最小値を０にしたい
データの最小値が１０００とかだけれども、０からY軸は始まって欲しい。
そんな時は、optionにこれを挿入。

```
scales: {
  yAxes: [{
      ticks: {
          beginAtZero:true
      }
  }]
}
}
```


##破線を描きたい
線の種類や太さ、色を変えたい時はdataの方を編集します。

```
datasets[
 {
   label: '点線',
   data: [10, 30, 20, 25, 35, 40, 60],
   borderDash: [5, 5], // ダッシュ線のスタイル。[5, 3]など。
 }
]
```

参考記事
https://pizzamanz.net/web/javascript/chart_js_borderdash/

##途中からの線を破線にしたい
ここは、悩んだんですが、僕は二つのグラフを重ねることで解決しました。
１つ目のグラフが普通の線担当、
２つ目のグラフが破線担当ってイメージです。

dataの部分を、nullを使いながら分断するとうまくいきます。
例：

```
datasets[
 {
   data: [10, 30, 20, 25, 35, 40, ],
 },
 {
   data: [, , , , , 40, 60],
 }
]
```
交差している点は、ホバーしたときの挙動が微妙だったので、この方法でよかったのかまだ悩み中です。
知ってる方は教えて欲しいです。


なかなか渋い記事になりましたが、以上です。
かなり小回りのきく便利なライブラリだと思いました。