Vue pie chart show after a while (like show console with a warning) - vue.js

I am trying to fill a pie chart on my vue application, I can correctly fill data into it, but the page didn't show immediately the pie chart, but after a while (like if a show console), and I got a warning in console :
vue.esm.js?efeb:628 [Vue warn]: Invalid prop: type check failed for
prop "chartData". Expected Object, got Null
found in
--->
at src/components/StastCard.vue
at src/App.vue
Here my code (Maybe there was another way to fill data, but I only succesfully done it in this way):
StastCard.vue:
<template>
<div>
<div class="container">
<div class="row">
<div class="col-sm">
<pie-chart :chartData="dataChart"></pie-chart>
</div>
<div class="col-sm"></div>
<div class="col-sm"></div>
</div>
</div>
</div>
</template>
<script>
import DataService from '#/services/DataService'
import PieChart from "#/plugins/PieChart.js";
export default {
name: 'StastCard',
props: {
username: {
type: String
}
},
components: {
PieChart
},
data: function() {
return {
dataChart: {
labels: ["Km", "KJ", "HB"],
datasets: [
{
label: "Data One",
backgroundColor: ["#41B883", "#E46651", "#00D8FF"],
data: [1, 10, 5]
}
]
},
}
},
methods: {
async addData() {
this.firstValue=DataService.getFirstValue()
this.secondValue=DataService.getSecondValue()
this.thirdValue=DataService.getThirdValue()
this.dataChart.labels.pop()
this.dataChart.labels.pop()
this.dataChart.labels.pop()
this.dataChart.labels.push(["Km"])
this.dataChart.labels.push(["KJ"])
this.dataChart.labels.push(["HB"])
this.dataChart.datasets[0].data.pop()
this.dataChart.datasets[0].data.pop()
this.dataChart.datasets[0].data.pop()
this.dataChart.datasets[0].data.push(this.firstValue)
this.dataChart.datasets[0].data.push(this.secondValue)
this.dataChart.datasets[0].data.push(this.thirdValue)
},
},
mounted() {
this.addData()
}
}
</script>
And here my PieChart.js
import { Pie, mixins } from 'vue-chartjs'
export default {
extends: Pie,
props: ['chartData', 'options'],
mounted() {
this.renderChart(this.chartData, this.options)
}
}
What am I doing wrong? Why my pie chart is not immediately displayed? Thank you

First, I think you might want to use reactiveProp to make your chart reactive with data changes.
Secondly, because of vue-chartjs will render child component before parent component, so you will get the Invalid prop warning. To fix it, you can change from mounted to created hook. You can find more information here.
import { Pie, mixins } from 'vue-chartjs'
export default {
extends: Pie,
mixins: [mixins.reactiveProp],
created() {
this.renderChart(this.chartData, {})
}
}
Lastly, you should assign chartData object to a new reference to make Vue reactive. An easy way is using JSON.parse(JSON.stringify())
methods: {
async addData() {
this.firstValue=DataService.getFirstValue()
this.secondValue=DataService.getSecondValue()
this.thirdValue=DataService.getThirdValue()
this.dataChart.labels.pop()
this.dataChart.labels.pop()
this.dataChart.labels.pop()
this.dataChart.labels.push(["Km"])
this.dataChart.labels.push(["KJ"])
this.dataChart.labels.push(["HB"])
this.dataChart.datasets[0].data.pop()
this.dataChart.datasets[0].data.pop()
this.dataChart.datasets[0].data.pop()
this.dataChart.datasets[0].data.push(this.firstValue)
this.dataChart.datasets[0].data.push(this.secondValue)
this.dataChart.datasets[0].data.push(this.thirdValue)
this.dataChart = JSON.parse(JSON.stringify(this.dataChart))
},
},

