Related
I've created a fabric custom class "VectorPlaceholder" that is basically a group that contains a Rectangle and a Vector:
// Fabric.js custom vector EPS object
fabric.VectorPlaceholder = fabric.util.createClass(fabric.Group, {
async: true,
type: 'vector-placeholder',
lockUniScalingWithSkew: false,
noScaleCache: true,
initialize: function (options) {
boundsRectangle = new fabric.Rect({
strokeDashArray: [10,10],
originX: 'center',
originY: 'center',
stroke: '#000000',
strokeWidth: 1,
width: options.width || 300,
height: options.height || 300,
fill: 'rgba(0, 0, 0, 0)',
});
this.setControlsVisibility({
ml: false,
mb: false,
mr: false,
mt: false,
});
this.originX = 'center',
this.originY = 'center',
this.callSuper('initialize', [boundsRectangle], options);
},
setVector: function (vector) {
//We remove any EPS that was in that position
var EPSGroup = this;
EPSGroup.forEachObject(function (object) {
if (object && object.type != "rect") {
EPSGroup.remove(object);
}
});
var scale = 1;
var xOffset = EPSGroup.getScaledWidth() / 2;
var yOffset = EPSGroup.getScaledHeight() / 2;
if (vector.height > vector.width) {
scale = EPSGroup.getScaledHeight() / vector.height;
xOffset = xOffset - (EPSGroup.getScaledWidth() - vector.width * scale) / 2
}
else {
scale = EPSGroup.getScaledWidth() / vector.width;
yOffset = yOffset - (EPSGroup.getScaledHeight() - vector.height * scale) / 2
}
vector.left = EPSGroup.left - xOffset;
vector.top = EPSGroup.top - yOffset;
vector.set('scaleY', scale);
vector.set('scaleX', scale);
var angle = 0;
if (EPSGroup.get('angle')) {
angle = EPSGroup.get('angle');
vector.setAngle(angle);
}
EPSGroup.addWithUpdate(vector);
EPSGroup.setCoords();
},
});
The idea of this class is to have a placeholder where users can upload SVGs.
This is done by calling to fabric.loadSVGFromString and then passing the result to the function in my custom class (setVector)
fabric.loadSVGFromString(svgString, function(objects, options) {
// Group the SVG objects to make a single element
var a = fabric.util.groupSVGElements(objects, options);
var EPSGroup = new fabric.VectorPlaceholder({});
EPSGroup.setVector(a);
This works perfectly when I create my custom object and don't rotate it. As you can see the group controls are aligned with the dashed rectangle.
The problem is when I create an empty VectorPlaceholder and I rotate it manually. After the manual rotation, when setVector is called this is what happens:
I can't understand why the group controls ignore the rotation, what I'm doing wrong? How can I make the group controls render aligned with the rotated rectangle?
You need to set the angle after you make setVector method
http://jsfiddle.net/2segrwx0/1/
// Fabric.js custom vector EPS object
fabric.VectorPlaceholder = fabric.util.createClass(fabric.Group, {
async: true,
type: 'vector-placeholder',
lockUniScalingWithSkew: false,
noScaleCache: true,
initialize: function (options) {
boundsRectangle = new fabric.Rect({
strokeDashArray: [10,10],
originX: 'center',
originY: 'center',
stroke: '#000000',
strokeWidth: 1,
width: options.width || 300,
height: options.height || 300,
fill: 'rgba(0, 0, 0, 0)',
});
this.setControlsVisibility({
ml: false,
mb: false,
mr: false,
mt: false,
});
this.originX = 'center',
this.originY = 'center',
this.callSuper('initialize', [boundsRectangle], options);
},
setVector: function (vector) {
//We remove any EPS that was in that position
var EPSGroup = this;
EPSGroup.forEachObject(function (object) {
if (object && object.type != "rect") {
EPSGroup.remove(object);
}
});
var scale = 1;
var xOffset = EPSGroup.getScaledWidth() / 2;
var yOffset = EPSGroup.getScaledHeight() / 2;
if (vector.height > vector.width) {
scale = EPSGroup.getScaledHeight() / vector.height;
xOffset = xOffset - (EPSGroup.getScaledWidth() - vector.width * scale) / 2
}
else {
scale = EPSGroup.getScaledWidth() / vector.width;
yOffset = yOffset - (EPSGroup.getScaledHeight() - vector.height * scale) / 2
}
vector.left = EPSGroup.left - xOffset;
vector.top = EPSGroup.top - yOffset;
vector.set('scaleY', scale);
vector.set('scaleX', scale);
/*var angle = 0;
if (EPSGroup.get('angle')) {
angle = EPSGroup.get('angle');
vector.setAngle(angle);
} */
EPSGroup.addWithUpdate(vector);
EPSGroup.setCoords();
},
});
canvas = new fabric.Canvas('c', {
});
fabric.loadSVGFromString('<svg height="210" width="500"><polygon points="100,10 40,198 190,78 10,78 160,198" style="fill:lime;stroke:purple;stroke-width:5;fill-rule:nonzero;" /></svg>', function(objects, options) {
// Group the SVG objects to make a single element
var a = fabric.util.groupSVGElements(objects, options);
var EPSGroup = new fabric.VectorPlaceholder({});
EPSGroup.left=200;
EPSGroup.top=200;
EPSGroup.setVector(a);
EPSGroup.angle=45;
canvas.add(EPSGroup);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.5/fabric.js"></script>
<canvas id="c" width=500 height=300 ></canvas>
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).
I'm struggling with left & right margins on a donut chart I have created using Chart.js. More specifically, it seems impossible to reduce the seemingly 25% side margins on the donut charts. Does anyone know how I accomplish this or if it's even possible? I've been looking over all the github issues and documentation I can find, but it seems like nothing is working.
Github Issues I've found:
Issue-449
Issue-1266
Currently this is what I have
The issue is the large margins on the sides, I can't get them to go away no matter what properties I adjust
Dimension-wise I'd like both charts to take up 50% of the blue container's width and 75% of its height. I'm trying to use percentages for everything so it's responsive. If I use the responsive property in the chart.js config it keeps those side margins in tact. Ultimately, this is what I am trying to do roughly (the desired height isn't accurate in this example).
Right now I'm generating the charts like so
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.0/Chart.bundle.min.js"></script>
<script>
window.onload = function() {
Chart.pluginService.register({
afterUpdate: function (chart) {
if (chart.config.options.elements.center) {
var helpers = Chart.helpers;
var centerConfig = chart.config.options.elements.center;
var globalConfig = Chart.defaults.global;
var ctx = chart.chart.ctx;
var fontStyle = helpers.getValueOrDefault(centerConfig.fontStyle, globalConfig.defaultFontStyle);
var fontFamily = helpers.getValueOrDefault(centerConfig.fontFamily, globalConfig.defaultFontFamily);
if (centerConfig.fontSize)
var fontSize = centerConfig.fontSize;
// figure out the best font size, if one is not specified
else {
ctx.save();
var fontSize = helpers.getValueOrDefault(centerConfig.minFontSize, 1);
var maxFontSize = helpers.getValueOrDefault(centerConfig.maxFontSize, 256);
var maxText = helpers.getValueOrDefault(centerConfig.maxText, centerConfig.text);
do {
ctx.font = helpers.fontString(fontSize, fontStyle, fontFamily);
var textWidth = ctx.measureText(maxText).width;
// check if it fits, is within configured limits and that we are not simply toggling back and forth
if (textWidth < chart.innerRadius * 2 && fontSize < maxFontSize)
fontSize += 1;
else {
// reverse last step
fontSize -= 1;
break;
}
} while (true)
ctx.restore();
}
// save properties
chart.center = {
font: helpers.fontString(fontSize, fontStyle, fontFamily),
fillStyle: helpers.getValueOrDefault(centerConfig.fontColor, globalConfig.defaultFontColor)
};
}
},
afterDraw: function (chart) {
if (chart.center) {
var centerConfig = chart.config.options.elements.center;
var ctx = chart.chart.ctx;
ctx.save();
ctx.font = chart.center.font;
ctx.fillStyle = chart.center.fillStyle;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
var centerX = (chart.chartArea.left + chart.chartArea.right) / 2;
var centerY = (chart.chartArea.top + chart.chartArea.bottom) / 2;
ctx.fillText(centerConfig.text, centerX, centerY);
ctx.restore();
}
},
})
var config = {
type: 'doughnut',
data: {
labels: [
"Savings",
"Checking"
],
datasets: [{
data: [300, 50],
backgroundColor: [
"#FF6384",
"#36A2EB"
],
hoverBackgroundColor: [
"#FF6384",
"#36A2EB"
]
}]
},
options: {
responsive:false,
maintainAspectRatio:false,
title: {
fullWidth: false,
display: true,
text: 'Current Balance'
},
legend: {
position:'bottom',
labels: {
boxWidth:15
}
},
elements: {
center: {
// the longest text that could appear in the center
maxText: '$000000',
text: '$40,000',
fontColor: 'black',
fontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
fontStyle: 'normal',
minFontSize: 1,
maxFontSize: 256,
}
}
}
};
var ctx = document.getElementById("myDoughnutChart").getContext("2d");
var myDoughnutChart = new Chart(ctx, config);
var ctx2 = document.getElementById("myDoughnutChart2").getContext("2d");
var myDoughnutChart2 = new Chart(ctx2, config);
};
</script>
</head>
With the portion of the HTML looking like this
<div class="col left">
<div class="section side-sm" style="background-color:blue;">
</div>
<div class="section side-sm" style="background-color:black;">
</div>
<div class="section side-lg">
<div class="accountContainer" style="height:75%;overflow:hidden;">
<canvas id="myDoughnutChart" style="background-color:white;"></canvas>
</div>
<div class="expenseContainer" style="height:75%;overflow:hidden;">
<canvas id="myDoughnutChart2" style="background-color:white;"></canvas>
</div>
</div>
</div>
With the applicable CSS for that HTML looking like this
html, body {
margin: 0;
height: 100%;
font-family: 'Roboto', sans-serif;
}
/* Start: Column-Specific */
.col {
height:100%;
float:left;
}
.left, .right {
width:25%;
height:97%;
background-color:white;
}
/*Start: Section-Specific */
.section {
width:100%;
clear: both;
margin:auto;
border-radius: 10px;
display:table;
}
.col.left>.section, .col.right>.section {
width:97%;
}
.side-lg {
height:40%;
background-color:blue;
margin-top:1%;
}
Can anyone help me figure out how to get rid of those large margins and accomplish the look that I've described/shown? Is it possible?
I misread your question, I thought you need google charts. Anyway, there is potential solution for chart js. You will need to add width and height attributes. For example:
<canvas id="myDoughnutChart" width="250%" height="310%"></canvas>
Here is a link to JsFiddle with your code + updated width and height attributes for the first chart (second one is the same for comparison):
https://jsfiddle.net/cncdf4od/2/
there is actually a simple solution i found in one of the issues
aspectRatio: 1 in the chart options
https://github.com/chartjs/Chart.js/issues/449
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.