Drawing images on top of graph doesn't work with ChartJS - javascript

I'm trying to label a pie chart with icons that I'd draw on with the drawImage() function, but it's not working. Here's my code:
var config = {
type: 'pie',
data: {
datasets: [{
data: [
"1",
"2"
]
}],
labels: [
"1",
"2"
]
}
};
var ctx = document.getElementById('Chart1').getContext('2d');
var myChart = new Chart(ctx, config);
var image = new Image();
image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAA1BMVEX/AAAZ4gk3AAAASElEQVR4nO3BgQAAAADDoPlTX+AIVQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwDcaiAAFXD1ujAAAAAElFTkSuQmCC';
image.onload = () => {
ctx.drawImage(image, 0, 0, 100, 100)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="Chart1"></canvas>
I want it to draw the image on top of the chart, but it's not working, any ideas?

The issue is that chartJs constantly updates the canvas by clearing it and re-rendering the whole thing, like on hovering and mouse movement to add the labels and do some animations, so anything you add to the canvas will be lost.
The workaround in the github discussion regarding the same issue is the way to go. You just need to create a new chart type extending the existing chart type you want to use where you can override the draw method of the new chart type to do the extra stuff you want, the syntax for extending the charts has changed since the github discussion, so we'll use the new one:
Chart.defaults.pieWithExtraStuff = Chart.defaults.pie; // the name of the new chart type is "pieWithExtraStuff"
Chart.controllers.pieWithExtraStuff = Chart.controllers.pie.extend({ // creating the controller for our "pieWithExtraStuff" chart by extending from the default pie chart controller
draw: function(ease) { // override the draw method to add the extra stuff
Chart.controllers.pie.prototype.draw.call(this, ease); // call the parent draw method (inheritance in javascript, whatcha gonna do?)
var ctx = this.chart.ctx; // get the context
if (this.labelIconImage) { // if the image is loaded
ctx.drawImage(this.labelIconImage, 0, 0, 100, 100); // draw it
}
},
initialize: function(chart, datasetIndex) { // override initialize too to preload the image, the image doesn't need to be outside as it is only used by this chart
Chart.controllers.pie.prototype.initialize.call(this, chart, datasetIndex);
var image = new Image();
image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAA1BMVEX/AAAZ4gk3AAAASElEQVR4nO3BgQAAAADDoPlTX+AIVQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwDcaiAAFXD1ujAAAAAElFTkSuQmCC';
image.onload = () => { // when the image loads
this.labelIconImage = image; // save it as a property so it can be accessed from the draw method
chart.render(); // and force re-render to include it
};
}
});
var config = {
type: "pieWithExtraStuff", // use our own chart type, otherwise what's the point?
data: { /* ... */ }
};
var ctx = document.getElementById('Chart1').getContext('2d');
var myChart = new Chart(ctx, config);
Now whenever the pieWithExtraStuff chart re-renders, it will also draw the image.
Demo:
Chart.defaults.pieWithExtraStuff = Chart.defaults.pie;
Chart.controllers.pieWithExtraStuff = Chart.controllers.pie.extend({
draw: function(ease) {
Chart.controllers.pie.prototype.draw.call(this, ease);
var ctx = this.chart.ctx;
if (this.labelIconImage) {
ctx.drawImage(this.labelIconImage, 0, 0, 100, 100);
}
},
initialize: function(chart, datasetIndex) {
Chart.controllers.pie.prototype.initialize.call(this, chart, datasetIndex);
var image = new Image();
image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAA1BMVEX/AAAZ4gk3AAAASElEQVR4nO3BgQAAAADDoPlTX+AIVQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwDcaiAAFXD1ujAAAAAElFTkSuQmCC';
image.onload = () => {
this.labelIconImage = image;
chart.render();
};
}
});
var config = {
type: "pieWithExtraStuff",
data: {
datasets: [{
data: [
"1",
"2"
]
}],
labels: [
"1",
"2"
]
}
};
var ctx = document.getElementById('Chart1').getContext('2d');
var myChart = new Chart(ctx, config);
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="Chart1"></canvas>

Related

Exporting dynamic chartjs to jspdf

I'm having a problem with the code below:
var reportPageHeight = 16500;
var reportPageWidth = 12400;
// create a new canvas object that we will populate with all other canvas objects
var pdfCanvas = $('<canvas />').attr({
id: "pcanvaspdf",
width: reportPageWidth,
height: reportPageHeight
});
// keep track canvas position
var pdfctx = $(pdfCanvas)[0].getContext('2d');
var pdfctxX = 0;
var pdfctxY = 0;
var buffer = 100;
// for each chart.js chart
$("canvas").each(function (index) {
// get the chart height/width
var canvasHeight = 4000
var canvasWidth = 6500;
// draw the chart into the new canvas
pdfctx.drawImage($(this)[0], pdfctxX, pdfctxY, canvasWidth, canvasHeight);
pdfctxX += canvasWidth + buffer;
// our report page is in a grid pattern so replicate that in the new canvas
if (index % 2 === 1) {
pdfctxX = 0;
pdfctxY += canvasHeight + buffer;
}
});
// download the pdf
pdf.save('ChartPDF.pdf');
The problem is that I lose the transparency of the chart and instead I get a black background when the chart is exported to PDF. Is there any way to change it to white when exporting to PDF?
Thanks in advance.
Set the canvas backgrounds to white:
canvas {
background-color: #fff;
}
If that doesn't work, draw the canvas background in your chart's plugins object:
plugins: [
{
id: 'background',
beforeDraw: function(chartInstance) {
var chart = chartInstance.chart;
var ctx = chart.ctx;
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, chart.width, chart.height);
}
}
],

