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.
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.