How to Use Chart.js in a Vue Project demo repo

Chart.js (opens new window) is a simple HTML5 Charts using the <canvas> tag.

vue-chartjs (opens new window) is a wrapper for Chart.js in Vue.

Before, I used vue-charts (opens new window) as the wrapper. But since it is now deprecated, I use another wrapper and updated this page.

WARNING

vue-chartjs is not compatible with Vue 3 at the time of this writing, so this tutorial is only for Vue 2.

# 👣 Steps

  1. Create a Vue project and install Chart.js and vue-chartjs.
npm i -g @vue/cli
vue create vue2-demo
cd vue2-demo
npm i chart.js vue-chartjs
  1. Create a page for the chart, e.g. in src/views/chart.vue. Here I will create bar, doughnut, and line charts.
<template>
  <div>
    <h1>Chart Demo</h1>

    <div class="grid">
      <ChartBar />
      <ChartDoughnut />
      <ChartLine />
    </div>
  </div>
</template>

<script>
import ChartBar from "@/components/ChartBar";
import ChartDoughnut from "@/components/ChartDoughnut";
import ChartLine from "@/components/ChartLine";

export default {
  components: {
    ChartBar,
    ChartDoughnut,
    ChartLine
  }
};
</script>

<style lang="scss" scoped>
.grid {
  display: grid;
  gap: 2rem;

  @media (min-width: 768px) {
    grid-template-columns: 1fr 1fr;
  }
}
</style>
  1. Create a .js components for the base charts in src/components.

In src/components/ChartBarBase.js:

import { Bar, mixins } from "vue-chartjs";
const { reactiveProp } = mixins;

export default {
  extends: Bar,
  mixins: [reactiveProp],
  props: ["chartData"],
  data() {
    return {
      options: {
        scales: {
          yAxes: [
            {
              ticks: {
                beginAtZero: true
              }
            }
          ]
        },
        responsive: true
      }
    };
  },
  mounted() {
    this.renderChart(this.chartData, this.options);
  }
};

In src/components/ChartDoughnutBase.js:

import { Doughnut, mixins } from "vue-chartjs";
const { reactiveProp } = mixins;

export default {
  extends: Doughnut,
  mixins: [reactiveProp],
  props: ["chartData"],
  data() {
    return {
      options: {
        scales: {
          yAxes: [
            {
              ticks: {
                beginAtZero: true
              }
            }
          ]
        },
        responsive: true
      }
    };
  },
  mounted() {
    this.renderChart(this.chartData, this.options);
  }
};

In src/components/ChartLineBase.js:

import { Line, mixins } from "vue-chartjs";
const { reactiveProp } = mixins;

export default {
  extends: Line,
  mixins: [reactiveProp],
  props: ["chartData"],
  data() {
    return {
      options: {
        scales: {
          yAxes: [
            {
              ticks: {
                beginAtZero: true
              }
            }
          ]
        },
        responsive: true
      }
    };
  },
  mounted() {
    this.renderChart(this.chartData, this.options);
  }
};
  1. Create the wrapper component for each chart in src/components.

In src/components/ChartBar.vue:

<template>
  <b-card title="Bar">
    <b-card img-bottom>
      <ChartBarBase :chart-data="chartData" />
    </b-card>
  </b-card>
</template>

<script>
import ChartBarBase from "@/components/ChartBarBase";

export default {
  components: {
    ChartBarBase
  },
  data() {
    return {
      chartData: null
    };
  },
  mounted() {
    this.fillData();
  },
  methods: {
    fillData() {
      this.chartData = {
        labels: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
        datasets: [
          {
            backgroundColor: "#de98ab",
            borderColor: "#0c0306",
            data: [1, 3, 5, 7, 2, 4, 6],
            label: "Bar"
          },
          {
            backgroundColor: "#98ddde",
            borderColor: "#030c0c",
            data: [1, 5, 2, 6, 3, 7, 4],
            label: "Baz"
          }
        ]
      };
    }
  }
};
</script>

In src/components/ChartDoughnut.vue:

<template>
  <b-card title="Doughnut">
    <b-card img-bottom>
      <ChartDoughnutBase :chart-data="chartData" />
    </b-card>
  </b-card>
</template>

<script>
import ChartDoughnutBase from "@/components/ChartDoughnutBase";

export default {
  components: {
    ChartDoughnutBase
  },
  data() {
    return {
      chartData: null
    };
  },
  mounted() {
    this.fillData();
  },
  methods: {
    fillData() {
      this.chartData = {
        labels: ["Foo", "Bar", "Baz"],
        datasets: [
          {
            backgroundColor: ["#f36e60", "#ffdb3b", "#185190"],
            hoverBackgroundColor: ["#fbd2cd", "#fef5c9", "#d1e3f7"],
            data: [10, 20, 40]
          }
        ]
      };
    }
  }
};
</script>

In src/components/ChartLine.vue:

<template>
  <b-card title="Line">
    <b-form-group>
      <b-form-radio
        v-for="(item, index) in btn"
        :key="index"
        v-model="radio"
        :name="item.label"
        :value="item.value"
        @change="updateChart"
      >
        {{ item.label }}
      </b-form-radio>
    </b-form-group>

    <b-card img-bottom>
      <ChartLineBase :chart-data="chartData" />
    </b-card>
  </b-card>
</template>

<script>
import ChartLineBase from "@/components/ChartLineBase";

export default {
  components: {
    ChartLineBase
  },
  data() {
    return {
      btn: [
        { label: "Today", value: "day" },
        { label: "This Week", value: "week" }
      ],
      chartData: null,
      data: {
        day: [1, 3, 5, 3, 1],
        week: [12, 14, 16, 18, 11, 13, 15]
      },
      labels: {
        day: [8, 10, 12, 14, 16],
        week: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
      },
      radio: "day"
    };
  },
  mounted() {
    this.fillData();
  },
  methods: {
    fillData() {
      this.chartData = {
        labels: this.labels[this.radio],
        datasets: [
          {
            borderColor: "#81894e",
            data: this.data[this.radio],
            label: "Foo"
          }
        ]
      };
    },
    updateChart() {
      this.$nextTick(() => {
        this.fillData();
      });
    }
  }
};
</script>

# 📖 Explanation

# Base

  • extends extends the imported base chart.
  • reactiveProp watches changes to the prop, so the chart can update or re-render if the data has changed.
  • props registers the prop for passing data to the chart base.
  • options is for storing options that will be used by the respective chart. You can pass them as a prop in each chart component instead if you need to use unique options in every chart.
    • beginAtZero makes the chart starts from zero if set to true.
    • responsive makes the chart responsive if set to true.
  • Calling this.renderChart() creates the chart instance.

INFO

Separating the base from the wrapper component makes the component reusable because you can use one base for multiple charts with the same type but with different data. But if you only need one chart, you can input the data directly inside the base component. Not that I recommend it.

# Bar

  • chartData is initially null. After mounted, the fillData method is called and fill the data.
  • labels sets the x-axis labels.
  • datasets is an array for dataset objects.
    • backgroundcolor changes the background color.
    • bordercolor specifies the border color.
    • data accepts an array of data.
    • label is for the data name.

# Doughnut

  • hoverBackgroundColor is for setting the background color when it is hovered over.

# Line

  • updateChart is called instead of fillData because @change can precede v-model, meaning the chart sees the radio value just before it changes, making it show the wrong data. I heard it is different for different browsers, but it happens in Firefox. To be safe, just wait for the next tick before re-filling the data.
Last updated: 11/1/2020, 3:28:11 PM