I have created a doughnut chart using vue-chart.js, Chart.js and using some values which are within my vuex state. The chart works apart from when the vuex state is updated the chart doesn't updated too.
I have tried to use computed properties to try keep the chart up to date. But then I get the following errors:
Error in callback for watcher "chartData": "TypeError: Cannot set property 'data' of undefined"
TypeError: Cannot set property 'data' of undefined
DoughnutChart.vue:
<script>
import { Doughnut, mixins } from 'vue-chartjs';
const { reactiveProp } = mixins;
export default {
extends: Doughnut,
mixins: [reactiveProp],
props: ['chartData', 'options'],
mounted () {
this.renderChart(this.chartdata, this.options)
}
}
</script>
AppCharts.vue:
<template>
<div id="chart_section">
<h2 class="section_heading">Charts</h2>
<div id="charts">
<DoughnutChart :chart-data="datacollection" :options="chartOptions" class="chart"></DoughnutChart>
</div>
</div>
</template>
<script>
import DoughnutChart from './DoughnutChart';
import { mapGetters } from 'vuex';
export default {
components: {
DoughnutChart
},
computed: {
...mapGetters(['boardColumnData']),
datacollection() {
return {
datasets: [{
data: [this.boardColumnData[0].total.$numberDecimal, this.boardColumnData[1].total.$numberDecimal, this.boardColumnData[2].total.$numberDecimal, this.boardColumnData[3].total.$numberDecimal],
backgroundColor: [
'#83dd1a',
'#d5d814',
'#fdab2f',
'#1ad4dd'
],
borderColor: [
'#83dd1a',
'#d5d814',
'#fdab2f',
'#1ad4dd'
],
}]
}
},
},
data() {
return {
chartOptions: null
}
},
mounted () {
this.fillData();
},
methods: {
fillData() {
this.chartOptions = {
responsive: true,
maintainAspectRatio: false
}
}
}
}
</script>
this is what boardColumnData looks like after getting the state:
[
{
"name":"Opportunities",
"percentage":{
"$numberDecimal":"0"
},
"total":{
"$numberDecimal":70269
}
},
{
"name":"Prospects",
"percentage":{
"$numberDecimal":"0.25"
},
"total":{
"$numberDecimal":0
}
},
{
"name":"Proposals",
"percentage":{
"$numberDecimal":"0.5"
},
"total":{
"$numberDecimal":5376
}
},
{
"name":"Presentations",
"percentage":{
"$numberDecimal":"0.75"
},
"total":{
"$numberDecimal":21480
}
},
]
The $numberDecimal value is what is updated inside vuex when an event on another component happens. When these values are changed I want the chart to update with the new values.
You must pass the data in the prop. I.e. fastest solution for you would be to have fillData() return the datacollection in the correct format.
E.g.
fillData(){
return {
datasets: [
{
data: [
this.boardColumnData[0].total.$numberDecimal,
this.boardColumnData[1].total.$numberDecimal,
this.boardColumnData[2].total.$numberDecimal,
this.boardColumnData[3].total.$numberDecimal,
],
backgroundColor: ["#83dd1a", "#d5d814", "#fdab2f", "#1ad4dd"],
borderColor: ["#83dd1a", "#d5d814", "#fdab2f", "#1ad4dd"],
},
];
}
}
You need to do the same for the options, but pass them in a new options=yourOptions() prop.
This drove me absolutely bonkers. I hope this still helps you (or someone else).
Related
I trying to make a COVID19 visualization site using Chart.js and VueJs
this is My App.vue that contains the API call and stores the data into arrays
<template>
<div id="app" class="container">
<div class="row mt-5" v-if="PositiveCases.length > 0">
<div class="col">
<h2>Positives</h2>
<lineChart :chartData="PositiveCases" :options="chartOptions" label="Positive" />
</div>
</div>
</div>
</template>
<script>
import axios from "axios";
import moment from 'moment'
import lineChart from "./components/lineChart.vue";
export default {
name: 'App',
components: {
lineChart
},
data(){
return{
PositiveCases : [],
Deaths: [],
Recoverd: [],
chartOptions: {
responsive: true,
maintainAspectRatio: false
}
}
},
async created(){
const {data} = await axios.get('https://api.covid19api.com/live/country/egypt')
//console.log(data);
data.forEach(d => {
const date = moment(d.Date,"YYYYMMDD").format("MM/DD")
const {Confirmed,Deaths,Recovered} = d
this.PositiveCases.push({date, total : Confirmed})
this.Deaths.push({date, total : Deaths})
this.Recoverd.push({date, total : Recovered})
// console.log("PositiveCases",this.PositiveCases);
// console.log("Deaths",this.Deaths);
// console.log("Recoverd",this.Recoverd);
});
}
}
</script>
and this is my lineChart.vue that contains the Line chart code the data stored correctly in both the dates and totals
<script>
import {Line} from 'vue-chartjs'
export default {
extends: Line,
props: {
label:{
type: String,
},
chartData:{
type: Array,
},
options:{
type: Object,
}
},
mounted(){
const dates = this.chartData.map(d => d.date).reverse()
const totals = this.chartData.map(d => d.total).reverse()
console.log("dates",dates);
console.log("totals",totals);
this.renderChart({
labels: dates,
datasets: [{
label: this.label,
data: totals,
}],
},this.options
)
}
}
</script>
the error in the console says
want to know what is the solution, all the data are stored correctly in both files
You are using V4 of vue-chart.js, the chart creation process has been changed as you can read here in the migration guide.
So instead of calling this.renderChart which was the old syntax you now have to use the actual component and pass the data to it like so:
<template>
<Bar :chart-data="chartData" />
</template>
<script>
// DataPage.vue
import { Bar } from 'vue-chartjs'
import { Chart, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale } from 'chart.js'
Chart.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale)
export default {
name: 'BarChart',
components: { Bar },
data() {
return {
chartData: {
labels: [ 'January', 'February', 'March'],
datasets: [
{
label: 'Data One',
backgroundColor: '#f87979',
data: [40, 20, 12]
}
]
}
}
}
}
</script>
My problem: I followed the doc from vue-chartjs but the chart is not rendering the data.
Here is my code:
My component Chart.vue
<script>
import { Line } from 'vue3-chart-v2'
export default {
extends: Line,
props: {
chartdata: {
type: Object,
default: null
},
options: {
type: Object,
default: null
}
},
mounted () {
this.renderChart(this.chartdata, this.options)
}
}
</script>
My Home.vue code:
<template>
<div class="container">
<line-chart
v-if="loaded"
:chartdata="chartdata"
:options="options"/>
</div>
</template>
<script>
import LineChart from '../components/Chart.vue'
export default {
name: 'LineChartContainer',
components: { LineChart },
data: () => ({
loaded: false,
chartdata: null,
options: {
responsive: true,
}
}),
async mounted () {
this.loaded = false
try {
const quotes = [
1, 2, 3, 4,
]
this.chartdata = quotes
this.loaded = true
} catch (e) {
console.error(e)
}
}
}
</script>
When I'm rendering the Home view, I see the axis of the chart, but no data inside...
In the console, I don't have any errors.
Later, I would like to fetch data from an API. Thus, the quotes would receive data from an external API. Here I just put some fake data for testing.
Does anyone can help me ? Thks
I was missing the import { defineComponent } from 'vue' in my component. Now it works using the below code.
<template>
<div class="container">
<line-chart
v-if="loaded"
:chartdata="chartdata"
:options="options"/>
</div>
</template>
<script>
import LineChart from '../components/Chart.vue'
import { defineComponent } from 'vue'
export default defineComponent({
name: 'LineChartContainer',
components: { LineChart },
data: () => ({
loaded: false,
chartdata: null,
options: {
responsive: true,
}
}),
async mounted () {
this.loaded = false
try {
const quotes = [
1, 2, 3, 4,
]
this.chartdata = quotes
this.loaded = true
} catch (e) {
console.error(e)
}
}
})
</script>
working on a covid app to get familiar with vue2js.
Am now trying to get a graph with vue-chartjs but am failing to pass the data to the graph/chart component.
I make an API request with vuex and passing the data to my component: CountryGraph.vue which contains a Graph.vue with the chart itself.
vuex -> CountryGraph.vue -> Graph.vue
Passing data into CountryGraph.vue works:
But when I try to pass my data (countryGraph) as props to my char/Graph.vue component, then it is not done and I get in Graph.vue only the value undefined:
Why?
Below my code, first the CountryGraph.vue:
<template>
<section class="countryGraph">
<LineChart
:chartdata="chartData"
:options="chartOptions"
/>
</section>
</template>
<script>
import { mapGetters, mapActions } from "vuex";
import LineChart from "../graph/Graph";
export default {
name: "CountryGraph",
components: { LineChart },
data: () => ({
chartData: {
labels: this.countryGraph.map((el) => el.date),
datasets: [
{
label: "Confirmed",
backgroundColor: "#f87979",
data: this.countryGraph.map(
(el) => el.confirmed
),
},
],
},
chartOptions: {
responsive: true,
maintainAspectRatio: false,
},
}),
methods: {
...mapActions(["selectCountryGraph"]),
},
computed: {
...mapGetters(["countryGraph"]),
},
};
</script>
<style></style>
And my chart/Graph.vue component which is made so, that I can reuse it (as stated in vue-chartjs guide):
<script>
import { Bar } from "vue-chartjs";
export default {
extends: Bar,
props: {
chartdata: {
type: Object,
default: null,
},
options: {
type: Object,
default: null,
},
},
mounted() {
this.renderChart(this.chartdata, this.options);
},
};
</script>
<style />
When I use mocked data, like instead of
labels: this.countryGraph.map((el) => el.data)
I do labels: ["q", "w", "e", "r", "t"]
and instead of
data: this.countryGraph.map(el => el.confirmed)
I do data: [0, 1, 2, 3, 4]
everything works fine.
Also, when I pass my variables directly into the component, like:
<LineChart
:chartdata="this.countryGraph.map((el) => el.data)"
:options="chartOptions"
/>
Then I can see the data as props in the child (Graph.vue) component.
But in this case I use v-bind: and in the earlier one not. Maybe that is the problem?
A couple issues to note:
It looks like you're mapping a nonexisting property (el.data should be el.date). Possibly just a typo in the question.
this.countryGraph.map((el) => el.data) ❌
^
data() is not reactive, and cannot rely on computed props, so the countryGraph computed prop will not be available in data() and will not update chartData with changes. One way to fix this is to make chartData a computed prop:
export default {
computed: {
...mapGetters(["countryGraph"]),
// don't use an arrow function here, as we need access to component instance (i.e., this.countryGraph)
chartData() {
return {
labels: this.countryGraph.map((el) => el.date),
datasets: [
{
label: "Confirmed",
backgroundColor: "#f87979",
data: this.countryGraph.map((el) => el.confirmed),
},
],
}
}
}
}
I'm trying to use a component called "Vue-Chartjs" to create a LineChart.
But when i try to use the demo of Line Chart. An error occured.
I got: Uncaught TypeError: Cannot read property 'reactiveProp' of undefined
My line-chart.js:
import { Line, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins
export default {
extends: Line,
mixins: [reactiveProp],
props: ['options'],
mounted () {
// this.chartData is created in the mixin.
// If you want to pass options please create a local options object
this.renderChart(this.chartData, this.options)
}
}
My dashboard.vue :
<template>
<div class="small">
<p>Dashboard</p>
<line-chart :chart-data="datacollection"></line-chart>
<button #click="fillData()">Randomize</button>
</div>
</template>
<script>
import LineChart from '../charts/line-chart'
export default {
components:{
LineChart,
},
data () {
return {
datacollection: null
}
},
mounted () {
this.fillData()
},
methods: {
fillData () {
this.datacollection = {
labels: [this.getRandomInt(), this.getRandomInt()],
datasets: [
{
label: 'Data One',
backgroundColor: '#f87979',
data: [this.getRandomInt(), this.getRandomInt()]
}, {
label: 'Data One',
backgroundColor: '#f87979',
data: [this.getRandomInt(), this.getRandomInt()]
}
]
}
},
getRandomInt () {
return Math.floor(Math.random() * (50 - 5 + 1)) + 5
}
}
}
</script>
You can go one step further and extract the reactiveProp out of mixins
import { Line, mixins: { reactiveProp } } from 'vue-chartjs'
Finnaly i fixed it by clear the package-lock.json, remove node_modules
run
npm install vue-chartjs chart.js --save
and
run
npm install
and it is ok!
I have a parent component with 2 child components that both inherit from the same base component. (This parent component is being created and used in a Vue Storybook). Both SiblingAComponent and SiblingBComponent inherit the same BaseComponent, and instantiate the same inherited data classInstance, which is a vanilla JS class instance from another library. I am trying to access this classInstance from the parent component to pass as data into the second sibling component (in this case, from SiblingAComponent to SiblingBComponent), by using an reference siblingARef. However, I get this error from the storybook compiler:
too much recursion
isArguments#http://localhost:6006/vendors~main.9107ef8d0bc0558399e1.bundle.js:49010:16
keys#http://localhost:6006/vendors~main.9107ef8d0bc0558399e1.bundle.js:49073:28
_traverse#http://localhost:6006/vendors~main.9107ef8d0bc0558399e1.bundle.js:119972:19
_traverse#http://localhost:6006/vendors~main.9107ef8d0bc0558399e1.bundle.js:119974:28
ParentComponent Story:
storiesOf("ParentComponent Story", module)
.addDecorator(
withKnobs({
escapeHTML: false
})
)
.add("Passing data from A to B", () => ({
name: 'ParentComponent',
components: {
SiblingAComponent,
SiblingBComponent,
},
data() {
return {
siblingAData: [....], // array of objects
siblingAOptions: {
axes: {},
height: "50px",
},
siblingBData: [...], // array of objects
siblingBOptions: null,
}
},
mounted() {
const siblingAInstance = this.$refs.siblingARef.classInstance;
const newOptions = {
legend: {
external: {
reference: siblingAInstance,
},
},
};
// this line is where I am getting an error
this.siblingBOptions = legendExternal;
},
template: `
<SiblingAComponent ref="siblingARef" :data="siblingAData" :options="siblingAOptions"/>
<SiblingBComponent v-if="siblingBData" :data="siblingBData" :options="siblingBOptions"/>
`,
}));
SiblingAComponent:
<template>
<div class="sibling-a-component"></div>
</template>
<script>
import { ComponentA } from '#libraryexample/components';
import BaseComponent from './base-component.vue';
export default {
name: 'SiblingAComponent',
extends: BaseComponent,
mounted() {
this.classInstance = new ComponentA(this.$el, {
data: this.data,
options: this.options,
});
},
};
</script>
SiblingBComponent:
<template>
<div class="sibling-b-component"></div>
</template>
<script>
import { ComponentB } from '#libraryexample/components';
import BaseComponent from './base-component.vue';
export default {
name: 'SiblingBComponent',
extends: BaseComponent,
mounted() {
this.classInstance = new ComponentB(this.$el, {
data: this.data,
options: this.options,
});
},
};
</script>
BaseComponent:
<script>
export default {
name: 'BaseComponent',
data() {
return {
classInstance: null,
};
},
props: {
data: { type: [Object, Array], required: true },
options: { type: Object, required: true },
},
};
</script>
Coming from the Angular and React worlds, using a reference to access a Vanilla class instance from another Component is nothing new, even if it's unconventional. I am new to Vue, so I am wondering why would trying to access a class instance fail (works fine for primitive data types) and give me such a weird error? Where is the recursion occurring?