9
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

初心者のVuetify | Vuetify + Chart.jsで、グラフカードコンポーネントの作成

Last updated at Posted at 2021-03-06

ダッシュボードを作成するのに、カード型のUIでグラフ表示をしてみたかったので、
VuetifyとChart.jsで簡単な実装をしてみました。

対象読者

以下の内容を書いてます。

  • Vuetifyのカードコンポーネント実装
  • そこに、Chart.jsの埋め込み
  • Nuxt.JSのレンダリングタイミングの制御

できたもの

result.png

準備

create-nuxt-appでプロジェクトを作成します。
参考 インストール - NuxtJS

$ yarn create nuxt-app <project-name>

もしくは、GitHubにプロジェクトソースを置いてるので、それを利用してください。
今回は、Vuetifyベースでの記述になります。

Nuxt.JSでChart.jsを利用するために、Chart.jsと、vue-chart.jsをインストールします。

yarn add vue-chartjs chart.js

データをベタガキ

まずは、Chart.jsの設定に直接値を書き込みます。
components/charts/chart-card.vue

グラフ部分作成

まず,Chart.jsのレンダリングをするコンポーネントを記述します。
グラフを描画したい時は、このコンポーネントを呼び出します。

components/charts/chart-card/linechart.vue

<script>
import { Line } from 'vue-chartjs';
export default {
    extends: Line,
    name: 'chart',
    props: ['chartData', 'options'],
    mounted() {
        this.renderChart(this.chartData, this.options)
    }
}
</script>

カード作成 + グラフ描画

components/charts/chart-card/chart-card.vue

Vuetifyの、v-cardでカード部分を実現。
メインコンテンツを実装するv-card-textに、グラフ部分のコンポーネントを配置します。

template部

<template>
    <v-card min-width="80%">
        <v-card-title>
            Simple Chart
        </v-card-title>
        <v-card-text class="chart-container"> 
            <Chart :chartData="chartData" :options="options"/>
        </v-card-text>
    </v-card>
</template>

script部

data()で、グラフ描画に必要な情報を記述

  • chartData

    • labels: X軸のラベル (Array)
    • datasets
      • label: 各線の凡例
      • data: データの数値
      • fill: 線グラフより下を塗りつぶすか
      • backgroundColor: 塗りつぶす色
      • borderColor: 線の色
  • options

    • responsive: レスポンシブのOn/Off
    • maintainAspectRatio: 縦横比を自動調整するか
    • legend: 凡例の設定
<script>
import Chart from './linechart.vue';
export default {
    components: {
        Chart
    },
    data() {
        return {
            chartData: {
                labels: ['A', 'B', 'C'],
                datasets: [{
                    label: "sample",
                    data: [100, 150, 300],
                    fill: true,
                    backgroundColor: 'rgba(83, 224, 156, 0.2)',
                    borderColor: '#53e09c'
                }]
            },

            options: {
                responsive: true,
                maintainAspectRatio: false,
                legend: {
                    display: true
                },
            }
        }
    },
}
</script>

これをpageで描画

作成した、カードコンポーネントをpageで描画します。

<template>
    <div>
        <v-row>
            <v-col cols="12" sm="12" md="6" lg="4">
                <chartCard />
            </v-col>
        </v-row>
    </div>
</template>
<script>
import chartCard from '~/components/charts/chart-card/chart-card.vue';
export default {
    components: {
        chartCard,
    }
}
</script>

Vue Storeからの取得

components/charts/chart-card-withStore.vue

ベタガキで使うタイミングなんてそんなにないので、
よくNuxtで用いるパターンとして、Vuexを使うパターンで実装します。

ベタ描き部分との差分のみ記述します

Store (Vuex)を作成

store/chart.jsに、Chart用のStoreを作成。

参考

export const state = () => ({
    simple: {
        label: ['AA', 'AB', 'AC'],
        data: [1000, 2000, 2500]
    },
})

カード作成 + グラフ描画

