[Chartjs]-Vue.js component for Chart.js – strange issue on AJAX reload

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/

Leave a comment