2021-10-27

Nuxt.js + TypeScript + ApexCharts.jsによるグラフ機能の実装

@gc_tech70

はじめに

こんにちは。エンジニアリング事業本部の@gc_tech70です。 普段はNuxt.js、TypeScriptを使ってフロントエンド領域をメインに開発をしています。

今回は弊社サービスであるスマートマットライトにて、消費レポート(グラフ)機能を実装したため、Nuxt.js、TypeScript環境でApexCharts.jsを利用してグラフ機能を実装したときの技術選定、具体的な実装方法やハマりどころなどについて紹介したいと思います。

消費レポート

https://service.lite.smartmat.io/news/consumption-report_release_20211015

グラフライブラリの選定

今回グラフ機能を実装するためにApexCharts.jsを採用しましたが、選定した理由については以下のようになっています。

要件

グラフ機能を実装する際の要件はざっくり以下の通りです。

  • BtoCサービスなので、できるだけリッチに見せたい

    • 可能であればUIのカスタマイズ性があるものやアニメーションなどがあると良い

  • レスポンシブに対応したい

    • グラフのデータが多い場合、できれば横スクロールしたい

  • グラフ内に基準線を表示したい
    • スマートマットライトの場合、注文タイミングの線を表示したい
  • グラフは一旦折れ線グラフのみで、将来的には棒グラフも使いたい

ライブラリ比較

Vueで使えるグラフライブラリ自体は結構ありますが、ざっくり調べた感じドキュメント量やスター数などを見るとvue-chart.jsApexCharts.jsの二択かなという印象だったので、今回はその2つで比較をしていきます。

参考記事: https://qiita.com/engineerYodaka/items/7d86de58959ca633716d

vue-chart.js

Chart.jsをVueで使うためのラッパーライブラリ

  • 公式ドキュメント
  • スター数 4.5k
    • Chart.js 55.1k
  • Chart.jsのVue対応版
  • レスポンシブに対応(横スクロールなし)
  • レンダリング時にcanvasタグにグラフを生成
  • アニメーションあり
  • 折れ線、棒グラフあり
  • グラフの線、色のスタイル変更可能
  • 基本的なグラフの設定は全てChart.jsの公式を調べる必要あり
  • グラフ内に基準線の表示が可能
    • ただし、追加でプラグインが必要であり、Chart.jsのバージョンによっては動かないことがある vue-chart

ApexCharts.js

  • 公式ドキュメント
  • スター数 10.8k
  • Vue, React, Angularサポート
  • レスポンシブに対応(横スクロールなし)
  • レンダリング時にsvgタグにグラフを生成
  • アニメーションあり
  • 折れ線、棒グラフあり
  • グラフ内の任意のポイントに点や線を引ける
  • グラフの線、色のスタイル変更可能
  • CSV、グラフ画像ダウンロード機能あり
  • React, Vue, Angularの公式デモコードあり ApexCharts

比較結果

上記比較をした結果、カスタムできる要素の多さ、グラフの種類、メンテナンス頻度などにおいては特に差はない印象でした。

「レスポンシブに対応している。(グラフのデータが多い場合、できれば横スクロールしたい)」という要件に関しても、どちらもレスポンシブには対応しているものの、描画時点の画面サイズに合わせたグラフ画像を生成するような動きとなっているようで、横スクロールには対応してなく、差はないようです。

「グラフ内に基準線を表示したい」という要件に関しては、ApexCharts.jsの方がグラフ内の任意のポイントに点や線を配置することができ、vue-chart.jsでも基準線を表示することはできそうなのですが、標準機能にはなく追加で別のプラグインを入れる必要があり、chart.jsのバージョンによっては動かないこともあるそうなので、この点においてはApexCharts.jsの方が軍配が上がりそうです。

また、ApexCharts.jsには標準でCSVやグラフ画像のダウンロード機能がついていたり、グラフ内に画像を挿入できたりと、追加の要件があった際の拡張性の面でも期待できそうです。

ドキュメントについてはどちらも豊富ですが、強いて言うならvue-chart.jsではグラフの設定はChart.jsの公式を調べる必要がある一方で、ApexCharts.jsは公式で細かい設定のVueのサンプルコードが公開されており、個人的にはApexCharts.js調べやすい印象でした。

UI的な部分もvue-chart.jsはシンプルでApexCharts.jsの方がリッチな印象です。(スタイルやアニメーションの設定をしっかりすればどちらも近いところまで寄せることはできると思いますが)

上記のような点から今回はApexCharts.jsを採用することに決めました。

実装方法

前提環境

  • Nuxt.js 2.15.7
    • ログイン前提の管理画面のため、特にSEOを意識する必要はないのでSSRはしていません
  • TypeScript 4.2
  • vue-apexcharts 1.6.2