I finally found a solution, I change the .js file with this:
import { Pie } from 'vue-chartjs'
export default {
extends: Pie,
props: {
chartdata: {
type: Object,
default: null
},
options: {
type: Object,
default: null
}
},
methods: {
renderpie() {
this.renderChart(this.chartdata, this.options)
}
},
mounted() {}
}
Here my view:
<template>
<div>
<div class="container">
<div class="row">
<div class="col-sm">
<pie-chart :chartData="dataChart"></pie-chart>
</div>
<div class="col-sm"></div>
<div class="col-sm"></div>
</div>
</div>
</div>
</template>
<script>
import DataService from '#/services/DataService'
import PieChart from "#/plugins/PieChart.js";
export default {
name: 'StastCard',
props: {
username: {
type: String
}
},
components: {
PieChart
},
data: function() {
return {
dataChart: {},
firstValue:'',
secondValue:'',
thirdValue:''
}
},
methods: {
addData() {
this.firstValue=DataService.getFirstValue()
this.secondValue=DataService.getSecondValue()
this.thirdValue=DataService.getThirdValue()
var hrate = []
this.heart_rate.forEach(el => {
hrate.push(el.rate)
})
this.dataChart = {
labels: ['Km', 'Kj', 'HB'],
datasets: [
{
label: 'Data One',
backgroundColor: ['#41B883', '#E46651', '#00D8FF'],
data: [this.firstValue,this.secondValue,this.thirdValue]
}
]
}
},
},
async created() {
await this.addData()
this.$refs.pie.renderpie()
}
}
</script>

Related

Attribute patch in pinia store is working from one component, but not from another (Nuxt.js)

