いい感じのグラフをTypeScriptなNuxt.jsとvue-chartjs(chart.js)で書いてみた

読了目安:17分

Nuxt.jsで開発しているWebサービスで、
棒グラフとか線グラフを使いたいなと思い、いろいろ調べた備忘録。

vue-chartjsを使うと、いい感じのグラフが簡単にできた(´ω`)

こんな感じで使ってます!!

登録されている本の総額と総冊数の推移を棒グラフと線グラフで表示!!
色も文字も変えられて、いい感じに(´ω`)


vue-chartjsの使い方

全体の流れはこんな感じ。
1. インストール
2. コンポーネントを作る
3. コンポーネントを配置する

1. インストール

まずはインストール

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

2. コンポーネントを作る

<!-- Template Tag can not be merged... -->

<script lang="ts">
import { Component, Vue, Prop, Watch, mixins } from "nuxt-property-decorator";
import Chart from "chart.js";
import VueChart from "vue-chartjs";
const Line = VueChart.Line;
const reactiveProp = VueChart.mixins.reactiveProp;

@Component
export default class ChartLine extends mixins(Line, reactiveProp) {
  @Prop({ default: {} }) chartData: Chart.ChartData;
  @Prop({ default: {} }) options: Chart.ChartOptions;

  mounted() {
    this.renderChart(this.chartData, this.options);
  }
}
</script>

注意点としては、以下の3つ

  1. vue-chartjsが生成するので、<template>タグをつけない
  2. mixinsが重複するので、import Chart from "chart.js";
  3. データのProp名はreactivePropで決まってるのでchartDataにする
    参考: src/mixins/index.js | Github

この例は折れ線グラフのため、Lineをextendsしてるけど、
ほかのチャートは、それぞれコンポーネントを用意すればOK

棒グラフの場合はこんな感じ。

<!-- Template Tag can not be merged... -->

<script lang="ts">
import { Component, Vue, Prop, Watch, mixins } from "nuxt-property-decorator";
import Chart from "chart.js";
import VueChart from "vue-chartjs";
// 棒グラフの場合は、Barを使う
const Bar = VueChart.Bar;
const reactiveProp = VueChart.mixins.reactiveProp;

@Component
// mixinsもBarに変更
export default class ChartLine extends mixins(Bar, reactiveProp) {
  @Prop({ default: {} }) chartData: Chart.ChartData;
  @Prop({ default: {} }) options: Chart.ChartOptions;

  mounted() {
    this.renderChart(this.chartData, this.options);
  }
}
</script>

3. コンポーネントを配置する

2.で作成したコンポーネントChartLineとChartBarを配置する。
チャートのデータ(chartData)やオプション、スタイルをpropで設定。

<template>
  <div class="chart-container">
    <ChartLine :chartData="chartData" :options="chartOption" :styles="chartStyles" />
  </div>
</template>

<script lang="ts">
import { Component, Vue, Prop } from "nuxt-property-decorator";
import { ChartData, ChartOptions } from "chart.js";
import ChartLine from "~/components/ChartLine.vue";
import ChartBar from "~/components/ChartBar.vue";

@Component({ components: { ChartLine, ChartBar } })
export default class ChartPage extends Vue {
  // チャートのデータ
  private chartData: ChartData = {
    // 横軸のラベル
    labels: ["A", "B", "C", "D", "E"],
    // データのリスト
    datasets: [
      {
        label: "Data One", // データのラベル
        data: [1, 5, 3, 4, 3] // データの値。labelsと同じサイズ
      },
      {
        label: "Data Two",
        data: [10, 50, 30, 40, 30]
      }
    ]
  };

  // チャートのオプション
  private chartOption: ChartOptions = {
    // アスペクト比を固定しないように変更
    maintainAspectRatio: false
  };

  // チャートのスタイル: <canvas>のstyle属性として設定
  private chartStyles = {
    height: "100%",
    width: "100%"
  };
}
</script>

