Chart.js stacked bar chart text on top of the stacked bars - javascript

I have Chart.js chart inside a div element. The way that I got so far is good looking and it is what I need, but it is missing one big part, a dynamic text that has to aggregate the data of the chart and show a summary. Not bar by bar , but a summary of the two bars. This is where I got so far:
var chart;
var opts = {
layout: {
padding: {
left: 0,
right: 0,
top: 0,
bottom: 0
}
},
responsive: true,
maintainAspectRatio: false,
legend: {
display: false,
},
tooltips: {
enabled: false,
},
scales: {
xAxes: [{
stacked: true,
display: false,
ticks: {
fontColor: "#fff",
}
}, ],
yAxes: [{
stacked: true,
display: false,
ticks: {
fontColor: "#fcafe4",
mirror: true
}
}]
}
};
var datasets = [{
label: "My Label",
backgroundColor: ["#FF3A2F"],
data: [],
},
{
label: "Your Label",
backgroundColor: ["#0CB04A"],
data: [],
}
];
var labels = ['Label 1'];
var ctx = document.getElementById('chart-canvas');
chart = new Chart(ctx, {
type: "horizontalBar",
data: {
labels: labels,
datasets: datasets
},
options: opts
});
function refresh() {
chart.data.datasets[0].data[0] = Math.floor(Math.random() * 1000) + 1;
chart.data.datasets[1].data[0] = Math.floor(Math.random() * 1000) + 1;
chart.config.options.scales.xAxes[0].ticks.max = chart.data.datasets[0].data[0] + chart.data.datasets[1].data[0];
chart.update();
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js"></script>
<div class="row">
<div class="col-lg-5 small">My Stacked Chart</div>
<div class="col-lg-6">
<div class="chart-container">
<canvas id="chart-canvas"></canvas>
</div>
</div>
<div class="col-lg-1 my-auto">
<div class="btn-group">
<button type="button" class="btn btn-primary btn-md btn-sm" onClick="refresh()">
Refresh
</button>
</div>
</div>
</div>
I'm wondering if there is a way that I can add a text that will be in the middle of the canvas and will show data that is in the chart. For example XXXX of Total and will be updated every time the Refresh button is clicked. Something like that:
Thanks in advance!
Julian

The Plugin Core API offers a range of hooks that may be used for performing custom code. You can use the afterDraw hook to draw text directly on the canvas using CanvasRenderingContext2D.fillText().
chart = new Chart(ctx, {
type: "horizontalBar",
plugins: [{
afterDraw: chart => {
var ctx = chart.chart.ctx;
var value = chart.config.data.datasets[0].data[0];
var total = chart.config.data.datasets.reduce((t, ds) => t + ds.data[0], 0);
var percent = Math.round(1000 / total * value) / 10;
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = "24px Arial";
ctx.fillText(value + ' (' + percent + '%) of ' + total, xAxis.right / 2, (yAxis.bottom + yAxis.top) / 2);
ctx.restore();
}
}],
...
Please have a look at your amended code below.
var chart;
var opts = {
layout: {
padding: {
left: 0,
right: 0,
top: 0,
bottom: 0
}
},
responsive: true,
maintainAspectRatio: false,
legend: {
display: false,
},
tooltips: {
enabled: false,
},
scales: {
xAxes: [{
stacked: true,
display: false,
ticks: {
fontColor: "#fff",
}
}, ],
yAxes: [{
stacked: true,
display: false,
ticks: {
fontColor: "#fcafe4",
mirror: true
}
}]
}
};
var datasets = [{
label: "My Label",
backgroundColor: ["#FF3A2F"],
data: [],
},
{
label: "Your Label",
backgroundColor: ["#0CB04A"],
data: [],
}
];
var labels = ['Label 1'];
var ctx = document.getElementById('chart-canvas');
chart = new Chart(ctx, {
type: "horizontalBar",
plugins: [{
afterDraw: chart => {
var ctx = chart.chart.ctx;
var value = chart.config.data.datasets[0].data[0];
var total = chart.config.data.datasets.reduce((t, ds) => t + ds.data[0], 0);
var percent = Math.round(1000 / total * value) / 10;
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = "24px Arial";
ctx.fillText(value + ' (' + percent + '%) of ' + total, xAxis.right / 2, (yAxis.bottom + yAxis.top) / 2);
ctx.restore();
}
}],
data: {
labels: labels,
datasets: datasets
},
options: opts
});
refresh();
function refresh() {
chart.data.datasets[0].data[0] = Math.floor(Math.random() * 1000) + 1;
chart.data.datasets[1].data[0] = Math.floor(Math.random() * 1000) + 1;
chart.config.options.scales.xAxes[0].ticks.max = chart.data.datasets[0].data[0] + chart.data.datasets[1].data[0];
chart.update();
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js"></script>
<div class="row">
<div class="col-lg-5 small">My Stacked Chart</div>
<div class="col-lg-6">
<div class="chart-container">
<canvas id="chart-canvas"></canvas>
</div>
</div>
<div class="col-lg-1 my-auto">
<div class="btn-group">
<button type="button" class="btn btn-primary btn-md btn-sm" onClick="refresh()">
Refresh
</button>
</div>
</div>
</div>

Related

How to change type of chartjs with button?

I have a button in my template to change the graph's type. I'm trying to do this through a button that triggers a changeChart function that receives the type of chart to be changed. But when I click on the button I get an error:
Uncaught TypeError: Cannot set properties of undefined (setting 'type')
Button:
<button class="btn btn-success" type="button" onclick="changeChart('line')" style="margin-top: 20px;">Line Chart</button>
<button class="btn btn-info" type="button" onclick="changeChart('bar')" style="margin-top: 20px;">Bar Chart</button>
Chart Js with function in the end
<script>
$(function () {
var $populationChart = $("#faturamento-mes");
$.ajax({
url: $populationChart.data("url"),
success: function (data) {
const backgroundcolor = []
for(i=0; i < data.data.length; i++){
if (data.data[i] < 2000){backgroundcolor.push('rgb(120, 0, 0)')
}else if (data.data[i] >= 2000){backgroundcolor.push('rgb(33,94,33)')
}
}
var ctx = $populationChart[0].getContext("2d");
new Chart(ctx, {
type: 'bar',
data: {
labels: data.labels,
datasets: [{
label: 'Faturamento',
backgroundColor: backgroundcolor,
fill: false,
data: data.data,
}]
},
options: {
responsive: true,
legend: {
display: false,
position: 'top',
labels: {
generateLabels: chart => {
return [{
datasetIndex: 0,
text: '',
fillStyle: 'white',
}];
}
}
},
title: {
display: true,
text: 'Faturamento por mês'
},
scales:{
yAxes:[{
display: true,
ticks: {
beginAtZero: true,
}
}]
},
animation: {
onComplete : function() {
var chartInstance = this.chart,
ctx = chartInstance.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.fillStyle = 'rgb(0, 0, 0)';
this.data.datasets.forEach(function(dataset, i) {
var meta = chartInstance.controller.getDatasetMeta(i);
meta.data.forEach(function(bar, index) {
if (dataset.data[index] > 0) {
var data = dataset.data[index];
ctx.fillText('R$'+ data, bar._model.x, bar._model.y);
}
});
});
}
}
}
});
}
});
});
function changeChart(charttype){
Chart.config.type = charttype;
Chart.update();
}

How to add images to chart labels with vue-chartjs?

I want to add flag icons under the country code labels but am completely stuck.
Image of the chart with my current code
The images are named BR.svg, FR.svg and MX.svg and are located under #/assets/icons/flags/
I am using vue#2.6.12 and vue-chartjs#3.5.1 in my project. This is my Chart.vue component:
<script>
import { Bar } from 'vue-chartjs'
export default {
extends: Bar,
data: () => ({
chartdata: {
labels: ['BR', 'FR', 'MX'],
datasets: [
{
label: 'Lorem ipsum',
backgroundColor: '#AF78D2',
data: [39, 30, 30],
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
legend: {
display: false,
},
tooltips: {
"enabled": false
},
scales : {
xAxes : [ {
gridLines : {
display : false
}
} ],
yAxes: [{
ticks: {
beginAtZero: true,
suggestedMin: 0,
suggestedMax: 40,
stepSize: 5,
}
}]
},
"hover": {
"animationDuration": 0
},
"animation": {
"duration": 1,
"onComplete": function() {
var chartInstance = this.chart,
ctx = chartInstance.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
this.data.datasets.forEach(function(dataset, i) {
var meta = chartInstance.controller.getDatasetMeta(i);
meta.data.forEach(function(bar, index) {
var data = dataset.data[index] + '%';
ctx.fillText(data, bar._model.x, bar._model.y - 5);
});
});
}
},
}
}),
mounted () {
this.renderChart(this.chartdata, this.options)
}
}
</script>
This runnable code below is the closest to a solution I have come by hours of searching. But it still won't do the trick because I don't know how to integrate it with what I have.
const labels = ['Red Vans', 'Blue Vans', 'Green Vans', 'Gray Vans'];
const images = ['https://i.stack.imgur.com/2RAv2.png', 'https://i.stack.imgur.com/Tq5DA.png', 'https://i.stack.imgur.com/3KRtW.png', 'https://i.stack.imgur.com/iLyVi.png'];
const values = [48, 56, 33, 44];
new Chart(document.getElementById("myChart"), {
type: "bar",
plugins: [{
afterDraw: chart => {
var ctx = chart.chart.ctx;
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
xAxis.ticks.forEach((value, index) => {
var x = xAxis.getPixelForTick(index);
var image = new Image();
image.src = images[index],
ctx.drawImage(image, x - 12, yAxis.bottom + 10);
});
}
}],
data: {
labels: labels,
datasets: [{
label: 'My Dataset',
data: values,
backgroundColor: ['red', 'blue', 'green', 'lightgray']
}]
},
options: {
responsive: true,
legend: {
display: false
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}],
xAxes: [{
ticks: {
padding: 30
}
}],
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="myChart" height="90"></canvas>
When I have added the plugin code into my own code I get an error message saying 'plugins' is already defined in props, but I can't manage to use it somehow.
Anyone who knows how to implement this afterDraw plugin into my code? I appreciate any input.
Thanks a lot in advance! :)
In the mounted of your vue component you can call the addPlugin (has to be done before the renderChart method) like this:
this.addPlugin({
id: 'image-label',
afterDraw: (chart) => {
var ctx = chart.chart.ctx;
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
xAxis.ticks.forEach((value, index) => {
var x = xAxis.getPixelForTick(index);
var image = new Image();
image.src = images[index],
ctx.drawImage(image, x - 12, yAxis.bottom + 10);
});
}
})
Documentation: https://vue-chartjs.org/api/#addplugin
It works in ChartJS version 4.0.1. data needs to return 'plugins':
data() {
return {
plugins: [{
afterDraw: chart => {
const ctx = chart.ctx;
const xAxis = chart.scales['x'];
const yAxis = chart.scales['y'];
xAxis.ticks.forEach((value, index) => {
let x = xAxis.getPixelForTick(index);
ctx.drawImage(images[index], x - 12, yAxis.bottom + 10)
});
}
}],
data: {...
Please note that ctx should be chart.ctx and NOT chart.chart.ctx.. Similarly, it should be chart.scales['x'] and NOT chart.scales['x-axis-0'].
After you return plugins, this needs to be referenced in your Bar component like so..
<Bar :data="data" :options="options" :plugins="plugins"/>

how can i use inline plugin inner title for chart js?

i am creating a JavaScript web page with two doughnut charts. to create the charts i'm using one function and calling it twice with different data for each chart. however when i do that the text in the middle of the doughnut chart is being changed for both charts not only for current one. how can i write the text individually for each?
this is my code
function chart(dummynames, dummyValues, dummyColors, percentage, id) {
//dummy names, dummy values and dummy colors are arrays
new Chart(document.getElementById(id), {
type: 'doughnut',
data: {
labels: dummynames,
datasets: [{
label: "tessers",
backgroundColor: dummyColors,
data: dummyValues,
}]
},
options: {
title: {
display: true,
align: 'center',
horizontalAlign: "center",
verticalAlign: "bottom",
dockInsidePlotArea: true
},
legend: {
display: false
},
cutoutPercentage: 70,
},
});
Chart.pluginService.register({
beforeDraw: function(chart) {
var width = chart.chart.width,
height = chart.chart.height + 35,
ctx = chart.chart.ctx;
ctx.restore();
var fontSize = (height / 200).toFixed(2);
ctx.font = fontSize + "em sans-serif";
ctx.textBaseline = "middle";
var text = percentage,
textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = height / 2;
ctx.fillText(text, textX, textY);
ctx.save();
}
})
}
enter image description here
You should place Chart.pluginService.register outside of the chart function, thus making sure that it is invoked once only.
Please take a look at your amended code and see how it works.
Chart.pluginService.register({
beforeDraw: function(chart) {
var width = chart.chart.width,
height = chart.chart.height + 35,
ctx = chart.chart.ctx;
ctx.save();
var fontSize = (height / 200).toFixed(2);
ctx.font = fontSize + "em sans-serif";
ctx.textBaseline = "middle";
var text = chart.data.datasets[0].percentage,
textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = height / 2;
ctx.fillText(text, textX, textY);
ctx.restore();
}
});
function chart(dummynames, dummyValues, dummyColors, percentage, id) {
new Chart(document.getElementById(id), {
type: 'doughnut',
data: {
labels: dummynames,
datasets: [{
label: "tessers",
backgroundColor: dummyColors,
data: dummyValues,
percentage: percentage
}]
},
options: {
title: {
display: true,
align: 'center',
horizontalAlign: "center",
verticalAlign: "bottom",
dockInsidePlotArea: true
},
legend: {
display: false
},
cutoutPercentage: 70,
},
});
}
chart(['A', 'B'], [5, 6], ['red', 'blue'], '26.846%', 'myChart1');
chart(['X', 'Y'], [7, 5], ['green', 'purple'], '19.451%', 'myChart2');
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
<div style="display: flex">
<div>
<canvas id="myChart1"></canvas>
</div>
<div>
<canvas id="myChart2"></canvas>
</div>
</div>

How to add text at center of the chart js donut chart

I want to place the text my data values at the center of the chart js donut charts, I don't know how to do that, I checked the chart js official docs, but they didn't provide any information about this, how can I achieve this.
Here is my code:
HTML:
<canvas id="gx_150s_658Ed8745321" width="200" height="120"></canvas>
JS:
var randomScalingFactor = function () {
return Math.round(Math.random() * 100);
};
var gx_150s_658Ed8745321_ctx = document.getElementById('gx_150s_658Ed8745321').getContext('2d');
var gx_150s_658Ed8745321 = new Chart(gx_150s_658Ed8745321_ctx, {
type: 'doughnut',
data: {
labels: ['Utilized', 'Balence'],
datasets: [{
label: 'Utilized',
data: [95, 5],
backgroundColor: [
'rgb(0, 153, 0, 0.7)',
],
borderColor: [
'rgba(54, 162, 235, 2)',
],
borderWidth: 1
}]
},
options: {
legend: {
display: false
},
title: {
display: true,
text: ' Utilized : 95 %'
},
animation: {
animateScale: true,
animateRotate: true
},
}
});
Expected output:
I'm using a simple plug-in:
config = {
options: {
//...
}
//...
plugin: [{
id: 'my-doughnut-text-plugin',
afterDraw: function (chart, option) {
let theCenterText = "50%" ;
const canvasBounds = canvas.getBoundingClientRect();
const fontSz = Math.floor( canvasBounds.height * 0.10 ) ;
chart.ctx.textBaseline = 'middle';
chart.ctx.textAlign = 'center';
chart.ctx.font = fontSz+'px Arial';
chart.ctx.fillText(theCenterText, canvasBounds.width/2, canvasBounds.height*0.70 )
}
}];
}
You still need to calculate what you wan't in the center text (variable theCenterText).
we can use the animation onComplete callback to know when the animation has finished.
then we can calculate the size and placement of the canvas,
and position a label in the center of the canvas.
animation: {
animateScale: true,
animateRotate: true,
onComplete: function() {
var canvasBounds = canvas.getBoundingClientRect();
dataLabel.innerHTML = ' Utilized : 95 %';
var dataLabelBounds = dataLabel.getBoundingClientRect();
dataLabel.style.top = (canvasBounds.top + (canvasBounds.height / 2) - (dataLabelBounds.height / 2)) + 'px';
dataLabel.style.left = (canvasBounds.left + (canvasBounds.width / 2) - (dataLabelBounds.width / 2)) + 'px';
}
},
see following working snippet...
$(document).ready(function() {
var randomScalingFactor = function () {
return Math.round(Math.random() * 100);
};
var canvas = document.getElementById('gx_150s_658Ed8745321');
var dataLabel = document.getElementById('data-label');
var gx_150s_658Ed8745321_ctx = canvas.getContext('2d');
var gx_150s_658Ed8745321 = new Chart(gx_150s_658Ed8745321_ctx, {
type: 'doughnut',
data: {
labels: ['Utilized', 'Balence'],
datasets: [{
label: 'Utilized',
data: [95, 5],
backgroundColor: [
'rgb(0, 153, 0, 0.7)',
],
borderColor: [
'rgba(54, 162, 235, 2)',
],
borderWidth: 1
}]
},
options: {
legend: {
display: false
},
animation: {
animateScale: true,
animateRotate: true,
onComplete: function() {
var canvasBounds = canvas.getBoundingClientRect();
dataLabel.innerHTML = ' Utilized : 95 %';
var dataLabelBounds = dataLabel.getBoundingClientRect();
dataLabel.style.top = (canvasBounds.top + (canvasBounds.height / 2) - (dataLabelBounds.height / 2)) + 'px';
dataLabel.style.left = (canvasBounds.left + (canvasBounds.width / 2) - (dataLabelBounds.width / 2)) + 'px';
}
},
}
});
});
#data-label {
font-size: 20px;
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.bundle.min.js"></script>
<canvas id="gx_150s_658Ed8745321" width="200" height="120"></canvas>
<span id="data-label"></span>

Put the value into the doughnut chart

I need to input the value of the first percentage into the chart, like this image
In this case, put the value of pedro(33%) within the chart.
I am beginner with chartJS and do not know it completely. Is it possible to do that?
var randomScalingFactor = function() {
return Math.round(Math.random() * 100);
};
var config = {
type: 'doughnut',
data: {
datasets: [{
data: [
33,
67,
],
backgroundColor: [
"#F7464A",
"#46BFBD",
],
label: 'Expenditures'
}],
labels: [
"Pedro: 33 ",
"Henrique: 67 ",
]
},
options: {
responsive: true,
legend: {
position: 'bottom',
},
title: {
display: true,
text: 'Pedro Henrique Kuzminskas Miyazaki de Souza'
},
animation: {
animateScale: true,
animateRotate: true
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
var dataset = data.datasets[tooltipItem.datasetIndex];
var total = dataset.data.reduce(function(previousValue, currentValue, currentIndex, array) {
return previousValue + currentValue;
});
var currentValue = dataset.data[tooltipItem.index];
var precentage = Math.floor(((currentValue / total) * 100) + 0.5);
return precentage + "%";
}
}
}
}
};
var ctx = document.getElementById("myChart").getContext("2d");
window.myDoughnut = new Chart(ctx, config); {
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.min.js"></script>
<canvas id="myChart" width="400" height="200"></canvas>
This is not part of the default behavior. You will need to modify the chart.js script.
How to add text inside the doughnut chart using Chart.js?

Categories