記法はOptions API(Vue.extend)を採用しています。

導入

参考リンク: https://github.com/apexcharts/vue-apexcharts

インストール

yarn add apexcharts  vue-apexcharts

or

npm install --save apexcharts vue-apexcharts

設定

/plugings/vue-apexcharts.ts

import VueApexCharts from 'vue-
apexcharts'
Vue.use(VueApexCharts)

Vue.component('apexchart', VueApexCharts)

nuxt.config.ts

plugins: [
  '@/plugins/vue-apexcharts',
],

表示

ApexChartsSample.vue

  • ApexCharts.jsはTypeScriptにも対応しているようで、しっかり型定義ファイルがありました。
  • xaxis.categoriesがの値がX軸のラベル
  • series.dataの値がY軸
    • seriesは配列なので、複数のグラフを同時に表示することも可能です。
<template>
  <div>
    <apexchart
      width="500"
      type="line"
      :options="chartOptions"
      :series="series"
    ></apexchart>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'
import { ApexOptions } from 'apexcharts'

export default Vue.extend({
  data(): {
    chartOptions: ApexOptions
    series: ApexAxisChartSeries
  } {
    return {
      chartOptions: {
        chart: {
          id: 'vuechart-example',
        },
        xaxis: {
          categories: [1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998],
        },
      },
      series: [
        {
          name: 'グラフ名',
          data: [30, 40, 35, 50, 49, 60, 70, 91],
        },
      ],
    }
  },
})
</script>

グラフサンプル

ApexCharts.jsのハマりどころ

日付が省略される

ApexCharts.jsではxaxisのtypeに datetimeを指定することでデータが日付形式の場合、きれいにレイアウトに収まるように自動で一部の日付を省略してくれます。

export default Vue.extend({
  data(): {
    chartOptions: ApexOptions
    series: ApexAxisChartSeries
  } {
    return {
      chartOptions: {
        chart: {
          id: 'vuechart-example',
        },
        xaxis: {
          type: 'datetime',
          categories: [
            '2021-09-26',
            '2021-09-27',
            '2021-09-28',
            '2021-09-29',
            '2021-09-30',
            '2021-10-03',
            '2021-10-04',
            '2021-10-05',
          ],
          labels: {
            format: 'MM/dd', // 日付のフォーマット
          },
        },
      },
      series: [
        {
          name: 'グラフ名',
          data: [157, 135, 135, 113, 99, 99, 46, 46],
        },
      ],
    }
  },
})

日付グラフ

しかし、一方でデータを全て見せたい時にはこの機能を使うと省略されてしまうので、そういう場合はtypeに categoryを指定することで全てのデータが表示されます。 ※ただし、 categoryは文字列型のようなイメージなので表示したい日付のフォーマットには事前に変換しておく必要があります。

<script lang="ts">
import Vue from 'vue'
import { ApexOptions } from 'apexcharts'

export default Vue.extend({
  data(): {
    chartOptions: ApexOptions
    series: ApexAxisChartSeries
  } {
    return {
      chartOptions: {
        chart: {
          id: 'vuechart-example',
        },
        xaxis: {
          type: 'category',
          categories: [
            '2021-09-26',
            '2021-09-27',
            '2021-09-28',
            '2021-09-29',
            '2021-09-30',
            '2021-10-03',
            '2021-10-04',
            '2021-10-05',
          ],
        },
      },
      series: [
        {
          name: 'グラフ名',
          data: [157, 135, 135, 113, 99, 99, 46, 46],
        },
      ],
    }
  },
})
</script>

ApexCharts.js 折れ線グラフ 文字列

目盛がばらつく

ApexCharts.jsではデータの値によってY軸の最大、最小値を自動で近い数値に設定してくれます。 基本的には表示する値に近い数値がY軸の目盛りに設定され、見やすく切りのいい数値が設定されます。 また、最大、最小値に関しては自身で設定することもでき、例えば最小値0を設定するとマイナスの値があった時にグラフ中に表示されなくなります。 しかし、最大、最小値を設定した場合、グラフ中の最小値から最大値が10分割された値が目盛りに設定され、値によってはとても見づらくなります。 これの対策としては、最大、最小値を安易に設定しないことや、最大、最小値には切りのいい数値を設定することで解消できます。

最大、最小値を設定してないパターン

ApexCharts.js 折れ線 グラフ デフォルト


<script lang="ts">
import Vue from 'vue'
import { ApexOptions } from 'apexcharts'