I've those two components using Nuxt 3.
The setActive method in component 1 changes the state of activeColor, but the cancelEdit method in component 2 does not.
Any idea why this is the case?
Component 1
Here the setActive method changes activeColor:
<template>
<div class="f-button" #click="addColor">+ Add Color</div>
{{ activeColor }}
<div class="f-colors">
<Color v-for="color in colors" :color="color" :edit="activeColor === color" #click="setActive(color)"/>
</div>
</template>
<script>
import {useProjectStore} from "~/stores/projects";
import {storeToRefs} from "pinia";
import Color from "~/components/Color.vue";
export default defineComponent({
name: "ColorManagement",
components: {Color},
setup() {
const projectStore = useProjectStore()
const { getColors, getActiveColor } = storeToRefs(projectStore);
return {
projectStore,
colors: getColors,
activeColor: getActiveColor
};
},
methods: {
addColor() {
...
},
setActive(color) {
this.projectStore.$patch({ activeColor: color })
}
}
});
</script>
Component 2
Here the cancelEdit method doesn't change activeColor:
<div class="f-color">
<div class="f-color__actions" v-if="edit">
<div class="f-color__action" #click="cancelEdit">
<Cancel /><span>Cancel</span>
</div>
</div>
</div>
</template>
<script>
import Cancel from "~/components/icons/Cancel.vue";
import {useProjectStore} from "~/stores/projects";
import {storeToRefs} from "pinia";
export default defineComponent({
name: "Color",
components: {Cancel},
props: ["color","edit"],
setup() {
const projectStore = useProjectStore()
const { activeColor } = storeToRefs(projectStore);
return {
projectStore,
activeColor
};
},
methods: {
cancelEdit() {
this.projectStore.$patch({ activeColor: false })
}
}
});
</script>
nuxt.config.ts
export default defineNuxtConfig({
vite: {
css: {
preprocessorOptions: {
scss: {
additionalData: '#use "#/assets/styles/_styles.scss" as *;'
}
}
}
},
modules: ['#pinia/nuxt']
})
Store
import { defineStore } from "pinia";
export const useProjectStore = defineStore({
id: 'project',
state: () => {
return {
project: {
colors: [{ color: false, name: '' }]
},
activeColor: null
}
},
getters: {
getColors(state){
return state.project.colors || [];
},
getActiveColor(state){
return state.activeColor;
}
}
});
Ok, if I got this correctly, the deal is this:
Your so called Component 2 is the <Color ... component being used in Component 1, right?
When you trigger cancelEdit inside Component 2 (aka Color) you are also triggering the logic from setActive due to this <Color ...#click="setActive(color)"...so your activeColor is set to false (from the cancelEdit method) but right after it is set to active again, got it?
To fix this (if you don't want to change your HTML structure) you can use events stopPropagation method inside the cancelEdit:
cancelEdit(e) {
e.stopPropagation()
this.projectStore.$patch({ activeColor: false })
}
Event.stopPropagation() reference

Vue/Vuex dynamic component not changing

I have a vuex store of "nodes". Each one has a type of Accordion or Block.
{
"1":{
"id":1,
"title":"Default title",
"nodes":[],
"type":"Block"
},
"2":{
"id":2,
"title":"Default title",
"nodes":[],
"type":"Accordion"
}
}
When I use the type to create a dynamic component it works great:
<ul>
<li v-for="(node, s) in nodes" :key="parentId + s">
<component :is="node.type" :node="node" :parent-id="parentId"></component>
</li>
</ul>
But when I change it, nothing happens in the view layer:
convert(state, { to, id }) {
state.nodes[id].type = to;
Vue.set(state.nodes[id], "type", to);
},
I even use Vue.set. How can I make this update?
It updates immediately if I then push another node into the array.
CodeSandbox:
https://codesandbox.io/s/romantic-darwin-dodr2?file=/src/App.vue
The thing is that your getter will not work, because it's not pure: Issue. But you can use deep watcher on your state instead:
<template>
<div class="home">
<h1>Home</h1>
<Sections :sections="nodesArr" :parent-id="null"/>
</div>
</template>
<script>
// # is an alias to /src
import Sections from "#/components/Sections.vue";
import { mapState } from "vuex";
export default {
name: "home",
components: {
Sections
},
data: () => {
return {
nodesArr: []
};
},
computed: {
...mapState(["nodes", "root"])
},
watch: {
root: {
handler() {
this.updateArr();
},
deep: true
}
},
mounted() {
this.updateArr();
},
methods: {
updateArr() {
this.nodesArr = this.root.map(ref => this.nodes[ref]);
}
}
};
</script>

How to make this vue component reusable with it's props?

I am trying to make this component reusable so later can install it in any project and via props add needed values e.g. images and function parameters (next, prev, intervals...) inside any component.
<template>
<div>
<transition-group name='fade' tag='div'>
<div v-for="i in [currentIndex]" :key='i'>
<img :src="currentImg" />
</div>
</transition-group>
<a class="prev" #click="prev" href='#'>❮</a>
<a class="next" #click="next" href='#'>❯</a>
</div>
</template>
<script>
export default {
name: 'Slider',
data() {
return {
images: [
'https://cdn.pixabay.com/photo/2015/12/12/15/24/amsterdam-1089646_1280.jpg',
'https://cdn.pixabay.com/photo/2016/02/17/23/03/usa-1206240_1280.jpg',
'https://cdn.pixabay.com/photo/2015/05/15/14/27/eiffel-tower-768501_1280.jpg',
'https://cdn.pixabay.com/photo/2016/12/04/19/30/berlin-cathedral-1882397_1280.jpg'
],
timer: null,
currentIndex: 0,
}
},
mounted: function() {
this.startSlide();
},
methods: {
startSlide: function() {
this.timer = setInterval(this.next, 4000);
},
next: function() {
this.currentIndex += 1
},
prev: function() {
this.currentIndex -= 1
}
},
computed: {
currentImg: function() {
return this.images[Math.abs(this.currentIndex) % this.images.length];
}
}
}
</script>
styles...
So later it would be <Slider... all props, images loop here/> inside other components.
How can be it be achieved?
Just move what needs to come from another component to props. That way other component can pass the relevant info it needs.
export default {
name: 'Slider',
props: {
images: Array,
next: Function
prev: Function,
// and so on
},
...
The parent component would call it like:
<Slider :images="imageArray" :next="nextFunc" :prev="prevFunc" />
EDIT
You can pass an interval value via props:
export default {
name: 'Slider',
props: { intervalVal: Number },
methods: {
startSlide: function() {
this.timer = setInterval(this.next, this.intervalVal);
},
}
You can also pass function from parent to child via props.
export default {
name: 'Slider',
props: { next: Function },
methods: {
someMethod: function() {
this.next() // function from the parent
},
}
I don't really understand your use case 100% but these are possible options.

Is it possible to dynamically add chart type in the extends: property, based on props from parent component?

I have a vue chartjs component which imports the whole vue-chartjs library. My idea is, is it possible to somehow pass the type of the chart which I want and add it to the 'extends: VueCharts.charttype?.' In the example I provide it extends the VueCharts.Line, I need this property to be dynamically interpolated, passed from props. Is it possible this charttype to come from a parent props dynamically and how?
<script>
import { VueCharts } from "vue-chartjs";
export default {
extends: VueCharts.Line,
props: ["chartdata", "options"],
mounted() {
this.renderChart(this.chartdata, this.options);
}
}
</script>
<style scoped>
</style>
since extends the same as mixins, you need to pass a dynamic mixin, in order to do that you need two components, imagine we have component ChartWrapper :
<template>
<div>
<div>{{ chartType }}</div>
<chart :chart-data="datacollection"/>
</div>
</template>
<script>
import Chart from "./Chart";
import { VueCharts, mixins } from "vue-chartjs";
const { reactiveProp } = mixins;
export default {
name: "ChartWrapper",
components: {
Chart
},
props: {
chartType: {
type: String,
required: true
}
},
data() {
return {
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()]
}
]
}
};
},
methods: {
getRandomInt() {
return Math.floor(Math.random() * (50 - 5 + 1)) + 5;
}
},
created() {
if (this.chartType) {
Chart.mixins = [reactiveProp,VueCharts[this.chartType]];
}
}
};
</script>
this component takes chartType as a prop, and I import all charts as VueCharts in top of the script ==> 1
second component:
<script>
export default {
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);
}
};
</script>
the second component just has options props, and renderChart function invoked.
==> 2
What is happening?
the ChartWrapper component receives the chart type by chartType prop, in the created hook, if chartType exist, assign the chart(resolved by VueCharts[this.chartType]) to Chart component as a mixin in addition to reactiveProp,
I also pass the chart data to Chart component.
in the end, call the ChartWrapper component:
<ChartWrapper chartType="Bar"/>
Live example on code sandbox: https://codesandbox.io/s/vue-template-w9r8k
You can also choose for the option to just extend the Line chart and update the config of the chart with the chart type you want and give it an update so it changes type.
<script>
import { Line, mixins } from 'vue-chartjs';
const { reactiveProp } = mixins;
export default {
extends: Line,
name: "LineChart",
mixins: [reactiveProp],
props: {
options: { type: Object },
chartType: { type: String }
},
mounted () {
this.renderChart(this.chartData, this.options);
},
watch: {
options: {
deep: true,
handler () {
this.$data._chart.options = this.options;
this.updateChart();
}
},
chartType (newVal) {
this.$data._chart.config.type = newVal;
this.updateChart()
}
},
methods: {
updateChart () {
this.$data._chart.update();
},
}
}
</script>

