How should I refer to the chart in a vue chart instance and destroy it? I've tried:
var chartVue = new Vue({
el: '#chart',
extends: VueChartJs.Line,
data: {
chartData: {...},
options: {...}
},
mounted() {
this.renderMyChart()
},
methods: {
renderMyChart: function() {
this.renderChart(
this.chartData,
this.options
);
}
},
watch: {
chartData: function() {
this._chart.destroy();
this.renderMyChart();
}
}
});
But it complains
TypeError: Cannot read property 'destroy' of undefined
So it seems this._chart is not the right way to refer to the current chart, anybody knows what is the right way to do this? The idea comes from this stack overflow answer.
It seems this._chart has been moved to this._data._chart, so for instance to make the options reactive, add a watcher as follows:
watch: {
options: function() {
this._data._chart.destroy();
this.renderChart(this.chartData, this.options);
}
}
The Chart object accessible via this._chart is not set until after the renderChart method is called. What's most likely happening is that your chartData is somehow being updated before that renderChart method has been called. So, the watcher for chartData is referencing this._chart before it has been defined.
However, vue-chartjs has a baked-in way to re-render the chart when the dependant data change: the reactiveData and reactiveProp mixins. By adding either of these mixins, the changes to the chartData (data-property or prop, respectively) will re-render the chart to reflect the updated data.
You should use the reactiveData mixin instead:
var chartVue = new Vue({
el: '#chart',
extends: VueChartJs.Line,
mixins: [ VueChartJS.mixins.reactiveData ],
data() {
return {
chartData: {...},
options: {...}
}
},
mounted() {
this.renderChart(this.chartData, this.options);
},
});
Related
I am using Chart.js 3.5 and Vue 3.
I was successfully able to draw a chart, and I am trying to trigger a data change, inside a Vue method. Unfortunately, I encounter the following issue: "Uncaught TypeError: Cannot set property 'fullSize' of undefined".
Edit2: Added a missed }. Code should now be runnable
MyChart.vue:
<template>
<canvas id="chartEl" width="400" height="400" ref="chartEl"></canvas>
<button #click="addData">Update Chart</button>
</template>
<script>
import Chart from 'chart.js/auto';
export default {
name: "Raw",
data() {
return {
chart: null
}
},
methods: {
createChart() {
this.chart= new Chart(this.$refs["chartEl"], {
type: 'doughnut',
data: {
labels: ['VueJs', 'EmberJs', 'ReactJs', 'AngularJs'],
datasets: [
{
backgroundColor: [
'#41B883',
'#E46651',
'#00D8FF',
'#DD1B16'
],
data: [100, 20, 80, 20]
}
]
},
options: {
plugins: {}
}
})
},
addData() {
const data = this.chart.data;
if (data.datasets.length > 0) {
data.labels.push('data #' + (data.labels.length + 1));
for (var index = 0; index < data.datasets.length; ++index) {
data.datasets[index].data.push(123);
}
// Edit2: added missed }
this.chart.update(); } // this line seems to cause the error}
}
},
mounted () {
this.createChart()
},
}
</script>
Edit1: Adding the following to the options makes the chart update successfully, but the error is still present and the animation does not work. The chart flickers and displays the final (updated) state. Other animations, such as hiding/showing arcs do not seem to be afected
options: {
responsive: true,
}
Edit3: Adding "maintainAspectRatio:false" option seems to again stop chart from updating (the above mentioned error is still present)
By walking through the debugger, the following function from 'chart.esm.js' seems to be called successfully a few times, and then error out on last call:
beforeUpdate(chart, _args, options) {
const title = map.get(chart); // this returns null, which will cause the next call to error with the above mentioned exception.
layouts.configure(chart, title, options);
title.options = options;
},
//////////////////////
configure(chart, item, options) {
item.fullSize = options.fullSize;
item.position = options.position;
item.weight = options.weight;
},
This may be a stale post but I just spent several hours wrestling with what seems like the same problem. Perhaps this will help you and/or future people with this issue:
Before assigning the Chart object as an attribute of your Vue component, call Object.seal(...) on it.
Eg:
const chartObj = new Chart(...);
Object.seal(chartObj);
this.chart = chartObj;
This is what worked for me. Vue aggressively mutates attributes of objects under its purview to add reactivity, and as near as I can tell, this prevents the internals of Chart from recognising those objects to retrieve their configurations from its internal mapping when needed. Object.seal prevents this by barring the object from having any new attributes added to it. I'm counting on Chart having added all the attributes it needs at init time - if I notice any weird behaviour from this I'll update this post.
1 year later, Alan's answer helps me too, but my code failed when calling chart.destroy().
So I searched and found what seems to be the "vue way" of handling it: markRaw, here is an example using options API:
import { markRaw } from 'vue'
// ...
export default {
// ...
beforeUnmount () {
if (this.chart) {
this.chart.destroy()
}
},
methods: {
createChart() {
const chart = new Chart(this.$refs["chartEl"], {
// ... your chart data and options
})
this.chart = markRaw(chart)
},
addData() {
// ... your update
this.chart.update()
},
},
}
I use Ractive to display data from a constantly changing array (received from PouchDB) with the magic: trueparameter. That worked fine until in Version 0.9.x this parameter was no longer available.
So it is advised to use ractive.update()every time my array changes. The problem I'm facing seems quite simple, but I'm fairly inexperienced:
For tracking the changes I do roughly the following (taken from a PouchDB script):
fetchInitialDocs().then(renderDocsSomehow).then(reactToChanges).catch(console.log.bind(console));
function renderDocsSomehow() {
let myRactive = new Ractive({
el: '#myDiv',
template: 'my Template',
//magic: true,
data:{
doc1: docs
},
components: {myComponents},
computed: {computedValues}
});
}
function reactToChanges() {
//here my changes appear
onUpdatedOrInserted(change.doc);
myRactive.update() // <- I cannot use update here because "myRactive" is not known at this point.
}
What I tried also was setting an observer in the renderDocsSomehow() function
ractive.observe('doc1', function(newValue, oldValue, keypath) {
ractive.update()
});
but this did not do the trick.
So how can I inform Ractive that my data has changed and it needs to update itself?
If reactToChanges is just a function that sets up "listeners" for myRactive and is never called elsewhere, you can probably put the code for it in an oninit. This way, you have access to the instance.
function renderDocsSomehow() {
let myRactive = new Ractive({
el: '#myDiv',
template: 'my Template',
//magic: true,
data:{
doc1: docs
},
components: {myComponents},
computed: {computedValues},
oninit(){
//here my changes appear
onUpdatedOrInserted(change.doc);
this.update()
}
});
}
I found some sort of answer and thought I'd share it, even if it is not working 100% as desired.
If I return ractive from my first function It's usable in the second one:
fetchInitialDocs().then(renderDocsSomehow).then(reactToChanges).catch(console.log.bind(console));
function renderDocsSomehow() {
let myRactive = new Ractive({
el: '#myDiv',
template: 'my Template',
data:{
doc1: docs
},
components: {myComponents},
computed: {computedValues}
});
return myRactive;
}
function reactToChanges() {
//here my changes appear
onUpdatedOrInserted(change.doc);
renderDocsSomehow().update()
}
Sadly update()changes the whole ractive part of my page to its default state (every accordion that was folded out before is reverted and every chart.js is redrawn), which of course is not desirable in a highly dynamic application. Therefore I will nor mark it as answer by now.
I have a Vue instance where data property is initialised as an object:
var app = new Vue({
el: '#app',
data: {
obj: { }
},
methods: {
},
created: function() {
this.obj["obj2"] = {}
this.obj["obj2"].count = 0
},
mounted: function() {
setInterval(function() {
this.obj.obj2.count++
console.log(this.obj.obj2.count)
}.bind(this), 1000)
}
})
<div id="app">
{{ obj['obj2'].count }}
</div>
And then when the instance is created I add a property to the obj.
However, when I want to display the object's object property count, it shows 0 and is not reactive. If I defined the whole object in the data, it is reactive but I can't define the object in the data because its data depends on an external source - API, that's why it is filled with data in created function.
The only way how I managed to make it show the current count is by forcing updates on the view but I don't think it's the correct solution.
Any suggestions?
The problem is that Vue can not track completely new properties on its reactive objects. (It's a limitation of JavaScript).
It's described in detail here: https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
The short version is: You have to do
created: function() {
Vue.set(this.obj, 'obj2', {})
Vue.set(this.obj.obj2, 'count', 0)
}
or
created: function() {
Vue.set(this.obj, 'obj2', {
count: 0
})
}
I have a few different checklists for my "projects" and using the same Vue instance to handle these checklists that I am getting from my database. I am running into a problem though in which I really want to use the project's id and a type of checklist in my mounted() method to help my Controller endpoint (I'm using laravel but that is irrelevant here) point to the right database rows.
So for example:
HTML
<ul class="vue-checklist" data-project="16" data-type="permits">
</ul>
<ul class="vue-checklist" data-project="16" data-type="walkthrough">
</ul>
JS
new Vue({
el: '.vue-checklist',
data: {
items: [],
// is there a way to trap those data attrs here?
},
mounted : function(){
// I need to a way to access the project and type data attrs.
this.fetchChecklist(this.project, this.type); // <- does not work
},
methods: {
fetchChecklist : function(project, type){
this.$http.get('{ api/path }', { project: project, type: type}).then(function(response){
this.items = response.data;
})
}
});
Again, is there a way to get data-project and data-type attached in the HTML use that in the mounted() method.
You can reference the root element of the Vue instance via this.$el.
From there you can reference the element's attribute's via the getAttribute() method.
In your case, you could do something like this:
new Vue({
el: '.vue-checklist',
data: {
items: [],
project: null,
type: null,
},
mounted : function(){
this.project = this.$el.getAttribute('data-project');
this.type = this.$el.getAttribute('data-type');
this.fetchChecklist(this.project, this.type);
},
...
}
That isn't the most straight-forward solution though. If you're able, it'd be a lot cleaner to create a Vue instance on a parent element and then define vue-checklist as a component. That way you could just pass the project and type values as props to the component from the template:
Vue.component('vue-checklist', {
template: `<ul class="vue-checklist"></ul>`,
props: ['project', 'type'],
data: {
items: [],
},
mounted : function(){
this.fetchChecklist(this.project, this.type);
},
methods: {
fetchChecklist : function(project, type){
this.$http.get('{ api/path }', { project: project, type: type}).then(function(response){
this.items = response.data;
})
}
}
})
new Vue({
el: '#app',
})
<div id="app">
<vue-checklist project="16" type="permits"></vue-checklist>
<vue-checklist project="16" type="walkthrough"></vue-checklist>
</div>
According to the docs, the constructor of the Vue object is managed like this.
var vm = new Vue({
created: function () { console.log("I'm created!"); }
});
However, I can't figure out how to do the corresponding thing when a Vue component is created. I've tried the following but don't get any print to the console.
export default {
created: function() { console.log("Component created!"); }
}
Is it possible to subscribe/listen to a component being rendered? I'd like to react to that event by downloading some data and putting it in the store, so that the table that the component carries will get its information to display.
In my applications, I tend to use the mounted hook to load up some Ajax data once the component has mounted.
Example code from my app:
Vue.component('book-class', {
template: '#booking-template',
props: ['teacherid'],
data: function () {
return{
// few data items returned here..
message: ''
}
},
methods: {
// Few methods here..
},
computed: {
// few computed methods here...
},
mounted: function () {
var that = this;
$.ajax({
type: 'GET',
url: '/classinfo/' + this.teacherid,
success: function (data) {
console.log(JSON.stringify(data));
}
})
}
});
new Vue({
el: '#mainapp',
data: {
message: 'some message here..'
}
});
However, I can also use created() hook as well as it is in the lifecycle as well.
In Vue2 you have the following lifecycle hooks:
components doesn't have life cycle hooks like app. but they has something similar. that fixed my problem:
https://v2.vuejs.org/v2/api/#updated