export default Vue.extend({
  data(): {
    chartOptions: ApexOptions
    series: ApexAxisChartSeries
  } {
    return {
      chartOptions: {
        chart: {
          id: 'vuechart-example',
        },
        xaxis: {
          type: 'datetime',
          categories: [
            '2021-09-26',
            '2021-09-27',
            '2021-09-28',
            '2021-09-29',
            '2021-09-30',
            '2021-10-03',
            '2021-10-04',
            '2021-10-05',
          ],
          labels: {
            format: 'MM/dd',
          },
        },
      },
      series: [
        {
          name: 'グラフ名',
          data: [157, 135, 135, 113, 99, 99, 46, 46],
        },
      ],
    }
  },
})
</script>

最小値を設定しているパターン

ApexCharts.js 折れ線 グラフ 最小値


<script lang="ts">
import Vue from 'vue'
import { ApexOptions } from 'apexcharts'

export default Vue.extend({
  data(): {
    chartOptions: ApexOptions
    series: ApexAxisChartSeries
  } {
    return {
      chartOptions: {
        chart: {
          id: 'vuechart-example',
        },
        xaxis: {
          type: 'datetime',
          categories: [
            '2021-09-26',
            '2021-09-27',
            '2021-09-28',
            '2021-09-29',
            '2021-09-30',
            '2021-10-03',
            '2021-10-04',
            '2021-10-05',
          ],
          labels: {
            format: 'MM/dd',
          },
        },
        yaxis: {
          min: 0, // 最小値
        },
      },
      series: [
        {
          name: 'グラフ名',
          data: [157, 135, 135, 113, 99, 99, 46, 46],
        },
      ],
    }
  },
})
</script>

日付データがJSTでもUTCで解釈される

グラフのtypeをdatetimeで指定してJSTのデータを渡した際、デフォルトの設定だとUTCで解釈されてしまい、例えば'2021-10-04T03:50:03+09:00'という日付は、日本時間で'2021/10/04 03:50:03'の日時であることを意味していますが、グラフ上では9時間前の'2021/10/03 18:50:03'として解釈されていまいます。 これの対策としては、xaxis.labels.datetimeUTCにfalseを設定することでJSTのようなUTC以外のタイムゾーンの日時でも正常に解釈されるようです。

デフォルトの設定

<script lang="ts">
import Vue from 'vue'
import { ApexOptions } from 'apexcharts'

export default Vue.extend({
  data(): {
    chartOptions: ApexOptions
    series: ApexAxisChartSeries
  } {
    return {
      chartOptions: {
        chart: {
          id: 'vuechart-example',
        },
        xaxis: {
          type: 'datetime',
          categories: [
            '2021-10-04T03:50:03+09:00',
            '2021-10-05T03:50:03+09:00',
          ],
          labels: {
            format: 'MM/dd',
            showDuplicates: false,
            hideOverlappingLabels: true,
          },
        },
        tooltip: {
          x: {
            format: 'yyyy/MM/dd',
          },
        },
      },
      series: [
        {
          name: 'グラフ名',
          data: [70, 91],
        },
      ],
    }
  },
})
</script>

ApexCharts.js 折れ線 グラフ JST UTC

UTC以外のタイムゾーンの設定

<script lang="ts">
import Vue from 'vue'
import { ApexOptions } from 'apexcharts'

export default Vue.extend({
  data(): {
    chartOptions: ApexOptions
    series: ApexAxisChartSeries
  } {
    return {
      chartOptions: {
        chart: {
          id: 'vuechart-example',
        },
        xaxis: {
          type: 'datetime',
          categories: [
            '2021-10-04T03:50:03+09:00',
            '2021-10-05T03:50:03+09:00',
          ],
          labels: {
            format: 'MM/dd',
            showDuplicates: false,
            hideOverlappingLabels: true,
            datetimeUTC: false, // ここに追加
          },
        },
        tooltip: {
          x: {
            format: 'yyyy/MM/dd',
          },
        },
      },
      series: [
        {
          name: 'グラフ名',
          data: [70, 91],
        },
      ],
    }
  },
})
</script>

ApexCharts.js 折れ線 グラフ JST

グラフデータの更新

グラフのデータを更新したい時はオブジェクトの単一プロパティを更新しても、変更を検知できずグラフは更新されないのでオブジェクトそのものを上書きする必要があります。 これについては公式にも書いてあります。

https://github.com/apexcharts/vue-apexcharts

誤った例

this.chartOptions.xaxis = {
    labels: {
        style: {
          colors: ['red']
        }
    }
}}

正しい例

this.chartOptions = {...this.chartOptions, ...{
    xaxis: {
        labels: {
           style: {
             colors: ['red']
           }
        }
    }
}}

まとめ

ApexCharts.jsを使ってみた感想は、導入が簡単、拡張性が高い、デフォルトでUIが整っているという印象でした。 結構ハマりどころもありましたが、大体どのグラフライブラリも似たような感じなんじゃないかなと思いました。 個人的には結構おすすめのライブラリなので、グラフの実装を検討している方は是非使ってみてください!

最新の記事