Make Chartkick wait for data to populate from Firebase before rendering chart

I'm using chartkick in my Vue project. Right now, the data is loading from Firebase after the chart has rendered, so the chart is blank. When I change the code in my editor, the chart renders as expected, since it's already been retrieved from Firebase. Is there a way to make chartkick wait for the data to load before trying to render the chart? Thanks!
Line-Chart Component:
<template>
<div v-if="loaded">
<line-chart :data="chartData"></line-chart>
</div>
</template>
<script>
export default {
name: 'VueChartKick',
props: ['avgStats'],
data () {
return {
loaded: false,
chartData: this.avgStats
}
},
mounted () {
this.loaded = true
}
}
</script>
Parent:
<template>
...
<stats-chart v-if="avgStatsLoaded" v-bind:avgStats="avgStats" class="stat-chart"></stats-chart>
<div v-if="!avgStatsLoaded">Loading...</div>
...
</template>
<script>
import StatsChart from './StatsChart'
export default {
name: 'BBall',
props: ['stats'],
components: {
statsChart: StatsChart
},
data () {
return {
avgStatsLoaded: false,
avgStats: []
}
},
computed: {
sortedStats: function () {
return this.stats.slice().sort((a, b) => new Date(b.date) - new Date(a.date))
}
},
methods: {
getAvgStats: function () {
this.avgStats = this.stats.map(stat => [stat.date, stat.of10])
this.avgStatsLoaded = true
}
},
mounted () {
this.getAvgStats()
}
}
modify your code of StatsChart component:
you may use props directly
<template>
<div v-if="loaded">
<line-chart :data="avgStats"></line-chart>
</div>
</template>
export default {
name: 'VueChartKick',
props: ['avgStats'],
data () {
return {
loaded: false,
}
},
mounted () {
this.loaded = true
}
}