Pass context to options on React ChartJS 2

I'm using React ChartJS 2 to create some graphs but I want to show a label in top of them with the percentage and when I hover over them the real number. I found you can do something like this using the context on the options object.
var options = {
tooltips: {
enabled: false
},
plugins: {
datalabels: {
formatter: (value, ctx) => {
let datasets = ctx.chart.data.datasets;
if (datasets.indexOf(ctx.dataset) === datasets.length - 1) {
let sum = datasets[0].data.reduce((a, b) => a + b, 0);
let percentage = Math.round((value / sum) * 100) + '%';
return percentage;
} else {
return percentage;
}
},
color: '#fff',
}
}
};
var ctx = document.getElementById("pie-chart").getContext('2d');
var myChart = new Chart(ctx, {
type: 'pie',
data: {
datasets: data
},
options: options
});
Like this, but my problem is that I cannot get a way to use the context inside the options.
Does someone knows how to do this?
You don't have to manually pass the context to the datalabels' formatter function since the plugin takes care of this itself.
Here's a working example of the pie graph with the options specified above.
But if you want to access the chart's context in some other functions you want to pass to the options, then you can get it through the chart instance by using this.chart.ctx.
var options = {
animation: {
onComplete: function () {
var chartInstance = this.chart;
var ctx = chartInstance.ctx; // chart context
}
}
};

Adding a label to a doughnut chart in Chart.js shows all values in each chart

I'm using Chart.js to draw a series of charts on my site and I've written a helper method to draw different charts easily:
drawChart(ctxElement, ctxType, ctxDataLabels, ctxDataSets, midLabel) {
var ctx = ctxElement;
var data = {
labels: ctxDataLabels,
datasets: ctxDataSets
};
Chart.pluginService.register({
beforeDraw: function(chart) {
var width = chart.chart.width,
height = chart.chart.height,
ctx = chart.chart.ctx;
ctx.restore();
var fontSize = (height / 114).toFixed(2);
ctx.font = fontSize + "em sans-serif";
ctx.textBaseline = "middle";
var text = midLabel,
textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = height / 2;
ctx.fillText(text, textX, textY);
ctx.save();
}
});
var chart = new Chart(ctx, {
type: ctxType,
data: data,
options: {
legend: {
display: false
},
responsive: true
}
});
}
The last parameter for the drawChart() method contains the label that should be added in the middle of the chart. The Chart.pluginService.register part is the code that draws the label. The problem is that when I execute the drawChart method multiple times (in my case three times) and supply the label of each chart in the method executions, all three labels are shown on top of each other on each chart. I need to display each label in the corresponding chart. All other parameters are handled correctly, except for the label.
How do I achieve that?
A simple workaround is to add another parameter to your function to differentiate your charts from each other.
I chose to use the id of a chart for this, so that you are sure you won't affect another one.
You first need to edit a little bit your function :
// !!
// Don't forget to change the prototype
// !!
function drawChart(ctxElement, ctxType, ctxDataLabels, ctxDataSets, midLabel, id) {
var ctx = ctxElement;
var data = {
labels: ctxDataLabels,
datasets: ctxDataSets
};
Chart.pluginService.register({
afterDraw: function(chart) {
// Makes sure you work on the wanted chart
if (chart.id != id) return;
// From here, it is the same as what you had
var width = chart.chart.width,
height = chart.chart.height,
ctx = chart.chart.ctx;
// ...
}
});
// ...
}
From now, when you call your function, don't forget about the id :
// ids need to be 0, 1, 2, 3 ...
drawChart(ctxElement, ctxType, ctxDataLabels, ctxDataSets, "Canvas 1", 0);
drawChart(ctxElement, ctxType, ctxDataLabels, ctxDataSets, "Canvas 2", 1);
drawChart(ctxElement, ctxType, ctxDataLabels, ctxDataSets, "Canvas 3", 2);
You can see a fully working example on this fiddle (with 3 charts), and here is a preview :

Javascript ChartJS updating chart and canvas permanently

