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>
Related
I have a doughnut chart that gets data from an API. The chart displays ok but I can't get the chart data to update, and do that every 2 seconds. I have tried and searched many topics but most are outdated versions of chart.js and my javascript skills are poor.
Chart.js v3.9.1
const totalRunTime = current_array.MTConnectStreams.Streams.DeviceStream.ComponentStream[12].Samples.DynamicBasicData[1];
const totalTimeRemaining = current_array.MTConnectStreams.Streams.DeviceStream.ComponentStream[12].Samples.DynamicBasicData[13];
const timeRemaining = ((totalTimeRemaining / totalRunTime) * 100)
// Setup
const datapoints = [(100 - timeRemaining), timeRemaining];
const data = {
datasets: [{
data: datapoints,
backgroundColor: ['rgba(20, 121, 255, 0.7)', 'rgba(208, 208, 208, 0.5)'],
borderWidth: 1,
cutout: '70%'
}]
};
// Counter
const counter = {
id: 'counter',
beforeDraw( chart, args, options ) {
const { ctx, chartArea: { top, right, bottom, left, width, height } } = chart;
ctx.save();
ctx.font = options.fontSize + 'px ' + options.fontFamily;
ctx.textAlign = 'center';
ctx.fillStyle = options.fontColor;
ctx.fillText(datapoints[0].toFixed(3) + '%', width / 2, (height / 2) + (options.fontSize * 0.34));
}
};
// Config
const config = {
type: 'doughnut',
data,
options: {
plugins: {
legend: {
display: false
},
tooltip: {
enabled: false
},
counter: {
fontColor: '#193b68',
fontSize: '16',
fontFamily: 'Sofia Pro Medium'
}
}
},
plugins: [counter]
};
// Render init
const doughnutChart = new Chart(
document.getElementById('doughnutChart'),
config
);
setInterval(function addData(chart, data) {
chart.data.datasets.forEach((dataset) => {
dataset.data = data;
});
chart.update('none');
}, 2000
);
HTML
<canvas id="doughnutChart"></canvas>
It looks like it's not appearing to update because you're calling your data and storing in a variable which, from the scope of the application had not changed.
If you move your data request to a function and then call that from the initialising code and within setInterval you should see your data being updated freshly with each call to the function getData.
Here's a version of your code with what I mean, where getData() is used instead of datapoints:
const getData = () => {
console.log('Requesting data')
const totalRunTime = current_array.MTConnectStreams.Streams.DeviceStream.ComponentStream[12].Samples.DynamicBasicData[1];
const totalTimeRemaining = current_array.MTConnectStreams.Streams.DeviceStream.ComponentStream[12].Samples.DynamicBasicData[13];
const timeRemaining = ((totalTimeRemaining / totalRunTime) * 100)
return [(100 - timeRemaining), timeRemaining]
}
const data = {
datasets: [{
data: getData(),
backgroundColor: ['rgba(20, 121, 255, 0.7)', 'rgba(208, 208, 208, 0.5)'],
borderWidth: 1,
cutout: '70%'
}]
};
const counter = {
id: 'counter',
beforeDraw( chart, args, options ) {
const { ctx, chartArea: { top, right, bottom, left, width, height } } = chart;
ctx.save();
ctx.font = options.fontSize + 'px ' + options.fontFamily;
ctx.textAlign = 'center';
ctx.fillStyle = options.fontColor;
ctx.fillText(getData()[0].toFixed(3) + '%', width / 2, (height / 2) + (options.fontSize * 0.34));
}
};
const config = {
type: 'doughnut',
data,
options: {
plugins: {
legend: {
display: false
},
tooltip: {
enabled: false
},
counter: {
fontColor: '#193b68',
fontSize: '16',
fontFamily: 'Sofia Pro Medium'
}
}
},
plugins: [counter]
};
const doughnutChart = new Chart(
document.getElementById('chart'),
config
);
setInterval(function addData(chart, data) {
console.log('updating', Date.now())
doughnutChart.data.datasets.forEach((dataset) => {
dataset.data = getData();
});
doughnutChart.update('none');
}, 2000
);
Here's a codepen of an updating version but with random values used instead of your data requests.
This might be the problem and I hope this helps you.
One tip is to use console.log to see what's being called and placing your code in functions can make it easier to debug too by seeing where and when it is being called by using console.log or debugger.
I've been using Chart.JS a little more than a week, and I've faced some problems, but right now I'm really stuck.
I need to create something similar to this, there are 1 product(blue) value and the orange one is an estimate of its value on the next month. So what I need to do is basically (((a — b) * 100) / a (or b I can't really remember)) and this as a label not actual data, and the result would be some percentage that would be used as a line on top of one product.
So far I got only this code, but it's not working like I need to.
{
type: "line",
label: monthsLabels?.mesBaseLabel,
data: [
productsValues?.receitaLiquidaBase[0],
productsValues?.receitaLiquidaOrcado[0],
],
fill: false,
},
{
type: "line",
label: monthsLabels?.mesOrcadoLabel,
data: [
productsValues?.receitaLiquidaBase[0],
productsValues?.receitaLiquidaOrcado[0],
],
// data: productsValues?.receitaLiquidaOrcado,
fill: false,
},
{
type: "bar",
label: monthsLabels?.mesBaseLabel,
data: productsValues?.receitaLiquidaBase,
// [
// productsValues?.receitaLiquidaBase[0],
// productsValues?.receitaLiquidaBase[1],
// ],
backgroundColor: ["rgba(42,62,176, 1)"],
borderWidth: 4,
datalabels: {
// anchor: "end",
// align: "top",
font: {
size: 10,
},
rotation: -90,
color: "white",
formatter: (value, context) => {
// console.log(value);
if (value !== 0) {
return value?.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, "$&,");
} else {
return 0;
}
},
},
},
{
type: "bar",
label: monthsLabels?.mesOrcadoLabel,
data: productsValues?.receitaLiquidaOrcado,
backgroundColor: "orange",
borderWidth: 4,
datalabels: {
// anchor: "end",
// align: "top",
font: {
size: 10,
},
rotation: -90,
color: "black",
formatter: (value, context) => {
// console.log(value);
if (value !== 0) {
return value?.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, "$&,");
} else {
return 0;
}
},
},
},
Objects on, data and label are an array of number coming from some fetching.
You are best off using a custom inline plugin for this:
var options = {
type: 'bar',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: 'red'
},
{
label: '# of Points',
data: [7, 11, 5, 8, 3, 7],
backgroundColor: 'blue'
}
]
},
options: {
plugins: {
customValue: {
offset: 5,
dash: [5, 15]
}
}
},
plugins: [{
id: 'customValue',
afterDraw: (chart, args, opts) => {
const {
ctx,
data: {
datasets
},
_metasets
} = chart;
datasets[1].data.forEach((dp, i) => {
let increasePercent = dp * 100 / datasets[0].data[i] >= 100 ? Math.round((dp * 100 / datasets[0].data[i] - 100) * 100) / 100 : Math.round((100 - dp * 100 / datasets[0].data[i]) * 100) / 100 * -1;
let barValue = `${increasePercent}%`;
const lineHeight = ctx.measureText('M').width;
const offset = opts.offset || 0;
const dash = opts.dash || [];
ctx.textAlign = 'center';
ctx.fillText(barValue, _metasets[1].data[i].x, (_metasets[1].data[i].y - lineHeight * 1.5), _metasets[1].data[i].width);
if (_metasets[0].data[i].y >= _metasets[1].data[i].y) {
ctx.beginPath();
ctx.setLineDash(dash);
ctx.moveTo(_metasets[0].data[i].x, _metasets[0].data[i].y);
ctx.lineTo(_metasets[0].data[i].x, _metasets[1].data[i].y - offset);
ctx.lineTo(_metasets[1].data[i].x, _metasets[1].data[i].y - offset);
ctx.stroke();
} else {
ctx.beginPath();
ctx.setLineDash(dash);
ctx.moveTo(_metasets[0].data[i].x, _metasets[0].data[i].y - offset);
ctx.lineTo(_metasets[1].data[i].x, _metasets[0].data[i].y - offset);
ctx.lineTo(_metasets[1].data[i].x, _metasets[1].data[i].y - offset - lineHeight * 2);
ctx.stroke();
}
});
}
}]
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.4.1/chart.js"></script>
</body>
I achieved animating a plot using Jukka Kurkela example here.
Now I am having trouble customizing this plot further.
Logic of the custom plot
The plot starts animating with the x-axis labels being 0-20. When the plot reaches 20 then update the x-axis to be 20-40. Increment i or 20 until the x-axis reach its limit.
How to apply the logic above to the Example below?
// Generating data
var data = [];
var prev = 100;
for (var i=0;i<200;i++) {
prev += 5 - Math.random()*10;
data.push({x: i, y: prev});
}
var delayBetweenPoints = 100;
var started = {};
var ctx2 = document.getElementById("chart2").getContext("2d");
var chart2 = new Chart(ctx2, {
type: "line",
data: {
datasets: [
{
backgroundColor: "transparent",
borderColor: "rgb(255, 99, 132)",
borderWidth: 1,
pointRadius: 0,
data: data,
fill: true,
animation: (context) => {
var delay = 0;
var index = context.dataIndex;
if (!started[index]) {
delay = index * delayBetweenPoints;
started[index] = true;
}
var {x,y} = index > 0 ? context.chart.getDatasetMeta(0).data[index-1].getProps(['x','y'],
true) : {x: 0, y: 100};
return {
x: {
easing: "linear",
duration: delayBetweenPoints,
from: x,
delay
},
y: {
easing: "linear",
duration: delayBetweenPoints * 500,
from: y,
delay
},
skip: {
type: 'boolean',
duration: delayBetweenPoints,
from: true,
to: false,
delay: delay
}
};
}
}
]
},
options: {
scales: {
x: {
type: 'linear'
}
}
}
});
<div class="chart">
<canvas id="chart2"></canvas>
</div>
<script src="https://www.chartjs.org/dist/master/Chart.js"></script>
Solved it! Instead of incrementing 20 seconds, it is incrementing every 5 seconds ahead of time. Definitely a better experience for the user.
Got help from Rowf Abd's post.
var myData = [];
var prev = 100;
for (var i=0;i<60;i++) {
prev += 5 - Math.random()*10;
myData.push({x: i, y: prev});
}
var ctx = document.getElementById('myChart').getContext('2d');
var chart = new Chart(ctx, {
type: 'line',
data: {
datasets: [{
data: [myData[0]],
pointRadius: 0,
fill: false,
borderColor: "black",
lineTension: 0
}]
},
options: {
legend: {
onClick: (e) => e.stopPropagation()
},
title:{
fontColor: 'Black'
},
layout: {
padding: {
right: 10
}
},
scales: {
xAxes: [{
type: 'linear',
ticks: {
}
}],
yAxes: [{
scaleLabel: {
// fontFamily: 'Lato',
fontSize: 19,
fontColor: "Black"
}
}]
}
}
});
var next = function() {
var data = chart.data.datasets[0].data;
var count = data.length;
var xabsmin = 20;
var xabsmax = 60;
var incVar = 5;
data[count] = data[count - 1];
chart.update({duration: 0});
data[count] = myData[count];
chart.update();
if (count < myData.length - 1) {
setTimeout(next, 500);
}
if (data[count].x < xabsmin) {
chart.config.options.scales.xAxes[0].ticks.min = xabsmin - xabsmin;
chart.config.options.scales.xAxes[0].ticks.max = xabsmin;
chart.update();
}
if(data[count].x >= xabsmin && data[count].x < (xabsmax)){
var currentT = parseFloat(data[count].x);
var modDiv = (currentT % incVar);
var tempXMax = (currentT) + (incVar - modDiv);
chart.config.options.scales.xAxes[0].ticks.max = tempXMax;
chart.config.options.scales.xAxes[0].ticks.min = tempXMax - xabsmin;
chart.update();
}
}
setTimeout(next, 500);
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.min.js"></script>
<canvas id="myChart"></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>
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?