Home > Back-end >  Rerender vue-chart in a nuxt.js project
Rerender vue-chart in a nuxt.js project

Time:04-15

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.

CodePudding user response:

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 enter image description here

  • Related