I made a chart in chartJS and I would like to update it using new data based on what the user chooses from a drop down list, using the same canvas. The problem is when i do the update function, the chart updates with the new data but it keeps coming back to the original chart after a while. How can i solve this? Here's the code, thank you for any help:
/* Original Chart */
var ctx3 = document.getElementById("canvas3").getContext("2d");
var canvas3 = new Chart(ctx3, {
type: 'line',
data: {
labels: stationRentalsLabels,
datasets: [{
label: 'Wypożyczenia',
fillColor: "rgba(220,280,220,0.5)",
strokeColor: "rgba(220,220,220,1)",
backgroundColor: "rgba(153,255,51,0.4)",
data: stationRentalsData
}]
}
});
/* event listener on drop-down list, when triggered, update chart */
select.addEventListener('change', function() {
updateChart()
});
/* Update Chart */
function updateChart() {
var stationRentalsHoursTemp = [];
var stationRentalName = [];
var determineHour = selectNumber.options[selectNumber.selectedIndex].innerHTML;
for (var i = 0; i < stationRentalsHours.length; i++) {
if (determineHour == stationRentalsHours[i]) {
stationRentalsHoursTemp.push(stationRentalsData[i])
stationRentalName.push(stationRentalsLabels[i]);
}
}
/* Updated Chart */
var ctx3 = document.getElementById("canvas3").getContext("2d");
var canvas3 = new Chart(ctx3, {
type: 'line',
data: {
labels: stationRentalName,
datasets: [{
label: 'Wypożyczenia',
fillColor: "rgba(220,280,220,0.5)",
strokeColor: "rgba(220,220,220,1)",
backgroundColor: "rgba(153,255,51,0.4)",
data: stationRentalsHoursTemp
}]
}
});
}
You are creating new chart on update function in the same div as before but in order to do that you need to destroy the previous instance of the chart by calling the destroy function before calling the updateChart function.
canvas3.destroy();
Another approach to solving your problem is by replacing the data not the chart itself when the updateChart function is called by calling the update function(Not initializing a new chart)
function updateChart() {
var stationRentalsHoursTemp = [];
var stationRentalName = [];
var determineHour = selectNumber.options[selectNumber.selectedIndex].innerHTML;
for (var i = 0; i < stationRentalsHours.length; i++) {
if (determineHour == stationRentalsHours[i]) {
stationRentalsHoursTemp.push(stationRentalsData[i])
stationRentalName.push(stationRentalsLabels[i]);
}
}
// just update the label and dataset not the entire chart
canvas3.data.labels = stationRentalName;
canvas3.data.datasets[0].data = stationRentalsHoursTemp;
canvas3.update();
}

ChartJs canvas showing previous graph when changing Graph types

I came across a unique incident in which when a user chooses from a drop down and the graph changes if you scan across the canvas the previous will show up like a ghost in the background. I know you can use something like graph.destroy() but I am not sure if that is appropriate and where to put it.
code where I update graphs
var ctx = document.getElementById("myChart").getContext("2d");
ctx.canvas.width = 600;
ctx.canvas.height = 200;
function updateChart() {
// ctx.canvas.destroy();
var determineChart = $("#chartType").val();
var params = dataMapMain[determineChart];
if ([params.method] == "Pie") {
document.getElementById("subOption").hidden = false;
document.getElementById("arrowId").hidden = false;
var determineChart = $("#subOption").val();
var params = dataMapSub[determineChart];
$('#subOption').on('change', updateChart);
new Chart(ctx)[params.method](params.data, options, {});
}
else {
document.getElementById("subOption").hidden = true;
document.getElementById("arrowId").hidden = true;
new Chart(ctx)[params.method](params.data, options, {});
}
}
$('#chartType').on('change', updateChart)
updateChart();
This fiddle demonstrates the issue, hover over the chart to see the "ghost".
Because you are not destroying the instances of Chart that you are no longer using, chartjs is drawing multiple charts into the same canvas context.
You need to have a reference of the Chart instances you new up to be able to call destroy on them before you new up another one.
Add this to your code:
var ctx = document.getElementById("myChart").getContext("2d");
ctx.canvas.width = 600;
ctx.canvas.height = 200;
var chart; // assign your chart reference to this variable
function updateChart() {
var determineChart = $("#chartType").val();
var params = dataMapMain[determineChart];
if ([params.method] == "Pie") {
document.getElementById("subOption").hidden = false;
document.getElementById("arrowId").hidden = false;
var determineChart = $("#subOption").val();
var params = dataMapSub[determineChart];
$('#subOption').on('change', updateChart);
chart && chart.destroy(); // destroy previous chart
chart = new Chart(ctx)[params.method](params.data, options, {});
}
else {
document.getElementById("subOption").hidden = true;
document.getElementById("arrowId").hidden = true;
chart && chart.destroy(); // destroy previous chart
chart = new Chart(ctx)[params.method](params.data, options, {});
}
}
$('#chartType').on('change', updateChart)
updateChart();
And here is a fiddle that demonstrates the fix.

Categories