this.$store.state...で、NuxtのどこからでもStoreの値にアクセス可能なので、
chartDataの、labelsとdataの情報を、Storeから引っ張ってくるように記述。

+++ : 前の手順からの追記箇所

...

<script>
import Chart from './linechart.vue';
export default {
    (...)

    data() {
        return {
            (...)
            chartData: {
+++             labels: this.$store.state.charts_store.simple.label,
                datasets: [{
                    label: "sample",
+++                 data: this.$store.state.charts_store.simple.data,
                    fill: true,
                    backgroundColor: 'rgba(235, 70, 52, 0.2)',
                    borderColor: '#eb4634',
                }]
            },
            (...)
}
</script>

pageに追記すると

+++ : 前の手順からの追記箇所
(...): 省略

<template>
    <div>
        <v-row>
            (...)
+++         <v-col cols="12" sm="12" md="6" lg="4">
+++              <storeChartCard />
+++         </v-col>
        </v-row>
    </div>
</template>
<script>
(...)
+++ import storeChartCard from '~/components/charts/chart-card/chart-card-withStore.vue';

export default {
    components: {
        chartCard,
+++     storeChartCard,        
    }
}
</script>

これで、Storeの情報を使って、グラフ描画できました。

外部APIでの実行

components/charts/chart-card-withAPI.vue

Webアプリで一番使うのが、外部APIから情報を取得して、画面に描画する方法だと思います。なのでそのパターンを実装してみましょう。

今回は、日経平均株価のサンプルでやるために、Quandlで取得してみます。

RuntimeConfig | 外部APIのアクセストークン管理

APIを活用する時に、一番問題なのは、Nuxt.jsにToken情報などが晒されてしまう問題なので、今回は、Nuxt.jsのRuntimeConfigを使います、

かなり重要
本来は、privateRuntimeConfigを使うのですが、SSR化が必要だったりと色々設定が必要なので、今回は簡単するために、publicRuntimeConfigで実装します。ブラウザでAPIリクエストするので、アクセストークンは見えてしまう実装となります。完全に隠す必要がある場合は、SSR化して、privateRuntimeConfigで実装する必要があります。

publicRuntimeConfigの設定

APIアクセストークンを管理するために以下の手順を取ります。

  • 環境変数ファイル '.env'を作成
  • .envファイルの中を nuxt.config.jsから読み込み

環境変数ファイル作成

Nuxt.jsにおいて、環境変数情報は、.envファイルで管理します。

QUANDL_KEY=<Quandlのアクセストークン>

そして、nuxt.config.jsに、 process.env.変数名で .envファイルの中見を参照できます。

  publicRuntimeConfig: {
    quandlKey: process.env.QUANDL_KEY | <トークンベタガキ>
  },

利用方法

コンポーネント等から、this.$config.quandlKeyとすることで、環境変数の値を参照することができます。

カード作成 + グラフ作成

コード全体

=== : 前の手順からの変更箇所
+++ : 前の手順からの追記箇所
(...): 省略

コード全体
<template>
    <v-card min-width="80%">
            (...)
===       <Chart v-if="loaded" :chartData="chartData" :options="options"/>
===       <v-progress-circular v-else-if="loaded!=true" indeterminate color="#53e09c" />
        </v-card-text>
    </v-card>
</template>

<script>
+++ import axios from 'axios';
import Chart from './linechart.vue';

export default {
    (...)
    data() {
        return {
+++         loaded: false,

            chartData: {
===             labels: null,
                datasets: [{
                    label: "average value",
===                 data: null,
                    fill: true,
                    backgroundColor: 'rgba(232, 229, 74, 0.2)',
                    borderColor: '#e8e54a',
                }]
            },
            (...)
        }
    },
+++    async created() {
+++        const url = "https://www.quandl.com/api/v3/datasets/CHRIS/CME_NK2/data.json?api_key=" + this.$config.quandlKey;
+++        const response = await axios.get(url);
+++        
+++        const data = response.data.dataset_data.data;
+++        const last_7day = data.slice(0, 7).reverse();
+++        
+++        // // 直近7日の日付 + 終値
+++        const days = last_7day.map(item => item[0]);
+++        const last = last_7day.map(item => item[4]);
+++
+++        await this.$set(this.chartData, 'labels', days);
+++        await this.$set(this.chartData.datasets[0], 'data', last);
+++
+++        this.loaded = true;
+++
+++    },
}
</script>

APIからの情報を取得

まず、画面描画前にデータを取得する処理を実行するために、created()関数内でAPIを実行します。
created()関数は、画面のレンダリング前に実行した処理を記載します。
どのタイミングで、created()が呼ばれるかは、Nuxt Lifecycleにわかりやすいイラストがあります。

created() 内では、以下の処理を行っています.

  1. axios.getで日経平均株価データ取得
  2. レスポンスデータから、直近7日の情報を取得(slice)、日付の早い順にソート
  3. map関数で、日付・終値のArrayを抽出
  4. storeに登録
  5. 情報取得処理が完了したフラグを立てる

url内で、APIキーを付与するので、 this.$config.quandlKey でRuntimeConfigから、呼び出しています。

    async created() {
        const url = "https://www.quandl.com/api/v3/datasets/CHRIS/CME_NK2/data.json?api_key=" + this.$config.quandlKey;
        const response = await axios.get(url);
        
        const data = response.data.dataset_data.data;
        const last_7day = data.slice(0, 7).reverse();
        
        // // 直近7日の日付 + 終値
        const days = last_7day.map(item => item[0]);
        const last = last_7day.map(item => item[4]);

        await this.$set(this.chartData, 'labels', days);
        await this.$set(this.chartData.datasets[0], 'data', last);

        this.loaded = true;

    },

データ取得まで、レンダリングを待つ

Chart.jsは、templateのレンダリング次にデータが固定されるので、

  • created()で、情報を取得仕切る前に、グラフ部分のレンダリングが行われる
  • 結果、空白のグラフが生成される

という問題があります。なので、以下の対策を取ります。

  1. データの取得が完了するかどうかを判断する変数、loadedを定義
  2. templateの<Chart>部分で、v-ifを用いてレンダリング制御
  3. created()の末尾で、loaded = trueに変更

v-ifで、false状態にして、描画対象から外すことで、初回のレンダリング時に空で描画されるのを回避して、 v-ifがtrue状態になった時に、Chartのレンダリングが実行されるようになります。

template

レンダリング制御のために、 <Chart>に、v-ifを追記します。
また、読み込み中にローディング表示したいので、Vuetifyの v-progress-circularを、v-else-ifで表示するようにします。

<template>
    <v-card min-width="80%">
            ....
===       <Chart v-if="loaded" :chartData="chartData" :options="options"/>
===       <v-progress-circular v-else-if="loaded!=true" indeterminate color="#53e09c" />
        </v-card-text>
    </v-card>
</template>

pageに追記すると

API実行中は、ローディング画面になり、
データの読み込みが終わったらグラフ描画が走るようになります。

+++ : 前の手順からの追記箇所
(...): 省略

<template>
    <div>
        <v-row>
            (...)
+++         <v-col cols="12" sm="12" md="6" lg="4">
+++              <outsideAPIChartCard />
+++         </v-col>
        </v-row>
    </div>
</template>
<script>
(...)
+++   import outsideAPIChartCard from '~/components/charts/chart-card/chart-card-withAPI.vue';

export default {
    components: {
        chartCard,
        storeChartCard,
+++     outsideAPIChartCard,        
    }
}
</script>

output
loadapi.png

と実装できました。

まとめ

今回は、Vuetify + Chart.jsで、ダッシュボードに使えそうなグラフのコンポーネントを作成してみました。
今後。GitHubにギャラリー的にまとめようと考えています。

Source Code

9
14
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?