<style lang="scss">
.chart-container {
  /**
   * vue-chartjsで生成する<canvas>がabsoluteのため、
   * relateveを設定
   */
  position: relative;

  /**
   * chartStylesを設定しているので、
   * height/wightが有効になる
   */
  height: 40vh;
  width: 80vw;
  margin: 0 auto;
}
</style>

こんな感じでチャートデータを渡すと、こんな感じに(´ω`)

注意: ハマったポイント

グラフ描画だけであれば、データを渡すだけなので簡単(´ω`)
ただ、サイズを変えるのは一苦労...

注意するのは3つ。

  1. chartOptionmaintainAspectRatio: falseを指定
  2. chartStylesでチャートのheight/widthを100%に設定
  3. 親要素(.chart-container)にposition: relative;

vue-chartjsにはchartStyles以外にもpropがあるので、
ほかは公式ドキュメントを参照


いろいろ設定を変えてみる

色や形を変えるなどチャート自体の設定は、
chart.jsの公式ドキュメントを見ていく感じっぽい。

困ったら、公式ドキュメントをみれば、だいたい行けた感じ(´ω`)
ここからは、実際に使ってみた設定例を紹介。

長いので、目次を見て好きなのを見るのがオススメ。

dataset関連

参考: Charts · Chart.js documentation
ここに載ってる、それぞれのグラフを見るといい感じ♪

線の色を変える: borderColor

  private chartData: ChartData = {
    labels: ["A", "B", "C", "D", "E"],
    datasets: [
      {
        label: "Data One",
        data: [1, 5, 3, 4, 3],
        borderColor: "green" // 線の色
      },
      {
        label: "Data Two",
        data: [10, 50, 30, 40, 30],
        borderColor: "red"
      }
    ]
  };

スクリーンショット 2019-09-21 18.15.50.png

線の色を変えても、棒グラフの色は変わらない。。

塗りつぶしの色を変える: backgroundColor

  private chartData: ChartData = {
    labels: ["A", "B", "C", "D", "E"],
    datasets: [
      {
        label: "Data One",
        data: [1, 5, 3, 4, 3],
        borderColor: "green", // 線の色
        backgroundColor: "rgba(0, 255, 0, 0.4)" // 塗りつぶしの色
      },
      {
        label: "Data Two",
        data: [10, 50, 30, 40, 30],
        borderColor: "red",
        backgroundColor: "rgba(255, 0, 0, 0.4)"
      }
    ]
  };

スクリーンショット 2019-09-21 18.18.09.png

いい感じになったけど、折れ線グラフの場合は、点の背景色も変わるので、透過しないほうが良いかも。

色関連の値には、rgbaも使える。

塗りつぶしをしない: fill

  private chartData: ChartData = {
    labels: ["A", "B", "C", "D", "E"],
    datasets: [
      {
        label: "Data One",
        data: [1, 5, 3, 4, 3],
        borderColor: "green",
        backgroundColor: "rgba(0, 255, 0, 0.4)", 
        fill: false // 折れ線グラフの塗りつぶしを無効化
      },
      {
        label: "Data Two",
        data: [10, 50, 30, 40, 30],
        borderColor: "red",
        backgroundColor: "rgba(255, 0, 0, 0.4)",
        fill: false
      }
    ]
  };

スクリーンショット 2019-09-21 18.19.33.png

線を真っ直ぐにする: tension

  private chartData: ChartData = {
    labels: ["A", "B", "C", "D", "E"],
    datasets: [
      {
        label: "Data One",
        data: [1, 5, 3, 4, 3],
        borderColor: "green",
        backgroundColor: "rgba(0, 255, 0, 0.4)", 
        fill: false,
        tension: 0 // 線を真っ直ぐにする
      },
      {
        label: "Data Two",
        data: [10, 50, 30, 40, 30],
        borderColor: "red",
        backgroundColor: "rgba(255, 0, 0, 0.4)",
        fill: false,
        tension: 0
      }
    ]
  };

スクリーンショット 2019-09-21 18.20.41.png

折れ線ぽっくなった(´ω`)

