Rerender vue-chart in a nuxt.js project

3.2k Views Asked by At

I am working on a nuxt project and need to draw some charts. For that I use vue-chartjs as a plugin. Unfortunately the chart-data is fetched after the chart is drawn which results in an empty chart. I can't find a way to access the plugin and rerender the chart when the data is ready. Here is the code for the plugin:

// plugins/vue-chart.js

import Vue from 'vue'
import { Bar, Doughnut, Line, Pie } from 'vue-chartjs'

const registerComponent = function (name, originalComponent) {
  Vue.component(
    name,
    {
      extends: originalComponent,
      props: ['data', 'options'],
      mounted () {
        this.renderChart(this.data, this.options)
      },
      updated () {
        this.renderChart(this.data, this.options)
      }
    }
  )
}

registerComponent('BarChart', Bar)
registerComponent('DoughnutChart', Doughnut)
registerComponent('LineChart', Line)
registerComponent('PieChart', Pie)

And here is how I use the plugin to draw a line chart:

components/Charts.vue

<template>
  <client-only>
    <LineChart :data="lineData" :options="options" />
  </client-only>
</template>

<script>
export default {
  data() {
    return {
      loading: true,
      lineData: {
        labels: [],
        datasets: [
          {
            label: 'Close',
            data: [],
          },
          {
            label: 'High',
            data: [],
          },
        ],
      },
      options: {
        responsive: true,
        scales: {
          x: {
            display: true,
            title: {
              display: true,
            },
          },
          y: {
            display: true,
            title: {
              display: true,
              text: 'Value',
            },
          },
        },
      },
    }
  },
  async mounted() {
    try {
      const response = await this.$axios.get('/api/entries')
      if (response.status === 200) {
        for (let i = 0; i < response.data.length; i++) {
          this.lineData.labels.push(response.data[i].date)
          this.lineData.datasets[0].data.push(response.data[i].close)
          this.lineData.datasets[1].data.push(response.data[i].high)
        }
      }
    } catch (e) {
      console.log(e)
    }
  },
}
</script>

I am happy about any suggestions on how to solve this!

Edit I had to downgrade the versions of chart.js and vue-chartjs so that the project compiles.

Here is my package.json

{
  "name": "xyz",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev-fe": "nuxt",
    "build": "nuxt build",
    "start": "nuxt start",
    "generate": "nuxt generate",
    "lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .",
    "lint": "npm run lint:js"
  },
  "dependencies":
    "@nuxtjs/axios": "^5.13.6",
    "chart.js": "^2.7.1",
    "core-js": "^3.15.1",
    "nuxt": "^2.15.7",
    "vue": "^2.6.14",
    "vue-chartjs": "^3.4.0"
  },
  "devDependencies": {
    "@babel/eslint-parser": "^7.14.7",
    "@nuxtjs/eslint-config": "^6.0.1",
    "@nuxtjs/eslint-module": "^3.0.2",
    "@nuxtjs/tailwindcss": "^4.2.0",
    "eslint": "^7.29.0",
    "eslint-plugin-nuxt": "^2.0.0",
    "eslint-plugin-vue": "^7.12.1",
    "postcss": "^8.3.5"
  }
}

I use pretty much use the default in nuxt.config.js except that I added

{ src: '~/plugins/vue-chart.js', mode: 'client' }

to the plugins.

1

There are 1 best solutions below

0
On BEST ANSWER

Alright, I got a working example with the following.

Here is my vue-chartjs.js plugin

import Vue from 'vue'
import { Bar, Doughnut, Line, Pie, mixins } from 'vue-chartjs'

const registerComponent = function (name, originalComponent) {
  Vue.component(name, {
    extends: originalComponent,
    mixins: [mixins.reactiveProp],
    props: {
      chartData: {
        type: Object,
        default: () => {},
      },
      chartOptions: {
        type: Object,
        default: () => {},
      },
    },
    mounted() {
      this.renderChart(this.chartData, this.chartOptions)
    },
  })
}

registerComponent('BarChart', Bar)
registerComponent('DoughnutChart', Doughnut)
registerComponent('LineChart', Line)
registerComponent('PieChart', Pie)

/pages/index.vue

<template>
  <div>
    <line-chart
      :key="updated"
      :chart-data="lineData"
      :chart-options="options"
    />
  </div>
</template>

<script>
import FakeData from '@/fake.json'

export default {
  data() {
    return {
      updated: 0,
      lineData: {
        labels: [],
        datasets: [
          {
            label: 'Data One',
            backgroundColor: '',
            data: [],
          },
          {
            label: 'Data Two',
            backgroundColor: '',
            data: [],
          },
        ],
      },
      options: {
        responsive: true,
        scales: {
          x: {
            display: true,
            title: {
              display: true,
            },
          },
          y: {
            display: true,
            title: {
              display: true,
              text: 'Value',
            },
          },
        },
      },
    }
  },
  async fetch() {
    const response = await fetch('https://jsonplaceholder.typicode.com/todos/1')
    const data = await response.json()
    console.log('fake API title fetched:', data.title)

    const actualData = FakeData.data
    for (let i = 0; i < actualData.length; i++) {
      this.lineData.labels.push(actualData[i].date)
      this.lineData.datasets[0].backgroundColor = actualData[i].color1
      this.lineData.datasets[0].data.push(actualData[i].close)
      this.lineData.datasets[1].backgroundColor = actualData[i].color2
      this.lineData.datasets[1].data.push(actualData[i].high)
    }
    this.updated++
  },
}
</script>

and my fake .json API data, because I didn't have an API to get some real data

{
  "data": [
    {
      "date": "Jan",
      "color1": "#EC368D",
      "color2": "#51E5FF",
      "close": "0.4",
      "high": "0.7"
    },
    {
      "date": "Feb",
      "color1": "#EC368D",
      "color2": "#51E5FF",
      "close": "0.2",
      "high": "0.5"
    },
    {
      "date": "Mar",
      "color1": "#EC368D",
      "color2": "#51E5FF",
      "close": "0.6",
      "high": "0.8"
    }
  ]
}

I still mocked an API call, so it should work great as an example for you.

The idea behind this was to use the key hack, which is not really that good but eh, the maintainer is not really into helping people properly IMO.

So this is still the best that is available.

Here is my final result and the related Github repo.

enter image description here