javascriptvue.jschartsvue-chartjs

Vue Chart.js - Chart is not updating when data is changing


I'm using Vue.js and Chart.js to draw some charts. Each time I call the function generateChart(), the chart is not updated automatically. When I check the data in Vue Devtools, they are correct but the chart does not reflect the data. However, the chart does update when I resize the window.

I feel this is going to be something related with object and array change detection caveats, but I'm not sure what to do.

https://codepen.io/anon/pen/bWRVKB?editors=1010

<template>
    <el-dialog title="Chart" v-model="showGeneratedChart">
        <line-chart :chartData="dataChart"></line-chart>
    </el-dialog>
</template>

<script>
export default {
    data() {
        const self = this;
        return {
            dataChart: {
                labels: [],
                datasets: [
                    {
                        label: "label",
                        backgroundColor: "#FC2525",
                        data: [0, 1, 2, 3, 4],
                    },
                ],
            },
        };
    },
    methods: {
        generateChart() {
            this.dataChart["labels"] = [];
            this.dataChart["datasets"] = [];

            // ... compute datasets and formattedLabels

            this.dataChart["labels"] = formattedLabels;
            this.dataChart["datasets"] = datasets;
        },
    },
};
</script>         

LineChart.js

import { Line, mixins } from 'vue-chartjs'

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

Solution

  • Use a computed property for the chart data. And instead of calling this.renderChart on watch wrap it in a method and reuse that method on mounted and in watch.

    Vue.component("line-chart", {
      extends: VueChartJs.Line,
      props: ["data", "options"],
      mounted() {
        this.renderLineChart();
      },
      computed: {
        chartData: function() {
          return this.data;
        }
      },
      methods: {
        renderLineChart: function() {
        this.renderChart(
          {
            labels: [
              "January",
              "February",
              "March",
              "April",
              "May",
              "June",
              "July"
            ],
            datasets: [
              {
                label: "Data One",
                backgroundColor: "#f87979",
                data: this.chartData
              }
            ]
          },
          { responsive: true, maintainAspectRatio: false }
        );      
        }
      },
      watch: {
        data: function() {
          this._chart.destroy();
          //this.renderChart(this.data, this.options);
          this.renderLineChart();
        }
      }
    });
    
    var vm = new Vue({
      el: ".app",
      data: {
        message: "Hello World",
        dataChart: [10, 39, 10, 40, 39, 0, 0],
        test: [4, 4, 4, 4, 4, 4]
      },
      methods: {
        changeData: function() {
          this.dataChart = [6, 6, 3, 5, 5, 6];
        }
      }
    });
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width">
      <title>Vue.jS Chart</title>
    </head>
    <body>
    <div class="app">
        {{ dataChart }}
       <button v-on:click="changeData">Change data</button>
      <line-chart :data="dataChart" :options="{responsive: true, maintainAspectRatio: false}"></line-chart>
     
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
    <script src="https://unpkg.com/vue-chartjs@2.5.7-rc3/dist/vue-chartjs.full.min.js"></script>
    </body>
    </html>

    You could also make the options a computed property, and if option not going to change much you can setup default props. https://v2.vuejs.org/v2/guide/components.html#Prop-Validation

    Here is a working codepen https://codepen.io/azs06/pen/KmqyaN?editors=1010