折れ線グラフの点の大きさを変える: radius / hoverRadius

  private chartData: ChartData = {
    labels: ["A", "B", "C", "D", "E"],
    datasets: [
      {
        label: "Data One",
        data: [1, 5, 3, 4, 3],
        borderColor: "green",
        backgroundColor: "rgba(0, 255, 0, 0.4)", 
        fill: false,
        tension: 0,
        radius: 8, // 点の大きさ
        hoverRadius: 20 // 点の大きさ(マウスホバー時)
      },
      {
        label: "Data Two",
        data: [10, 50, 30, 40, 30],
        borderColor: "red",
        backgroundColor: "rgba(255, 0, 0, 0.4)",
        fill: false,
        tension: 0,
        radius: 8,
        hoverRadius: 20
      }
    ]
  };

スクリーンショット 2019-09-21 18.23.48.png

グラフを組み合わせる(複合グラフ): type

  private chartData: ChartData = {
    labels: ["A", "B", "C", "D", "E"],
    datasets: [
      {
        label: "Data One",
        data: [1, 5, 3, 4, 3],
        borderColor: "green",
        backgroundColor: "rgba(0, 255, 0, 0.4)", 
        fill: false,
        tension: 0,
        radius: 8,
        hoverRadius: 20,
      },
      {
        label: "Data Two",
        data: [10, 50, 30, 40, 30],
        borderColor: "red",
        backgroundColor: "rgba(255, 0, 0, 0.4)",
        fill: false,
        tension: 0,
        radius: 8,
        hoverRadius: 20,
        type: "line" // 折れ線グラフで表示
      }
    ]
  };

スクリーンショット 2019-09-21 18.29.23.png

Barのときにチャートのtypeを指定すると変更できる。

参考: Mixed · Chart.js documentation
参考: chart.js で複数軸の複合グラフを描く - Qiita

options関連

参考: Configuration · Chart.js documentation
このあたりを見ていくといい感じ。

それぞれの例がわかりくくいけど、このあたりにoptionのどこに書けばいいか書いてある。

スクリーンショット_2019-09-21_19_15_19.png

また、Y軸関連は、こっち。

参考: Axes · Chart.js documentation

凡例の位置を変える: legend

  private chartOption: ChartOptions = {
    maintainAspectRatio: false,
    legend: { position: "bottom" }, // 凡例の位置を変える
  }

スクリーンショット 2019-09-21 18.34.32.png

参考: Legend · Chart.js documentation

折れ線グラフの点の大きさなどを全体で設定: elements

 private chartData: ChartData = {
    labels: ["A", "B", "C", "D", "E"],
    datasets: [
      {
        label: "Data One",
        data: [1, 5, 3, 4, 3],
        borderColor: "green",
        backgroundColor: "rgba(0, 255, 0, 0.4)"
      },
      {
        label: "Data Two",
        data: [10, 50, 30, 40, 30],
        borderColor: "red",
        backgroundColor: "rgba(255, 0, 0, 0.4)",
        type: "line"
      }
    ]
  };

  // チャートのオプション
  private chartOption: ChartOptions = {
    maintainAspectRatio: false,
    legend: { position: "bottom" },
    elements: {
      point: {
        radius: 8, // 点の大きさ
        hoverRadius: 20 // 点の大きさ(マウスホバー時)
      },
      line: {
        tension: 0, // 線を真っ直ぐにする
        fill: false // 折れ線グラフの塗りつぶしを無効化
      }
    }
  };

スクリーンショット 2019-09-21 18.38.40.png

参考: Elements · Chart.js documentation

