I had huge problems with this
First I tried .clear()
then I tried .destroy()
and I tried setting my chart reference to null
What finally fixed the issue for me: deleting the <canvas>
element and then reappending a new <canvas>
to the parent container
My specific code (obviously there's a million ways to do this):
var resetCanvas = function(){
$('#results-graph').remove(); // this is my <canvas> element
$('#graph-container').append('<canvas id="results-graph"><canvas>');
canvas = document.querySelector('#results-graph');
ctx = canvas.getContext('2d');
ctx.canvas.width = $('#graph').width(); // resize to parent width
ctx.canvas.height = $('#graph').height(); // resize to parent height
var x = canvas.width/2;
var y = canvas.height/2;
ctx.font = '10pt Verdana';
ctx.textAlign = 'center';
ctx.fillText('This text is centered on the canvas', x, y);
};
With the approach you are taking (e.g. creating a new Chart object each time the date range changes), then you must first destroy the previous chart and then create the new one.
You can use the .destroy()
prototype method to do this. Here is exactly what the API states.
Use this to destroy any chart instances that are created. This will
clean up any references stored to the chart object within Chart.js,
along with any associated event listeners attached by Chart.js. This
must be called before the canvas is reused for a new chart.
Therefore, your code would look something like this (notice that we destroy and re-create).
// define a variable to store the chart instance (this must be outside of your function)
var myChart;
function loadFTPRChart(startdate, enddate) {
var BCData = {
labels: [],
datasets: [{
label: "Pass %",
backgroundColor: "#536A7F",
data: [],
stack: 1
}, {
label: "Fail %",
backgroundColor: "#e6e6e6",
data: [],
stack: 1
}, {
label: "Auto %",
backgroundColor: "#286090",
data: [],
stack: 2
}, {
label: "Manual %",
backgroundColor: "#f0f0f0",
data: [],
stack: 2
}]
};
$.getJSON("content/FTPR_AM_Graph_ajax.php", {
startdate: startdate,
enddate: enddate,
location: "M"
})
.done(function(data) {
console.log("data", data);
$.each(data.aaData, function(key, val) {
if (val == "") {
return true
}
BCData.labels.push("Coater " + val[0]);
BCData.datasets[0].data.push(parseFloat(val[2]));
BCData.datasets[1].data.push(parseFloat(100 - val[2]));
BCData.datasets[2].data.push(parseFloat(val[1]));
BCData.datasets[3].data.push(parseFloat(100 - val[1]));
});
var option = {
responsive: true,
};
console.log("BCData", BCData);
// if the chart is not undefined (e.g. it has been created)
// then destory the old one so we can create a new one later
if (myChart) {
myChart.destroy();
}
var ctx = document.getElementById("mybarChart2").getContext("2d");
myChart = new Chart(ctx, {
type: 'groupableBar',
data: BCData,
options: {
scales: {
yAxes: [{
ticks: {
max: 100,
},
stacked: true,
}]
}
}
});
});
}
With that said, it's expensive the destroy/create over and over and actually it isn't even necessary. There is another prototype method called .update()
that you can use to just re-render the chart if you have changed it's underlying data or label objects.
Here is a jsfiddle showing an example of changing the underlying data and labels and then re-rendering the chart. I would highly recommend you take this approach instead.
Here is how your code would look taking this better approach.
// define a variable to store the chart instance (this must be outside of your function)
var myChart;
function loadFTPRChart(startdate, enddate) {
var BCData = {
labels: [],
datasets: [{
label: "Pass %",
backgroundColor: "#536A7F",
data: [],
stack: 1
}, {
label: "Fail %",
backgroundColor: "#e6e6e6",
data: [],
stack: 1
}, {
label: "Auto %",
backgroundColor: "#286090",
data: [],
stack: 2
}, {
label: "Manual %",
backgroundColor: "#f0f0f0",
data: [],
stack: 2
}]
};
$.getJSON("content/FTPR_AM_Graph_ajax.php", {
startdate: startdate,
enddate: enddate,
location: "M"
})
.done(function(data) {
console.log("data", data);
$.each(data.aaData, function(key, val) {
if (val == "") {
return true
}
BCData.labels.push("Coater " + val[0]);
BCData.datasets[0].data.push(parseFloat(val[2]));
BCData.datasets[1].data.push(parseFloat(100 - val[2]));
BCData.datasets[2].data.push(parseFloat(val[1]));
BCData.datasets[3].data.push(parseFloat(100 - val[1]));
});
var option = {
responsive: true,
};
console.log("BCData", BCData);
// if the chart is not undefined (e.g. it has been created)
// then just update the underlying labels and data for each
// dataset and re-render the chart
if (myChart) {
myChart.data.labels = BCData.labels;
myChart.data.datasets[0].data = BCData.datasets[0].data;
myChart.data.datasets[1].data = BCData.datasets[1].data;
myChart.data.datasets[2].data = BCData.datasets[2].data;
myChart.data.datasets[3].data = BCData.datasets[3].data;
myChart.update();
} else {
// otherwise, this is the first time we are loading so create the chart
var ctx = document.getElementById("mybarChart2").getContext("2d");
myChart = new Chart(ctx, {
type: 'groupableBar',
data: BCData,
options: {
scales: {
yAxes: [{
ticks: {
max: 100,
},
stacked: true,
}]
}
}
});
}
});
}
Best Answer
Here is an update to your fiddle. The primary change (other than fixing the function name typo) is to add
before lines like
The
.destroy()
method gets rid of the event handler registrations etc, so you shouldn't see those weird "ghost charts" when you mouse over the graphics.