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>
Related
I am using in HTML using Canvas, how to use add the text inside Doughnut chart. Here is my javascript code and and HTML code. I have used chart js version 3.2.1 so please give solution for the same version(3).
var overallStatsCanvasCtx = document.getElementById('pademicOverallStats');
var dataPandemicEmp = {
labels: ['Normal', 'No Mask', 'Warning', 'High Temperature'],
datasets: [{
label: "Overall Statistics",
data: ['4000', '2000', '1500', '2500'],
backgroundColor: ['#43C187', '#8FC3F0', '#FFCD5E', '#FF4800'],
}]
};
var overallStatschartOptions = {
responsive: true,
plugins: {
legend: {
display: true,
align: 'center',
position: 'bottom',
labels: {
fontColor: '#474B4F',
usePointStyle: true,
}
}
},
};
var doughnutChart = new Chart(overallStatsCanvasCtx, {
type: 'doughnut',
data: dataPandemicEmp,
options: overallStatschartOptions,
});
<canvas id="pademicOverallStats"></canvas>
You will have to use a custom plugin for that
var data = {
labels: [
"Red",
"Blue",
"Yellow"
],
datasets: [{
data: [300, 50, 100],
backgroundColor: [
"#FF6384",
"#36A2EB",
"#FFCE56"
],
hoverBackgroundColor: [
"#FF6384",
"#36A2EB",
"#FFCE56"
]
}]
};
var promisedDeliveryChart = new Chart(document.getElementById('myChart'), {
type: 'doughnut',
data: data,
options: {
responsive: true,
plugins: {
legend: {
display: false
}
}
},
plugins: [{
id: 'text',
beforeDraw: function(chart, a, b) {
var width = chart.width,
height = chart.height,
ctx = chart.ctx;
ctx.restore();
var fontSize = (height / 114).toFixed(2);
ctx.font = fontSize + "em sans-serif";
ctx.textBaseline = "middle";
var text = "75%",
textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = height / 2;
ctx.fillText(text, textX, textY);
ctx.save();
}
}]
});
<body>
<canvas id="myChart"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js#3.2.0/dist/chart.min.js"></script>
</body>
var data = {
labels: [
"Red",
"Blue",
"Yellow"
],
datasets: [{
data: [300, 50, 100],
backgroundColor: [
"#FF6384",
"#36A2EB",
"#FFCE56"
],
hoverBackgroundColor: [
"#FF6384",
"#36A2EB",
"#FFCE56"
]
}]
};
var promisedDeliveryChart = new Chart(document.getElementById('myChart'), {
type: 'doughnut',
data: data,
options: {
responsive: true,
plugins: {
legend: {
display: false
}
}
},
plugins: [{
id: 'text',
beforeDraw: function(chart, a, b) {
var width = chart.width,
height = chart.height,
ctx = chart.ctx;
ctx.restore();
var fontSize = (height / 240).toFixed(2);
ctx.font = fontSize + "em sans-serif";
ctx.textBaseline = "middle";
var text = "75%",
textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = height / 2;
ctx.fillText(text, textX, textY);
ctx.save();
}
}]
});
<body>
<canvas id="myChart"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js#3.2.0/dist/chart.min.js"></script>
</body>
I'm doing an inline plugin and using chartjs version 3.9.1. I have adjusted the other answers to fit my needs which take into account a chart title and legend. The text in the center of the circle is also based on the total of the 0th dataset.
plugins: [{
id: 'doughnutInteriorText',
beforeDraw: function(chart) {
var width = chart.chartArea.width,
height = chart.chartArea.height,
ctx = chart.ctx;
ctx.restore();
var fontSize = (height / 114).toFixed(2);
ctx.font = fontSize + "em sans-serif";
ctx.textBaseline = "middle";
var text = chart.data.datasets[0].data.reduce((partialSum, a) => partialSum + a, 0),
textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = (height / 2) + chart.legend.height + chart.titleBlock.height;
ctx.fillText(text, textX, textY);
ctx.save();
}
}]
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>
I'm using ChartJS for my implementation of charts, but I notice that one of my graph's label is hidden. It is not showing its label above the bar. I've added a screenshot below for the comparison of two different bar graphs. The left graph shows the label even if it is on the very top but the other one is not showing. Please see my screenshot and code below.
function createChart(context, type, bgColor, bdColor, labels, actualData, options = {}){
new Chart(context, {
type: type,
data: {
labels: labels,
datasets: [{
label: "Actual",
backgroundColor: bgColor,
borderColor: bdColor,
data: actualData,
}]
},
options: options
});
}
function getOptions(displayLegend = true){
return {
events: false,
showTooltips: false,
legend: {
display: displayLegend
},
animation: {
duration: 0,
onComplete: function(){
var ctx = this.chart.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontFamily, 'normal', Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseLine = 'bottom';
ctx.fillStyle = '#0b7707';
this.data.datasets.forEach(function(dataset){
console.log(dataset);
for(var i = 0; i < dataset.data.length; i++){
for(var key in dataset._meta){
var model = dataset._meta[key].data[i]._model;
ctx.fillText(dataset.data[i], model.x, model.y - 13);
}
}
});
}
}
};
}
I solved this problem by adding an empty title to the chart, so it will create space above the chart and show labels above the bar
options: {
title: {
display: true,
text: ' '
},
....
This looks like a clear case of the datalabels going out of the canvas since the bar takes height dynamically as per the data values. You can set the max y-tick setting to solve this. Here is the jsfiddle -> https://jsfiddle.net/Luaf2tm4/5979/
Hope it helps!
var canvas = document.getElementById('myChart');
var data = {
labels: ["January", "February", "March", "April", "May", "June"],
datasets: [{
label: "My First dataset",
backgroundColor: "rgba(255,99,132,0.2)",
borderColor: "rgba(255,99,132,1)",
borderWidth: 2,
hoverBackgroundColor: "rgba(255,99,132,0.4)",
hoverBorderColor: "rgba(255,99,132,1)",
data: [500, 2000, 800, 600, 950, 890],
}]
};
function getOptions(displayLegend = false) {
return {
events: false,
showTooltips: false,
legend: {
display: displayLegend
},
scales: {
yAxes: [{
display: true,
stacked: true,
ticks: {
stepSize: 200,
min: 0, // minimum value
max: 2200 // maximum value, you can either hard code if you know your datainput, else computer the value through some logic i.e taking the max value from the dataset and adding some extra value to it.
}
}]
},
animation: {
duration: 0,
onComplete: function() {
var ctx = this.chart.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontFamily, 'normal', Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseLine = 'bottom';
ctx.fillStyle = '#0b7707';
this.data.datasets.forEach(function(dataset) {
console.log(dataset);
for (var i = 0; i < dataset.data.length; i++) {
for (var key in dataset._meta) {
var model = dataset._meta[key].data[i]._model;
ctx.fillText(dataset.data[i], model.x, model.y - 10);
}
}
});
}
}
};
}
var myBarChart = Chart.Bar(canvas, {
data: data,
options: getOptions()
});
I want to ask if there is a way to make Pie Chart As Circle Progress with percent value, I want just one slice colored
something like this:
This is my fiddle for now I want just one data.
HTML:
<canvas id="chartProgress" width="300px" height="200"></canvas>
JS:
var chartProgress = document.getElementById("chartProgress");
if (chartProgress) {
var myChartCircle = new Chart(chartProgress, {
type: 'doughnut',
data: {
labels: ["Africa", 'null'],
datasets: [{
label: "Population (millions)",
backgroundColor: ["#5283ff"],
data: [68, 48]
}]
},
plugins: [{
beforeDraw: function(chart) {
var width = chart.chart.width,
height = chart.chart.height,
ctx = chart.chart.ctx;
ctx.restore();
var fontSize = (height / 150).toFixed(2);
ctx.font = fontSize + "em sans-serif";
ctx.fillStyle = "#9b9b9b";
ctx.textBaseline = "middle";
var text = "68%",
textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = height / 2;
ctx.fillText(text, textX, textY);
ctx.save();
}
}],
options: {
legend: {
display: false,
},
responsive: true,
maintainAspectRatio: false,
cutoutPercentage: 85
}
});
}
I know I can do it with normal HTML&CSS or using simple plugin but I want to do it using Chart.js
The Plugin Core API offers different hooks that may be used for executing custom code. You already use the beforeDraw hook to draw text in the middle of the doughnut.
You could now also use the beforeInit hook to modify the chart configuration in order to fit your needs:
beforeInit: (chart) => {
const dataset = chart.data.datasets[0];
chart.data.labels = [dataset.label];
dataset.data = [dataset.percent, 100 - dataset.percent];
}
Given this code, the definition of your dataset would look simple as follows:
{
label: 'Africa / Population (millions)',
percent: 68,
backgroundColor: ['#5283ff']
}
Last you have to define a tooltips.filter, so that the tooltip appears only at the relevant segment.
tooltips: {
filter: tooltipItem => tooltipItem.index == 0
}
Please take a look at your amended code and see how it works.
var myChartCircle = new Chart('chartProgress', {
type: 'doughnut',
data: {
datasets: [{
label: 'Africa / Population (millions)',
percent: 68,
backgroundColor: ['#5283ff']
}]
},
plugins: [{
beforeInit: (chart) => {
const dataset = chart.data.datasets[0];
chart.data.labels = [dataset.label];
dataset.data = [dataset.percent, 100 - dataset.percent];
}
},
{
beforeDraw: (chart) => {
var width = chart.chart.width,
height = chart.chart.height,
ctx = chart.chart.ctx;
ctx.restore();
var fontSize = (height / 150).toFixed(2);
ctx.font = fontSize + "em sans-serif";
ctx.fillStyle = "#9b9b9b";
ctx.textBaseline = "middle";
var text = chart.data.datasets[0].percent + "%",
textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = height / 2;
ctx.fillText(text, textX, textY);
ctx.save();
}
}
],
options: {
maintainAspectRatio: false,
cutoutPercentage: 85,
rotation: Math.PI / 2,
legend: {
display: false,
},
tooltips: {
filter: tooltipItem => tooltipItem.index == 0
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chartProgress"></canvas>
I'm trying since to days and really new to Chart.js. Everything seems to be clear but now i would like to put a label on top of every single bar.
Trying this i get an error: this.scale is undefined. I got the animation.onComplete Snippet out of the net but it seems i make a mistake. The Chart works fine .. i just don't get the labels on top of the bars. Maybe someone can please help me with this ?!
I also have a line chart with the same problem.
var ctx = document.getElementById("chartA").getContext("2d");
Chart.defaults.global.animation.duration = 2400;
Chart.defaults.global.animation.easing = "easeInOutQuad";
Chart.defaults.global.elements.point.radius = 4;
Chart.defaults.global.elements.point.hoverRadius = 5;
Chart.defaults.global.elements.point.hitRadius = 1;
var chart = new Chart(ctx, {
type: "bar",
data: {
labels: ["A","B","C"],
datasets: [{
label: "Test",
backgroundColor: "rgba(255, 99, 132, 0.2)",
borderColor: "#CF2748",
borderWidth: 1,
data: [10,20,30]
}]
},
options: {
tooltips: { mode: 'nearest', intersect: false },
layout: { padding: { left: 20, right: 0, top: 0, bottom: 0 } },
legend: { display: true, position: 'top' },
scales: {
yAxes: [{
ticks: { maxTicksLimit: 9, stepSize: 300, callback: function(value, index, values) { return value+" €"; } }
}]
},
animation: {
onComplete: function () {
var ctx = this.chart.ctx; // this part doesn't work
ctx.font = this.scale.font;
ctx.fillStyle = this.scale.textColor;
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
this.datasets.forEach(function (dataset) {
dataset.bars.forEach(function (bar) {
ctx.fillText(bar.value, bar.x, bar.y - 5);
});
});
}
}
}
});
Thank you so much
Oliver
Thanks #Jeff I was testing around and get closer.
chart.data.datasets.forEach(function (dataset) {
dataset.data.forEach(function (value) {
ctx.fillText(value, x, y);
});
});
Now i have in "value" the right value. But i need to refer X and Y. Where do i get them? If i change X and Y with static value it works but all values were logically printed on the same space.
There are several issues with your code.
You could rather use the following chart plugin to accomplish the same :
Chart.plugins.register({
afterDatasetsDraw: function(chart) {
var ctx = chart.ctx;
chart.data.datasets.forEach(function(dataset, datasetIndex) {
var datasetMeta = chart.getDatasetMeta(datasetIndex);
datasetMeta.data.forEach(function(meta) {
var posX = meta._model.x;
var posY = meta._model.y;
var value = chart.data.datasets[meta._datasetIndex].data[meta._index];
// draw values
ctx.save();
ctx.textBaseline = 'bottom';
ctx.textAlign = 'center';
ctx.font = '16px Arial';
ctx.fillStyle = 'black';
ctx.fillText(value, posX, posY);
ctx.restore();
});
});
}
});
add this plugin at the beginning of your script.
ᴡᴏʀᴋɪɴɢ ᴇxᴀᴍᴘʟᴇ ⧩
Chart.plugins.register({
afterDatasetsDraw: function(chart) {
var ctx = chart.ctx;
chart.data.datasets.forEach(function(dataset, datasetIndex) {
var datasetMeta = chart.getDatasetMeta(datasetIndex);
datasetMeta.data.forEach(function(meta) {
var posX = meta._model.x;
var posY = meta._model.y;
var value = chart.data.datasets[meta._datasetIndex].data[meta._index];
// draw values
ctx.save();
ctx.textBaseline = 'bottom';
ctx.textAlign = 'center';
ctx.font = '16px Arial';
ctx.fillStyle = 'black';
ctx.fillText(value, posX, posY);
ctx.restore();
});
});
}
});
var ctx = document.getElementById("chartA").getContext("2d");
Chart.defaults.global.animation.duration = 2400;
Chart.defaults.global.animation.easing = "easeInOutQuad";
Chart.defaults.global.elements.point.radius = 4;
Chart.defaults.global.elements.point.hoverRadius = 5;
Chart.defaults.global.elements.point.hitRadius = 1;
var chart = new Chart(ctx, {
type: "bar",
data: {
labels: ["A", "B", "C"],
datasets: [{
label: "Test",
backgroundColor: "rgba(255, 99, 132, 0.2)",
borderColor: "#CF2748",
borderWidth: 1,
data: [10, 20, 30]
}]
},
options: {
tooltips: {
mode: 'nearest',
intersect: false
},
layout: {
padding: {
left: 20,
right: 0,
top: 0,
bottom: 0
}
},
legend: {
display: true,
position: 'top'
},
scales: {
yAxes: [{
ticks: {
maxTicksLimit: 9,
stepSize: 300,
callback: function(value, index, values) {
return value + " €";
}
}
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.min.js"></script>
<canvas id="chartA"></canvas>