Y軸を2つにする: scales.yAxes

 private chartData: ChartData = {
    labels: ["A", "B", "C", "D", "E"],
    datasets: [
      {
        label: "Data One",
        data: [1, 5, 3, 4, 3],
        borderColor: "green",
        backgroundColor: "rgba(0, 255, 0, 0.4)"
      },
      {
        label: "Data Two",
        data: [10, 50, 30, 40, 30],
        borderColor: "red",
        backgroundColor: "rgba(255, 0, 0, 0.4)",
        type: "line",
        yAxisID: "y-axis-2" // Y軸のIDを指定
      }
    ]
  };

  // チャートのオプション
  private chartOption: ChartOptions = {
    maintainAspectRatio: false,
    legend: { position: "bottom" },
    elements: {
      point: { radius: 8, hoverRadius: 20 },
      line: { tension: 0, fill: false }
    },
    scales: {
      yAxes: [
        { 
          // 左の軸。設定はデフォルトのまま
        },
        {
          id: "y-axis-2",
          position: "right" // 右に表示する
        }
      ]
    }
  };

スクリーンショット 2019-09-21 18.43.06.png

参考: Axes · Chart.js documentation

Y軸のラベルを変える: scales.yAxes.ticks

  private chartOption: ChartOptions = {
    maintainAspectRatio: false,
    legend: { position: "bottom" },
    elements: {
      point: { radius: 8, hoverRadius: 20 },
      line: { tension: 0, fill: false }
    },
    scales: {
      yAxes: [
        {
          ticks: {
            // 左の軸は、先頭に'¥'をつける
            callback: (label, index, labels) => "¥" + label.toLocaleString()
          }
        },
        {
          id: "y-axis-2",
          position: "right",
          ticks: {
             // 右の軸は、末尾に'冊'をつける
            callback: (label, index, labels) => label.toLocaleString() + "冊"
          }
        }
      ]
    }
  };

スクリーンショット 2019-09-21 18.46.00.png

ただ、このままだとホバーしたときのツールチップの値は変わらない。。
後述するtooltips.callbacks.labelを使って変更が必要。

参考: Axes · Chart.js documentation
参考: コピペでOK!Chart.js の数字を3桁カンマで表示する方法 – console dot log

横軸を非表示にする: scales.yAxes.display

  private chartOption: ChartOptions = {
    maintainAspectRatio: false,
    legend: { position: "bottom" },
    elements: {
      point: { radius: 8, hoverRadius: 20 },
      line: { tension: 0, fill: false }
    },
    scales: {
      yAxes: [
        {
          ticks: {
            callback: (label, index, labels) => "¥" + label.toLocaleString()
          }
        },
        {
          id: "y-axis-2",
          position: "right",
          display: false, // 右の軸を非表示にする
          ticks: {
            callback: (label, index, labels) => label.toLocaleString() + "冊"
          }
        }
      ]
    },
  }

スクリーンショット 2019-09-21 18.57.45.png

参考: Axes · Chart.js documentation

ホバー時のツールチップで両方表示にする: tooltips.mode

  private chartOption: ChartOptions = {
    maintainAspectRatio: false,
    legend: { position: "bottom" },
    elements: {
      point: { radius: 8, hoverRadius: 20 },
      line: { tension: 0, fill: false }
    },
    scales: {
      yAxes: [
        { ticks: { callback: (label, index, labels) => "¥" + label.toLocaleString() } },
        {
          id: "y-axis-2",
          position: "right",
          ticks: { callback: (label, index, labels) => label.toLocaleString() + "冊" }
        }
      ]
    },
    tooltips: { // ツールチップの設定
      mode: "index" // 全データを表示する
    }
  };

スクリーンショット 2019-09-21 18.50.18.png

参考: Tooltip · Chart.js documentation

