Ryan H
Ryan H

Reputation: 2973

Chart JS isn't dynamically updating with new data with update() method

I'm using Chart JS with the Vue Chart JS wrapper inside of my Nuxt JS project. For some reason, despite new data my chart isn't updating with new data. My charts are set up with a component named LineChart.vue, along with a plugin called LineChart.js inside of my plugins directory.

My component looks like:

<template>
  <div>
    <line-chart :chart-data="customChartData" class="data-chart"></line-chart>
  </div>
</template>

<style>
.data-chart canvas {
  width: 100% !important;
  height: auto !important;
}
</style>

<script>
import LineChart from '~/plugins/LineChart.js'
export default {
  components: {
    LineChart
  },
  props: {
    chartKey: {
      type: Number,
      default: 0
    },
    groupBy: {
      type: String,
      default: 'hourly'
    },
    labels: {
      type: Array,
      default: null
    },
    datasets: {
      type: Array,
      default: null
    },
    options: {
      type: Object,
      default: () => ({})
    }
  },
  data () {
    return {
      customChartData: {}
    }
  },
  mounted () {
    this.fillData()
  },
  methods: {

    /*
    ** Fill data
    */
    fillData () {

      const labelsToUse = []
      const labels = this.labels
      const first = labels[0]
      const last = labels[labels.length - 1]
      let diff = 0

      for (const [index, label] of labels.entries()) {
        const periodStart = this.$dayjs(Date.parse(first))
        const periodFinish = this.$dayjs(Date.parse(last))
        diff = periodFinish.diff(periodStart, 'day', true)

        if (!diff) {
          labelsToUse.push(this.$dayjs.tz(label, this.$auth.user.timezone).format('MMM Do'))
        }

        labelsToUse.push(this.$dayjs.tz(label, this.$auth.user.timezone).format(`${diff > 1 ? 'MMM Do' : 'HH:mm'}`))
      }

      this.customChartData = {
        labels: labelsToUse,
        datasets: this.datasets,
        options: this.options
      }
    }

  }
}
</script>

and my plugin looks like...

import { Line, mixins } from 'vue-chartjs'
import _ from 'lodash';
const { reactiveProp } = mixins

export default {
  extends: Line,
  mixins: [reactiveProp],
  data () {
    return {
      options: {
        responsive: true,
        maintainAspectRatio: false,
        legend: {
          display: false
        },
        scales: {
          yAxes: [{
            gridLines: {
              display: true,
              color: '#eceaea'
            },
          }],
          xAxes: [{
            gridLines: {
              display: false
            },
            ticks: {
              autoSkip: true,
              maxTicksLimit: window.innerWidth < 575 ? 2.1 : 4.1,
              maxRotation: 0,
              minRotation: 0
            }
          }]
        },
        elements: {
          point: {
            radius: 0,
            hitRadius: 35
          }
        }
      }
    }
  },
  mounted () {
    this.renderLineChart()
  },
  methods: {

    /*
    ** Get chart options
    */
    getChartOptions () {
      return this.options
    },

    /*
    ** Render a line chart
    */
    renderLineChart () {
      const options = this.getChartOptions()

      // this.chartdata is created in the mixin.
      // If you want to pass options please create a local options object
      this.renderChart(this.localData, options)
    }

  },
  computed: {
    localData: function() {
      return this.chartData
    }
  },
  watch: {
    chartData: {
      handler: function (val, oldVal) {
        const options = this.getChartOptions()
        const clone = _.cloneDeep(this.localData)

        console.log(this.localData)
        this._data._chart.update()
        this.renderChart(clone, options)
      },
      deep: true
    }
  }
}

When I log console.log(this.localData) inside of my watch method of my plugin, I'm not seeing my labels get updated with the new labels and data that are pushed from :labels="server[Object.keys(server)[0]].history.labels" inside of my webpage where I'm calling my line chart, what am I missing?

My page which called my chart component is:

<div class="col-span-12" v-for="(server, index) in servers" :key="index">
  <div class="bg-white p-6 rounded-md">
    <h3 class="text-md font-medium text-gray-900">{{ server[Object.keys(server)[0]].agent.agent }}</h3>
  </div>
  <hr v-if="server[Object.keys(server)[0]].history" />
  <div v-if="server[Object.keys(server)[0]].history" class="bg-white p-6 rounded-md">
    <h3 class="text-md font-medium text-gray-500 mb-3">Free memory</h3>
    <div class="chart chart--generic">
      <LineChart
        :labels="server[Object.keys(server)[0]].history.labels"
        :datasets="[{
          fill: false,
          borderWidth: 2.5,
          pointBackgroundColor: '#fff',
          borderColor: '#5046e5',
          data: server[Object.keys(server)[0]].history.data,
        }]"
      />
    </div>
  </div>
</div>

Upvotes: 1

Views: 1522

Answers (2)

RuNpiXelruN
RuNpiXelruN

Reputation: 1920

I'm currently building an app and using Nuxt and ApexCharts and had a very similar problem. My data was updating but I was seeing no change in my chart. How I solved this problem was to add a key to my chart component and manually force it to re-render. Vue uses the key attribute to keep track of what has changed and what hasn't. By supplying a new one we force a re-render.

An example of my solution is,

<apexcharts
  v-if="chartOptions"
  :id="chartId"
  :key="key"
  class="am2-utility-chart"
  :class="[isCategoryType ? 'marginLeft' : '']"
  :ref="chartId"
  :width="chartOptions.chart.width"
  :height="chartOptions.chart.height"
  :type="chartOptions.type"
  :options="chartOptions"
  :series="series"
/>

where key is a prop of type string, and from the parent i'm generating a new key by executing a helper function,

export function generateRandomString(value) {
  let r = Math.random().toString(36).substring(7)

  if (!value) return r
  return `${value}_${r}`
}

*the value argument in the function above is optional. I was passing the chart name to give the key more context.

Let me know if that helps, I'd be interested in hearing.

Upvotes: 1

Alex Pinilla
Alex Pinilla

Reputation: 101

After updating the array labels, You could try to call your fillData() method.

Hope it works

Upvotes: 3

Related Questions