3π
I have already posted an answer here, which points to some Vue.js
usage issues. But I think the real problem is different, as explained below:
You reload
function does the following:
this.chart.destroy();
this.load();
this.setChartClickHandler(this.ctx, this.chart);
If you notice, the second line is this.load()
which invokes fetchData
asynchronously. When the response
comes through, you render the chart using this.render(response.data)
Letβs say your AJAX data takes 400 milli-seconds to come. So your chart will be rendered only after 400 milli-seconds. But your this.chart
is already destroyed and will be recreated only after 400 milli-seconds inside your render
function, using new Chart(...)
.
But your reload
function immediately proceeds to call this.setChartClickHandler
with reference to this.chart
which is already destroyed.
To fix this, you need to call this.setChartClickHandler(..)
only after the new Chart
is created inside the render
function.
EDIT: additional thoughts on the updated question
You have another problem now: Your function is creating a new scope inside, and you have problem with binding of this
You currently have the following:
this.$nextTick(function() {
this.setChartClickHandler(this.ctx, this.chart);
});
Instead, you need to change it to:
this.$nextTick(() => {
this.setChartClickHandler(this.ctx, this.chart);
});
The arrow function ensures that you do not create a new scope inside, and your this
inside the function is same as this
of Vue component.
There is another function where you need to make the same change:
ctx.on('click', function(evt) {
var activePoints = chart.getElementsAtEvent(evt);
var label = chart.data.labels[activePoints[0]._index];
var value = chart.data.datasets[activePoints[0]._datasetIndex].data[activePoints[0]._index];
console.log(label,value);
});
The above lines need to be:
ctx.on('click', evt => {
var activePoints = chart.getElementsAtEvent(evt);
var label = chart.data.labels[activePoints[0]._index];
var value = chart.data.datasets[activePoints[0]._datasetIndex].data[activePoints[0]._index];
console.log(label,value);
});
0π
You are trying to use {{...}}
(mustaches) for binding your classes and IDs. VueJS does not allow you to do that. Instead you need to use v-bind as explained below:
The first line in your template is currently:
<div class="{{ chartDivSize }}">
You need to change it to:
<div v-bind:class="[chartDivSize]">
Ref: https://vuejs.org/guide/class-and-style.html#Object-Syntax
Most importantly, your canvas ID is probably not getting binded properly in your component. Currently you have:
<canvas id="{{ chartId }}" style="height: 352px; width: 704px;" width="1408" height="704"></canvas>
It needs to be:
<canvas v-bind:id="chartId" style="height: 352px; width: 704px;" width="1408" height="704"></canvas>
Ref: https://vuejs.org/guide/syntax.html#Attributes
Instead of v-bind:class
and v-bind:id
, you may also use shorthand format like :class
or :id
, as specified in the API docs for v-bind.
You may still have issues after the above. VueJS renders the DOM, but your canvas may not be available instantaneously. You need to wait for a slight delay before the canvas with that ID becomes available in DOM. For that, you may use Vue.nextTick()
, or simply this.$nextTick()
before calling your chart.js APIs.
Reference for Vue.nextTick()
: http://vuejs.org/api/#Vue-nextTick
Edit: Additional Reference
The above code should technically work β it should give you a canvas element with the ID specified. And in the nextTick()
, you should be able to get that <canvas>
and draw stuff into it.
If it does not work for any reason, you may need to capture the <canvas>
element using directives. Here is a recent answer in Stackoverflow (that I posted few days ago), which has a jsFiddle example: https://stackoverflow.com/a/40178466/654825
In the jsFiddle example for that answer, I have captured the <canvas>
element without using the id
parameter. Reason: A component is meant to be reused in multiple places, and therefore having a fixed id
may defeat that purpose. As seen in that example, you may attempt to capture the <canvas>
element directly using directives, which makes your component fully reusable.
Direct reference to that jsFiddle example: https://jsfiddle.net/mani04/r4mbh6nu/