ツールチップの表示を変える: tooltips.callbacks.label

  private chartOption: ChartOptions = {
    maintainAspectRatio: false,
    legend: { position: "bottom" },
    elements: {
      point: { radius: 8, hoverRadius: 20 },
      line: { tension: 0, fill: false }
    },
    scales: {
      yAxes: [
        { ticks: { callback: (label, index, labels) => "¥" + label.toLocaleString() } },
        {
          id: "y-axis-2",
          position: "right",
          ticks: { callback: (label, index, labels) => label.toLocaleString() + "冊" }
        }
      ]
    },
    tooltips: { // ツールチップの設定
      mode: "index",
      callbacks: {
        label: function(tooltipItem, data) { // ラベルの表示変更
          // データが'Data One'か'Data Two'かのインデックスを取得
          const index = tooltipItem.datasetIndex;
          if (index === 0) {
            // 凡例にあるラベルを取得
            var label = data.datasets[index].label || "";
            if (label) label += ": ";
            // 該当データを取得して、先頭に'¥'をつける
            label += "¥" + tooltipItem.yLabel.toLocaleString();
            return label;
          } else {
            var label = data.datasets[index].label || "";
            if (label) label += ": ";
            label += tooltipItem.yLabel.toLocaleString() + "冊";
            return label;
          }
        }
      }
    }
  };

スクリーンショット 2019-09-21 18.53.48.png

参考: Tooltip · Chart.js documentation
参考: コピペでOK!Chart.js の数字を3桁カンマで表示する方法 – console dot log

その他の便利関数

画面サイズを判定する

window.matchMedia().matchesでメディアクエリがJavaScriptで使えるらしい。。
以下のような関数を作っておけば、

private isDesktop() {
  return window.matchMedia("screen and (min-width:768px)").matches;
}

画面サイズなどによって、

  1. 横軸ラベルの表示/非表示を切り替えたり、
  2. 適用するスタイルを変えたり、
  3. 点の大きさを変えたり
  4. できる。

参考: Chart.js でレスポンシブ指定をするとサイズが自由に変更できなくなる - 約束の地

カラーコードのHEXをRGBAに変換する

こんな感じで、HEXカラーコードとopacityからRGBAに変換できる。

private convertHex(hex: string, opacity: number) {
  const arr = hex.replace("#", "");
  const r = parseInt(arr.substring(0, 2), 16);
  const g = parseInt(arr.substring(2, 4), 16);
  const b = parseInt(arr.substring(4, 6), 16);

  return "rgba(" + r + "," + g + "," + b + "," + opacity + ")";
}

RGBで入力するのは大変なので、これがあると便利(´ω`)
こんな感じで使えます(´ω`)

{
  label: "Data Two",
  data: [10, 50, 30, 40, 30],
  borderColor: "red",
  backgroundColor: this.convertHex("#FF0000", 0.4),
}

参考: Hex 2 rgba converter - JSFiddle

おわりに

vue-chartjsをつかえば、
TypeScriptなNuxt.jsでも簡単にいろんなグラフが表示できる(´ω`)

こんなのつくってます!!

積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!
積読ハウマッチは、Nuxt.js+Firebaseで開発してます!

もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ

こんな感じで、積読本・読了本の総数でどこまでいけるか表示したりしています♪

要望・感想・アドバイスなどあれば、
公式アカウント(@MemoryLoverz)や開発者(@kira_puka)まで

参考にしたサイト

Originally published at qiita.com

きらぷか@i18n補助ツール『トランスノート』開発者

フリーエンジニア/今はNuxt.js/いつかFlutter 受託&アプリ/Webサービス/ゲームを #個人開発 CS修士→SIer/R&D→フリー #paiza はAランクで満足/AtCoderしたい 仕事依頼やご相談はDMまで Kotlin/Python/Swift/Unity/Java/Haskell/DDD

Crieitは個人で開発中です。 興味がある方は是非記事の投稿をお願いします! どんな軽い内容でも嬉しいです。
なぜCrieitを作ろうと思ったか

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください!

ボードとは?

関連記事

コメント