Is it possible using Chart.js to display data values?
I want to print the graph.
Thanks for any advice..
There is an official plugin for Chart.js 2.7.0+ to do this: Datalabels
Otherwise, you can loop through the points / bars onAnimationComplete and display the values
Preview
HTML
<canvas id="myChart1" height="300" width="500"></canvas>
<canvas id="myChart2" height="300" width="500"></canvas>
Script
var chartData = {
labels: ["January", "February", "March", "April", "May", "June"],
datasets: [
{
fillColor: "#79D1CF",
strokeColor: "#79D1CF",
data: [60, 80, 81, 56, 55, 40]
}
]
};
var ctx = document.getElementById("myChart1").getContext("2d");
var myLine = new Chart(ctx).Line(chartData, {
showTooltips: false,
onAnimationComplete: function () {
var ctx = this.chart.ctx;
ctx.font = this.scale.font;
ctx.fillStyle = this.scale.textColor
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
this.datasets.forEach(function (dataset) {
dataset.points.forEach(function (points) {
ctx.fillText(points.value, points.x, points.y - 10);
});
})
}
});
var ctx = document.getElementById("myChart2").getContext("2d");
var myBar = new Chart(ctx).Bar(chartData, {
showTooltips: false,
onAnimationComplete: function () {
var ctx = this.chart.ctx;
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);
});
})
}
});
Fiddle - http://jsfiddle.net/uh9vw0ao/
This works for Chart.js 2.3, including for both line/bar types.
Important: Even if you don't need the animation, don't change the duration option to 0. Otherwise, you will get chartInstance.controller is undefined error.
var chartData = {
labels: ["January", "February", "March", "April", "May", "June"],
datasets: [
{
fillColor: "#79D1CF",
strokeColor: "#79D1CF",
data: [60, 80, 81, 56, 55, 40]
}
]
};
var opt = {
events: false,
tooltips: {
enabled: false
},
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);
});
});
}
}
};
var ctx = document.getElementById("Chart1"),
myLineChart = new Chart(ctx, {
type: 'bar',
data: chartData,
options: opt
});
<canvas id="myChart1" height="300" width="500"></canvas>
If you are using the plugin chartjs-plugin-datalabels then the following code options object will help.
Make sure you import import ChartDataLabels from 'chartjs-plugin-datalabels'; in your TypeScript file or add reference to <script src="chartjs-plugin-datalabels.js"></script> in your javascript file and register the plugin using ChartJS.register(ChartDataLabels).
options: {
maintainAspectRatio: false,
responsive: true,
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
}
}]
},
plugins: {
datalabels: {
anchor: 'end',
align: 'top',
formatter: Math.round,
font: {
weight: 'bold'
}
}
}
}
This animation option works for 2.1.3 on a bar chart.
Slightly modified Ross answer:
animation: {
duration: 0,
onComplete: function () {
// render the value of the chart above the bar
var ctx = this.chart.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, 'normal', Chart.defaults.global.defaultFontFamily);
ctx.fillStyle = this.chart.config.options.defaultFontColor;
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
this.data.datasets.forEach(function (dataset) {
for (var i = 0; i < dataset.data.length; i++) {
var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model;
ctx.fillText(dataset.data[i], model.x, model.y - 5);
}
});
}
}
Based on Ross's answer for Chart.js 2.0 and up, I had to include a little tweak to guard against the case when the bar's heights comes too chose to the scale boundary.
The animation attribute of the bar chart's option:
animation: {
duration: 500,
easing: "easeOutQuart",
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';
this.data.datasets.forEach(function (dataset) {
for (var i = 0; i < dataset.data.length; i++) {
var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model,
scale_max = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._yScale.maxHeight;
ctx.fillStyle = '#444';
var y_pos = model.y - 5;
// Make sure data value does not get overflown and hidden
// when the bar's value is too close to max value of scale
// Note: The y value is reverse, it counts from top down
if ((scale_max - model.y) / scale_max >= 0.93)
y_pos = model.y + 20;
ctx.fillText(dataset.data[i], model.x, y_pos);
}
});
}
}
I think the nicest option to do this in Chart.js v2.x is by using a plugin, so you don't have a large block of code in the options. In addition, it prevents the data from disappearing when hovering over a bar.
I.e., simply use this code, which registers a plugin that adds the text after the chart is drawn.
Chart.pluginService.register({
afterDraw: function(chartInstance) {
var ctx = chartInstance.chart.ctx;
// render the value of the chart above the bar
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, 'normal', Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
chartInstance.data.datasets.forEach(function (dataset) {
for (var i = 0; i < dataset.data.length; i++) {
var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model;
ctx.fillText(dataset.data[i], model.x, model.y - 2);
}
});
}
});
Following this good answer, I'd use these options for a bar chart:
var chartOptions = {
animation: false,
responsive : true,
tooltipTemplate: "<%= value %>",
tooltipFillColor: "rgba(0,0,0,0)",
tooltipFontColor: "#444",
tooltipEvents: [],
tooltipCaretSize: 0,
onAnimationComplete: function()
{
this.showTooltip(this.datasets[0].bars, true);
}
};
window.myBar = new Chart(ctx1).Bar(chartData, chartOptions);
This still uses the tooltip system and his advantages (automatic positionning, templating, ...) but hiding the decorations (background color, caret, ...)
I'd recommend using this plugin: datalabels
Labels can be added to your charts simply by importing the plugin into the JavaScript file, for example:
import 'chartjs-plugin-datalabels'
And can be fine-tuned using this documentation: https://chartjs-plugin-datalabels.netlify.com/options.html
From my experience, once you include the chartjs-plugin-datalabels plugin (make sure to place the <script> tag after the chart.js tag on your page), your charts begin to display values.
If you then choose you can customize it to fit your needs. The customization is clearly documented here but basically, the format is like this hypothetical example:
var myBarChart = new Chart(ctx, {
type: 'bar',
data: yourDataObject,
options: {
// other options
plugins: {
datalabels: {
anchor :'end',
align :'top',
// and if you need to format how the value is displayed...
formatter: function(value, context) {
return GetValueFormatted(value);
}
}
}
}
});
From Chart.js samples (file Chart.js-2.4.0/samples/data_labelling.html):
// Define a plugin to provide data labels
Chart.plugins.register({
afterDatasetsDraw: function(chartInstance, easing) {
// To only draw at the end of animation, check for easing === 1
var ctx = chartInstance.chart.ctx;
chartInstance.data.datasets.forEach(function (dataset, i) {
var meta = chartInstance.getDatasetMeta(i);
if (!meta.hidden) {
meta.data.forEach(function(element, index) {
// Draw the text in black, with the specified font
ctx.fillStyle = 'rgb(0, 0, 0)';
var fontSize = 16;
var fontStyle = 'normal';
var fontFamily = 'Helvetica Neue';
ctx.font = Chart.helpers.fontString(fontSize, fontStyle, fontFamily);
// Just naively convert to string for now
var dataString = dataset.data[index].toString();
// Make sure alignment settings are correct
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
var padding = 5;
var position = element.tooltipPosition();
ctx.fillText(dataString, position.x, position.y - (fontSize / 2) - padding);
});
}
});
}
});
Adapted the #Ross answer to work with 3.7.0 version of the Chartjs
animation: {
duration: 0,
onComplete: function() {
ctx = this.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.font.size, Chart.defaults.font.style, Chart.defaults.font.family);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
chartinst = this;
this.data.datasets.forEach(function(dataset, i) {
if(chartinst.isDatasetVisible(i)){
var meta = chartinst.getDatasetMeta(i);
meta.data.forEach(function(bar, index) {
var data = dataset.data[index];
ctx.fillText(data, bar.x, bar.y - 5);
});
}
});
}
}
In this case, animation can be 0
To have a nicer looking, you can disable the hover and the tooltip if you want a more "static" visualization
Also, the isDataSetVisible works to get rid of the numbers that stay shown when you hide the dataset in case of multiple datasets
I edited Aaron Hudon's answer a little, but only for bar charts. My version adds:
Fade in animation for the values.
Prevent clipping by positioning the value inside the bar if the bar is too high.
No blinking.
Downside: When hovering over a bar that has a value inside it, the value might look a little jagged. I have not found a solution do disable hover effects. It might also need tweaking depending on your own settings.
Configuration:
bar: {
tooltips: {
enabled: false
},
hover: {
animationDuration: 0
},
animation: {
onComplete: function() {
this.chart.controller.draw();
drawValue(this, 1);
},
onProgress: function(state) {
var animation = state.animationObject;
drawValue(this, animation.currentStep / animation.numSteps);
}
}
}
Helpers:
// Font color for values inside the bar
var insideFontColor = '255,255,255';
// Font color for values above the bar
var outsideFontColor = '0,0,0';
// How close to the top edge bar can be before the value is put inside it
var topThreshold = 20;
var modifyCtx = function(ctx) {
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, 'normal', Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
return ctx;
};
var fadeIn = function(ctx, obj, x, y, black, step) {
var ctx = modifyCtx(ctx);
var alpha = 0;
ctx.fillStyle = black ? 'rgba(' + outsideFontColor + ',' + step + ')' : 'rgba(' + insideFontColor + ',' + step + ')';
ctx.fillText(obj, x, y);
};
var drawValue = function(context, step) {
var ctx = context.chart.ctx;
context.data.datasets.forEach(function (dataset) {
for (var i = 0; i < dataset.data.length; i++) {
var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model;
var textY = (model.y > topThreshold) ? model.y - 3 : model.y + 20;
fadeIn(ctx, dataset.data[i], model.x, textY, model.y > topThreshold, step);
}
});
};
To prevent your numbers from being cut off if they're too close to the top of the canvas:
yAxes: [{
ticks: {
stepSize: Math.round((1.05*(Math.max.apply(Math, myListOfyValues)) / 10)/5)*5,
suggestedMax: 1.05*(Math.max.apply(Math, myListOfyValues)),
beginAtZero: true,
precision: 0
}
}]
10 = the number of ticks
5 = rounds tick values to the nearest 5 - all y values will be incremented evenly
1.05 = increases the maximum y axis tick value so the numbers don't get cut off
Something similar will work for xAxes too.
Related
I'm using a permutation of #Cmyker's code (see here) to have text in the center of a doughnut chart, but I'm having trouble getting the date setting I made to break into another line instead of sticking to the same line as the rest of the text. I've tried adding /n on the end of the text where I want it to break but that doesn't seem to be working, any advice on this?
//Date for chart
let today = new Date();
var date = today.getDate()+'/'+(today.getMonth()+1)+'/'+today.getFullYear();
//Chart
window.onload = function(){
//dataset
var data = {
labels: [" Akaun 1"," Akaun 2"],
datasets: [{
data: [1300.00, 895.75],
backgroundColor: ["#430092","#36A2EB"],
hoverBackgroundColor: ["#430092","#36A2EB"],
cutout: ["70%"],
}]
};
//init & config
var chart = new Chart(document.getElementById('myChart'), {
type: 'doughnut',
data: data,
options: {
responsive: true,
legend: { display: false},
labels: { font: {size: 12}}
}
});
const centerDoughnutPlugin = {
id: "annotateDoughnutCenter",
beforeDraw: (chart) => {
let width = chart.chartArea.left + chart.chartArea.right;
let height = chart.chartArea.top + chart.chartArea.bottom;
let ctx = chart.ctx;
ctx.restore();
//Font setting
let fontSize = (height / 250).toFixed(2);
ctx.font = fontSize + "em sans-serif";
ctx.textBaseline = "middle";
ctx.textAlign = "center";
let text = "Balance as of: \n" + date;
//Center text
let textX = width / 2;
let textY = height / 2;
console.log("text x: ", textX);
console.log("text y: ", textY);
ctx.fillText(text, textX, textY);
ctx.save();
},
};
Chart.register(centerDoughnutPlugin);
}
<canvas id="myChart">
</canvas>
CanvasRenderingContext2D.fillText() draws text in a single line. If you need to break it into multiple lines, you'll need to split the text and invoke filltext() multiple times.
As an alternative you may use the Canvas-Txt library.
Please take a look at this answer https://stackoverflow.com/a/54390661/2358409
I am creating a bar chart using Chart.JS. Working fine:
https://jsfiddle.net/uzat4y0c/
Now, I would like to add value labels to indicate the size of the individual bars. I did this with this code:
animation: {
onComplete: function(animation) {
var ctx = this.chart.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontFamily, 'normal', Chart.defaults.global.defaultFontFamily);
ctx.fillStyle = "grey";
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
var dataset = this.data.datasets[1];
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 - 1);
} //for key
} //for i
} //onComplete
} //animation
Working fine, as well:
https://jsfiddle.net/uzat4y0c/1/
However, when I hover over the bar elements, my value labels will disappear for a moment while the animation updates, as you test in above fiddle.
In trying to improve this, I am seeking to find the piece of code which hides the numbers in the first place upon my mouseover. This is where I get stuck. I used dev tools and set an Event Listener breakpoint on "request animation frame", which eventually brought me to this piece of the Chart.JS code:
requestAnimationFrame: function() {
var me = this;
if (me.request === null) {
// Skip animation frame requests until the active one is executed.
// This can happen when processing mouse events, e.g. 'mousemove'
// and 'mouseout' events will trigger multiple renders.
me.request = helpers.requestAnimFrame.call(window, function() {
me.request = null;
me.startDigest();
});
}
},
After I first get here, I can loop through the "me.request = " part four times, then the value labels will disappear on stepping over the final curly closing brace.
Somehow I am missing where exactly is which command which "hides" my labels, or (more likely) repaints the canvas without labels.
How to get to the core of this by properly debugging? Thanks for reading!
Better draw the labels using a plugin, instead of drawing on animation compete (this is the culprit).
plugins: [{
animationCompleted: false,
afterDatasetsDraw: function(chart, ease) {
if (!this.animationCompleted && ease !== 1) return;
this.animationCompleted = true;
var ctx = chart.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontFamily, 'normal', Chart.defaults.global.defaultFontFamily);
ctx.fillStyle = "grey";
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
var dataset = chart.data.datasets[1];
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 - 1);
} //for key
} //for i
}
}]
* add this followed by your chart options.
ᴡᴏʀᴋɪɴɢ ᴇxᴀᴍᴘʟᴇ
var ctx = document.getElementById("testChart").getContext("2d");
// ctx.canvas.width = 300;
// ctx.canvas.height = 300;
var data = {
labels: ["uno", "dos", "tres", "quattro"],
datasets: [{
label: "Invisible",
data: [0, 20, 60, 0],
backgroundColor: "transparent",
}, {
label: "Dollar",
data: [20, 40, 30, 90],
backgroundColor: "lightgreen",
}, ] //datasets
};
var options = {
responsive: true,
scales: {
xAxes: [{
display: true,
stacked: true,
}, ], //xAxes
yAxes: [{
display: true,
stacked: true,
ticks: {
beginAtZero: true,
min: 0,
max: 120,
} //ticks
}, ] //yAxes
}, //scales
title: {
display: true,
text: "Waterfall chart",
},
legend: {
display: false,
labels: {
boxWidth: 80,
fontColor: 'green'
}
},
tooltips: {
enabled: false
}, //tooltips
}; //options
var myBarChart = new Chart(ctx, {
type: 'bar',
data: data,
options: options,
plugins: [{
animationCompleted: false,
afterDatasetsDraw: function(chart, ease) {
if (!this.animationCompleted && ease !== 1) return;
this.animationCompleted = true;
var ctx = chart.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontFamily, 'normal', Chart.defaults.global.defaultFontFamily);
ctx.fillStyle = "grey";
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
var dataset = chart.data.datasets[1];
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 - 1);
} //for key
} //for i
}
}]
});
p,
canvas {
border: 1px solid red;
}
#canvasWrapper {
border: 1px solid green;
padding: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.js"></script>
<p>Lorem</p>
<p>ipsum</p>
<div id="canvasWrapper">
<canvas id="testChart"></canvas>
</div>
<p>Lorem</p>
<p>ipsum</p>
Refer here to learn more about ChartJS Plugins.
I'm pretty new in ChartJS and I'm having a horizontal bar chart:
HTML
<canvas id="mybarChart"></canvas>
JavaScript:
var ctx = document.getElementById("mybarChart");
ctx.height = 300;
var mybarChart = new Chart(ctx, {
type: 'horizontalBar',
responsive: true,
data: data,
options: {
legend: {
display: false
},
scales: {
yAxes: [{
display: false,
ticks: {
beginAtZero: true
},
gridLines: {
color: "rgba(0, 0, 0, 0)",
}
}],
xAxes: [{
display: false,
gridLines: {
color: "rgba(0, 0, 0, 0)",
},
barPercentage: 0.5,
categoryPercentage: 0.5
}]
}
}
});
for which I'm trying to add the legend on each bar like but right now it looks like
I've tried adding
onAnimationComplete: function () {
var ctx = this.chart.ctx;
ctx.font = this.scale.font;
ctx.fillStyle = this.scale.textColor
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
this.datasets.forEach(function (dataset) {
dataset.points.forEach(function (points) {
ctx.fillText(points.value, points.x, points.y - 10);
});
})
}
but still the same result. What am I doing wrong?
I found something here but the labels displayed on each bar are the ticks for Y axes.
Is possible to add the legend on each bar and also keep the tooltip?
Thanks in advance!
There are actually several ways that you can achieve this. For simplicity, I will just modify the example that you provided.
Keep in mind that this puts the label inside the bar. You can easily modify this to place it outside, but you will have to add logic to make sure you don't overflow on the top of the chart or into other bars (not very simple logic).
Also, this approach requires that you have configured a label for each dataset (which is needed to drive the regular legend anyway).
Just put this in your animation.onComplete property.
function() {
var ctx = this.chart.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontFamily, 'normal', Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'left';
ctx.textBaseline = 'bottom';
this.data.datasets.forEach(function(dataset) {
for (var i = 0; i < dataset.data.length; i++) {
var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model,
scale_max = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._yScale.maxHeight;
left = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._xScale.left;
offset = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._xScale.longestLabelWidth;
ctx.fillStyle = '#444';
var y_pos = model.y - 5;
var label = model.label;
// Make sure data value does not get overflown and hidden
// when the bar's value is too close to max value of scale
// Note: The y value is reverse, it counts from top down
if ((scale_max - model.y) / scale_max >= 0.93)
y_pos = model.y + 20;
// ctx.fillText(dataset.data[i], model.x, y_pos);
if (dataset.data[i] > 0) {
ctx.fillText(dataset.label, left + 10, model.y + 8);
}
}
});
}
Here is a jsfiddle example (forked from the example you provided).
As Charts.js does not yet support annotations, I have added annotations of the data points after the chart is drawn. using ctx.fillText as shown below.
animation: {
animateScale: true,
animateRotate: true,
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.fillStyle = this.chart.config.options.defaultFontColor;
ctx.textBaseline = 'bottom';
this.data.datasets.forEach(function (dataset, i) {
var meta = chartInstance.controller.getDatasetMeta(i);
meta.data.forEach(function (bar, index) {
data = dataset.data[index];
ctx.fillText(data, bar._model.x, bar._model.y - 5);
});
});
}
}
This works great, other than the fact that now the tooltip is shown below the newly added text. This is not that obvious, however sometimes it overlaps in a bad place meaning that you cannot see the tooltip behind.
Is there a way to set the z-index of the ctx.fillText or tooltip so I can layer them correctly?
#user3284707 Actually what you have to do is draw the numbers on top of your bars before the tooltips, you are drawing them onComplete, putting them on top of everything.
I draw those numbers using:
Chart.plugins.register({
beforeDraw: function(chartInstance) {
if (chartInstance.config.options.showDatapoints) {
var helpers = Chart.helpers;
var ctx = chartInstance.chart.ctx;
var fontColor = helpers.getValueOrDefault(chartInstance.config.options.showDatapoints.fontColor, chartInstance.config.options.defaultFontColor);
// render the value of the chart above the bar
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, 'normal', Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.fillStyle = fontColor;
chartInstance.data.datasets.forEach(function (dataset) {
for (var i = 0; i < dataset.data.length; i++) {
var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model;
var scaleMax = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._yScale.maxHeight;
var yPos = (scaleMax - model.y) / scaleMax >= 0.93 ? model.y + 20 : model.y - 5;
var label = dataset.data[i] || '';
ctx.fillText(label.toLocaleString(), model.x, yPos);
}
});
}
}
});
Notice the beforeDraw there.
Hope this helps 3 years later, I spent the last 30 minutes trying to fix this 🤣
If you are looking for the close solution of the code written in the question then here is the code:
let ctx2 = document.getElementById("barChart").getContext("2d");
let chart = new Chart(ctx2, {
type: 'bar',
data: {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label: 'Months',
data: ['20','30','10','15','50','35','25'],
backgroundColor: 'rgba(26,179,148,0.5)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
},
options: {
legend: {
display: false
},
responsive: true,
maintainAspectRatio: true,
legendCallback: function(chart) {
var text = [];
for (var i=0; i<chart.data.datasets.length; i++) {
text.push(chart.data.labels[i]);
}
return text.join("");
},
tooltips: {
mode: 'index',
callbacks: {
// Use the footer callback to display the sum of the items showing in the tooltip
title: function(tooltipItem, data) {
let title_str = data['labels'][tooltipItem[0]['index']];
let lastIndex = title_str.lastIndexOf(" ");
return title_str.substring(0, lastIndex);
},
label: function(tooltipItem, data) {
return 'val: '+data['datasets'][0]['data'][tooltipItem['index']];
},
},
},
scales: {
xAxes: [{
stacked: false,
beginAtZero: true,
// scaleLabel: {
// labelString: 'Month'
// },
ticks: {
min: 0,
autoSkip: false,
maxRotation: 60,
callback: function(label, index, labels) {
return label;
}
}
}]
}
},
plugins:[{
afterDatasetsDraw: function(chart,options) {
// var chartInstance = chart,
let ctx = chart.ctx;
ctx.font = Chart.defaults.global.defaultFontStyle;
ctx.fillStyle = Chart.defaults.global.textColor;
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
chart.data.datasets.forEach(function (dataset, i) {
var meta = chart.controller.getDatasetMeta(i);
meta.data.forEach(function (bar, index) {
ctx.fillText(Math.round(dataset.data[index]), bar._model.x, bar._model.y - 5);
});
})
}
}]
});
document.getElementById('barChart').innerHTML = chart.generateLegend();
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
<canvas id="barChart" height="140"></canvas>
</div>
Here, I have made use of plugin afterDatasetDraw. https://www.chartjs.org/docs/latest/developers/plugins.html?h=afterdatasetsdraw
For anyone interested I managed to figure this out, I ended up looking at the tooltip drawing functions within charts.js and using a modified version of this as a custom tooltip, thus drawing the tooltip after the annotations are added.
First add this to your opptions
config = {
options: {
tooltips: {
enabled: false,
custom: customTooltips
}
This then calls the custom tooltip function below.
var currentX = null;
var currentY = null;
var customTooltips = function (tooltip) {
var helpers = Chart.helpers;
var ctx = this._chart.ctx;
var vm = this._view;
if (vm == null || ctx == null || helpers == null || vm.opacity === 0) {
return;
}
var tooltipSize = this.getTooltipSize(vm);
var pt = {
x: vm.x,
y: vm.y
};
if (currentX == vm.x && currentY == vm.y) {
return;
}
currentX = vm.x;
currentY = vm.y;
// IE11/Edge does not like very small opacities, so snap to 0
var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
// Draw Background
var bgColor = helpers.color(vm.backgroundColor);
ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString();
helpers.drawRoundedRectangle(ctx, pt.x, pt.y, tooltipSize.width, tooltipSize.height, vm.cornerRadius);
ctx.fill();
// Draw Caret
this.drawCaret(pt, tooltipSize, opacity);
// Draw Title, Body, and Footer
pt.x += vm.xPadding;
pt.y += vm.yPadding;
// Titles
this.drawTitle(pt, vm, ctx, opacity);
// Body
this.drawBody(pt, vm, ctx, opacity);
// Footer
this.drawFooter(pt, vm, ctx, opacity);
};
If anyone would look for the solution for this issue, there is an easier way of achieving what OP needed.
Instead of drawing in onComplete callback, draw it in afterDatasetsDraw callback. It's being called just before the tooltip gets drawn.
I've got a horizontal bar chart displaying like so:
As the second data value on the bars (1.0, 0.8, etc.) are partially obscured, I would like to do one of the following things, in order of preference:
Move them to the left, so that they are completely visible
Change their font from white to back, so that they are completely visible
Remove them altogether, so that they are completely invisible
The code that is causing them to be written in the first (second?) place is this:
Chart.pluginService.register({
afterDraw: function (chartInstance) {
if (chartInstance.id !== 1) return; // affect this one only
var ctx = chartInstance.chart.ctx;
// render the value of the chart above the bar
ctx.font = Chart.helpers.fontString(14, 'bold',
Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
chartInstance.data.datasets.forEach(function (dataset) {
for (var i = 0; i < dataset.data.length; i++) {
var model = dataset._meta[Object.keys(dataset._me
[0]].data[i]._model;
ctx.fillText(dataset.data[i]
(Number.isInteger(dataset.data[i]) ? ".0" : "") + "%", ((model.x
model.base) / 2), model.y + (model.height / 3));
}
});
}
});
...but I don't see just where there I can manipulate those values as desired.
For context and full disclosure, here is all the code for the chart:
Chart.pluginService.register({
afterDraw: function (chartInstance) {
if (chartInstance.id !== 1) return; // affect this one only
var ctx = chartInstance.chart.ctx;
// render the value of the chart above the bar
ctx.font = Chart.helpers.fontString(14, 'bold'
Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
chartInstance.data.datasets.forEach(function (dataset) {
for (var i = 0; i < dataset.data.length; i++) {
var model = dataset._meta[Object.keys(dataset._met
[0]].data[i]._model;
ctx.fillText(dataset.data[i]
(Number.isInteger(dataset.data[i]) ? ".0" : "") + "%", ((model.x
model.base) / 2), model.y + (model.height / 3));
}
});
}
});
var ctxBarChart
$("#priceComplianceBarChart").get(0).getContext("2d");
var priceComplianceData = {
labels: [
"Bix Produce", "Capitol City", "Charlies Portland", "Cost
Fruit and Produce",
"Get Fresh Sales",
"Loffredo East", "Loffredo West", "Paragon", "Piazz
Produce"
],
datasets: [
{
label: "Price Compliant",
backgroundColor: "rgba(34,139,34,0.5)",
hoverBackgroundColor: "rgba(34,139,34,1)",
data: [99.0, 99.2, 99.4, 98.9, 99.1, 99.5, 99.6, 99.2, 99.7]
},
{
label: "Non-Compliant",
backgroundColor: "rgba(255, 0, 0, 0.5)",
hoverBackgroundColor: "rgba(255, 0, 0, 1)",
data: [1.0, 0.8, 0.6, 1.1, 0.9, 0.5, 0.4, 0.8, 0.3]
}
]
}
var priceComplianceOptions = {
scales: {
xAxes: [
{
stacked: true
}
],
yAxes: [
{
stacked: true
}
]
},
tooltips: {
enabled: false
}
};
var priceBarChart = new Chart(ctxBarChart,
{
type: 'horizontalBar',
data: priceComplianceData,
options: priceComplianceOptions
});
I am using Chart.js version 2.2.2
1st solution: Move to the left
In your plugin, set the context textAlign property to right if it is the second dataset:
chartInstance.data.datasets.forEach(function(dataset) {
for (var i = 0; i < dataset.data.length; i++) {
var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model;
// If it is the second dataset (red color) ..
if (dataset._meta[0].controller.index== 1) {
// .. align to the right
ctx.textAlign ="right";
// .. and write it at the right bound of the chart
ctx.fillText(parseFloat(dataset.data[i]).toFixed(2) + "%", (model.x - 2), (model.y + model.height / 3));
// This looks like it has been moved a bit to the left
}
// Else ..
else {
// .. write as usual
ctx.fillText(parseFloat(dataset.data[i]).toFixed(2) + "%", ((model.base + model.x) / 2), (model.y + model.height / 3));
}
}
});
Check the result on this jsFiddle.
2nd solution: Put the text in black
In your plugin, set the context fillStyle property to the color you want (#000 for instance):
afterDraw: function(chartInstance) {
var ctx = chartInstance.chart.ctx;
// render the value of the chart above the bar
ctx.font = Chart.helpers.fontString(14, 'bold',
Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
// Here:
ctx.fillStyle = "#000";
chartInstance.data.datasets.forEach(function(dataset) {
// ...
});
});
Check the result on this jsFiddle.
3rd solution: Remove it, pure and simple
Add a condition in your plugin to check which dataset you are currently working on:
chartInstance.data.datasets.forEach(function(dataset) {
for (var i = 0; i < dataset.data.length; i++) {
// If it is the second dataset (red color), we break out of the loop
if (dataset._meta[0].controller.index == 1) break;
var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model;
ctx.fillText(parseFloat(dataset.data[i]).toFixed(2) + "%", ((model.base + model.x) / 2), (model.y + model.height / 3));
}
});
Check the result on this jsFiddle.