How to render Text inside the doughnut chart, I am using ChartJs.
This question is related to
javascript
chart.js
Base on @rap-2-h answer,Here the code for using text on doughnut chart on Chart.js for using in dashboard like. It has dynamic font-size for responsive option.
HTML:
<div>text
<canvas id="chart-area" width="300" height="300" style="border:1px solid"/><div>
Script:
var doughnutData = [
{
value: 100,
color:"#F7464A",
highlight: "#FF5A5E",
label: "Red"
},
{
value: 50,
color: "#CCCCCC",
highlight: "#5AD3D1",
label: "Green"
}
];
$(document).ready(function(){
var ctx = $('#chart-area').get(0).getContext("2d");
var myDoughnut = new Chart(ctx).Doughnut(doughnutData,{
animation:true,
responsive: true,
showTooltips: false,
percentageInnerCutout : 70,
segmentShowStroke : false,
onAnimationComplete: function() {
var canvasWidthvar = $('#chart-area').width();
var canvasHeight = $('#chart-area').height();
//this constant base on canvasHeight / 2.8em
var constant = 114;
var fontsize = (canvasHeight/constant).toFixed(2);
ctx.font=fontsize +"em Verdana";
ctx.textBaseline="middle";
var total = 0;
$.each(doughnutData,function() {
total += parseInt(this.value,10);
});
var tpercentage = ((doughnutData[0].value/total)*100).toFixed(2)+"%";
var textWidth = ctx.measureText(tpercentage).width;
var txtPosx = Math.round((canvasWidthvar - textWidth)/2);
ctx.fillText(tpercentage, txtPosx, canvasHeight/2);
}
});
});
Here the sample code.try to resize the window. http://jsbin.com/wapono/13/edit
You can also paste mayankcpdixit's code in onAnimationComplete
option :
// ...
var myDoughnutChart = new Chart(ctx).Doughnut(data, {
onAnimationComplete: function() {
ctx.fillText(data[0].value + "%", 100 - 20, 100, 200);
}
});
Text will be shown after animation
This is also working at my end...
<div style="width: 100px; height: 100px; float: left; position: relative;">
<div
style="width: 100%; height: 40px; position: absolute; top: 50%; left: 0; margin-top: -20px; line-height:19px; text-align: center; z-index: 999999999999999">
99%<Br />
Total
</div>
<canvas id="chart-area" width="100" height="100" />
</div>
I'd avoid modifying the chart.js code to accomplish this, since it's pretty easy with regular CSS and HTML. Here's my solution:
HTML:
<canvas id="productChart1" width="170"></canvas>
<div class="donut-inner">
<h5>47 / 60 st</h5>
<span>(30 / 25 st)</span>
</div>
CSS:
.donut-inner {
margin-top: -100px;
margin-bottom: 100px;
}
.donut-inner h5 {
margin-bottom: 5px;
margin-top: 0;
}
.donut-inner span {
font-size: 12px;
}
The output looks like this:
None of the other answers resize the text based off the amount of text and the size of the doughnut. Here is a small script you can use to dynamically place any amount of text in the middle, and it will automatically resize it.
Example: http://jsfiddle.net/kdvuxbtj/
It will take any amount of text in the doughnut sized perfect for the doughnut. To avoid touching the edges you can set a side-padding as a percentage of the diameter of the inside of the circle. If you don't set it, it will default to 20. You also the color, the font, and the text. The plugin takes care of the rest.
The plugin code will start with a base font size of 30px. From there it will check the width of the text and compare it against the radius of the circle and resize it based off the circle/text width ratio.
It has a default minimum font size of 20px. If the text would exceed the bounds at the minimum font size, it will wrap the text. The default line height when wrapping the text is 25px, but you can change it. If you set the default minimum font size to false, the text will become infinitely small and will not wrap.
It also has a default max font size of 75px in case there is not enough text and the lettering would be too big.
This is the plugin code
Chart.pluginService.register({
beforeDraw: function(chart) {
if (chart.config.options.elements.center) {
// Get ctx from string
var ctx = chart.chart.ctx;
// Get options from the center object in options
var centerConfig = chart.config.options.elements.center;
var fontStyle = centerConfig.fontStyle || 'Arial';
var txt = centerConfig.text;
var color = centerConfig.color || '#000';
var maxFontSize = centerConfig.maxFontSize || 75;
var sidePadding = centerConfig.sidePadding || 20;
var sidePaddingCalculated = (sidePadding / 100) * (chart.innerRadius * 2)
// Start with a base font of 30px
ctx.font = "30px " + fontStyle;
// Get the width of the string and also the width of the element minus 10 to give it 5px side padding
var stringWidth = ctx.measureText(txt).width;
var elementWidth = (chart.innerRadius * 2) - sidePaddingCalculated;
// Find out how much the font can grow in width.
var widthRatio = elementWidth / stringWidth;
var newFontSize = Math.floor(30 * widthRatio);
var elementHeight = (chart.innerRadius * 2);
// Pick a new font size so it will not be larger than the height of label.
var fontSizeToUse = Math.min(newFontSize, elementHeight, maxFontSize);
var minFontSize = centerConfig.minFontSize;
var lineHeight = centerConfig.lineHeight || 25;
var wrapText = false;
if (minFontSize === undefined) {
minFontSize = 20;
}
if (minFontSize && fontSizeToUse < minFontSize) {
fontSizeToUse = minFontSize;
wrapText = true;
}
// Set font settings to draw it correctly.
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.font = fontSizeToUse + "px " + fontStyle;
ctx.fillStyle = color;
if (!wrapText) {
ctx.fillText(txt, centerX, centerY);
return;
}
var words = txt.split(' ');
var line = '';
var lines = [];
// Break words up into multiple lines if necessary
for (var n = 0; n < words.length; n++) {
var testLine = line + words[n] + ' ';
var metrics = ctx.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > elementWidth && n > 0) {
lines.push(line);
line = words[n] + ' ';
} else {
line = testLine;
}
}
// Move the center up depending on line height and number of lines
centerY -= (lines.length / 2) * lineHeight;
for (var n = 0; n < lines.length; n++) {
ctx.fillText(lines[n], centerX, centerY);
centerY += lineHeight;
}
//Draw text in center
ctx.fillText(line, centerX, centerY);
}
}
});
And you use the following options in your chart object
options: {
elements: {
center: {
text: 'Red is 2/3 the total numbers',
color: '#FF6384', // Default is #000000
fontStyle: 'Arial', // Default is Arial
sidePadding: 20, // Default is 20 (as a percentage)
minFontSize: 20, // Default is 20 (in px), set to false and text will not wrap.
lineHeight: 25 // Default is 25 (in px), used for when text wraps
}
}
}
Credit to @Jenna Sloan for help with the math used in this solution.
Here is cleaned up and combined example of above solutions - responsive (try to resize the window), supports animation self-aligning, supports tooltips
https://jsfiddle.net/cmyker/u6rr5moq/
Chart.types.Doughnut.extend({_x000D_
name: "DoughnutTextInside",_x000D_
showTooltip: function() {_x000D_
this.chart.ctx.save();_x000D_
Chart.types.Doughnut.prototype.showTooltip.apply(this, arguments);_x000D_
this.chart.ctx.restore();_x000D_
},_x000D_
draw: function() {_x000D_
Chart.types.Doughnut.prototype.draw.apply(this, arguments);_x000D_
_x000D_
var width = this.chart.width,_x000D_
height = this.chart.height;_x000D_
_x000D_
var fontSize = (height / 114).toFixed(2);_x000D_
this.chart.ctx.font = fontSize + "em Verdana";_x000D_
this.chart.ctx.textBaseline = "middle";_x000D_
_x000D_
var text = "82%",_x000D_
textX = Math.round((width - this.chart.ctx.measureText(text).width) / 2),_x000D_
textY = height / 2;_x000D_
_x000D_
this.chart.ctx.fillText(text, textX, textY);_x000D_
}_x000D_
});_x000D_
_x000D_
var data = [{_x000D_
value: 30,_x000D_
color: "#F7464A"_x000D_
}, {_x000D_
value: 50,_x000D_
color: "#E2EAE9"_x000D_
}, {_x000D_
value: 100,_x000D_
color: "#D4CCC5"_x000D_
}, {_x000D_
value: 40,_x000D_
color: "#949FB1"_x000D_
}, {_x000D_
value: 120,_x000D_
color: "#4D5360"_x000D_
}];_x000D_
_x000D_
var DoughnutTextInsideChart = new Chart($('#myChart')[0].getContext('2d')).DoughnutTextInside(data, {_x000D_
responsive: true_x000D_
});
_x000D_
<html>_x000D_
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>_x000D_
<script src="//cdnjs.cloudflare.com/ajax/libs/Chart.js/1.0.2/Chart.min.js"></script>_x000D_
<body>_x000D_
<canvas id="myChart"></canvas>_x000D_
</body>_x000D_
</html>
_x000D_
UPDATE 17.06.16:
Same functionality but for chart.js version 2:
https://jsfiddle.net/cmyker/ooxdL2vj/
var data = {_x000D_
labels: [_x000D_
"Red",_x000D_
"Blue",_x000D_
"Yellow"_x000D_
],_x000D_
datasets: [_x000D_
{_x000D_
data: [300, 50, 100],_x000D_
backgroundColor: [_x000D_
"#FF6384",_x000D_
"#36A2EB",_x000D_
"#FFCE56"_x000D_
],_x000D_
hoverBackgroundColor: [_x000D_
"#FF6384",_x000D_
"#36A2EB",_x000D_
"#FFCE56"_x000D_
]_x000D_
}]_x000D_
};_x000D_
_x000D_
Chart.pluginService.register({_x000D_
beforeDraw: function(chart) {_x000D_
var width = chart.chart.width,_x000D_
height = chart.chart.height,_x000D_
ctx = chart.chart.ctx;_x000D_
_x000D_
ctx.restore();_x000D_
var fontSize = (height / 114).toFixed(2);_x000D_
ctx.font = fontSize + "em sans-serif";_x000D_
ctx.textBaseline = "middle";_x000D_
_x000D_
var text = "75%",_x000D_
textX = Math.round((width - ctx.measureText(text).width) / 2),_x000D_
textY = height / 2;_x000D_
_x000D_
ctx.fillText(text, textX, textY);_x000D_
ctx.save();_x000D_
}_x000D_
});_x000D_
_x000D_
var chart = new Chart(document.getElementById('myChart'), {_x000D_
type: 'doughnut',_x000D_
data: data,_x000D_
options: {_x000D_
responsive: true,_x000D_
legend: {_x000D_
display: false_x000D_
}_x000D_
}_x000D_
});
_x000D_
<script src="//cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.6/Chart.bundle.js"></script>_x000D_
<canvas id="myChart"></canvas>
_x000D_
You can use css with relative/absolute positioning if you want it responsive. Plus it can handle easily the multi-line.
https://jsfiddle.net/mgyp0jkk/
<div class="relative">
<canvas id="myChart"></canvas>
<div class="absolute-center text-center">
<p>Some text</p>
<p>Some text</p>
</div>
</div>
I create a demo with 7 jQueryUI Slider and ChartJs (with dynamic text inside)
Chart.types.Doughnut.extend({
name: "DoughnutTextInside",
showTooltip: function() {
this.chart.ctx.save();
Chart.types.Doughnut.prototype.showTooltip.apply(this, arguments);
this.chart.ctx.restore();
},
draw: function() {
Chart.types.Doughnut.prototype.draw.apply(this, arguments);
var width = this.chart.width,
height = this.chart.height;
var fontSize = (height / 140).toFixed(2);
this.chart.ctx.font = fontSize + "em Verdana";
this.chart.ctx.textBaseline = "middle";
var red = $( "#red" ).slider( "value" ),
green = $( "#green" ).slider( "value" ),
blue = $( "#blue" ).slider( "value" ),
yellow = $( "#yellow" ).slider( "value" ),
sienna = $( "#sienna" ).slider( "value" ),
gold = $( "#gold" ).slider( "value" ),
violet = $( "#violet" ).slider( "value" );
var text = (red+green+blue+yellow+sienna+gold+violet) + " minutes";
var textX = Math.round((width - this.chart.ctx.measureText(text).width) / 2);
var textY = height / 2;
this.chart.ctx.fillStyle = '#000000';
this.chart.ctx.fillText(text, textX, textY);
}
});
var ctx = $("#myChart").get(0).getContext("2d");
var myDoughnutChart = new Chart(ctx).DoughnutTextInside(data, {
responsive: false
});
Alesana's solution works very nicely for me in general, but like others, I wanted to be able to specify where line breaks occur. I made some simple modifications to wrap lines at '\n' characters, as long as the text is already being wrapped. A more complete solution would force wrapping if there are any '\n' characters in the text, but I don't have time at the moment to make that work with font sizing. The change also centers a little better horizontally when wrapping (avoids trailing spaces). The code's below (I can't post comments yet).
It would be cool if someone put this plug-in on GitHub...
Chart.pluginService.register({
beforeDraw: function(chart) {
if (chart.config.options.elements.center) {
// Get ctx from string
var ctx = chart.chart.ctx;
// Get options from the center object in options
var centerConfig = chart.config.options.elements.center;
var fontStyle = centerConfig.fontStyle || 'Arial';
var txt = centerConfig.text;
var color = centerConfig.color || '#000';
var maxFontSize = centerConfig.maxFontSize || 75;
var sidePadding = centerConfig.sidePadding || 20;
var sidePaddingCalculated = (sidePadding / 100) * (chart.innerRadius * 2)
// Start with a base font of 30px
ctx.font = "30px " + fontStyle;
// Get the width of the string and also the width of the element minus 10 to give it 5px side padding
var stringWidth = ctx.measureText(txt).width;
var elementWidth = (chart.innerRadius * 2) - sidePaddingCalculated;
// Find out how much the font can grow in width.
var widthRatio = elementWidth / stringWidth;
var newFontSize = Math.floor(30 * widthRatio);
var elementHeight = (chart.innerRadius * 2);
// Pick a new font size so it will not be larger than the height of label.
var fontSizeToUse = Math.min(newFontSize, elementHeight, maxFontSize);
var minFontSize = centerConfig.minFontSize;
var lineHeight = centerConfig.lineHeight || 25;
var wrapText = false;
if (minFontSize === undefined) {
minFontSize = 20;
}
if (minFontSize && fontSizeToUse < minFontSize) {
fontSizeToUse = minFontSize;
wrapText = true;
}
// Set font settings to draw it correctly.
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.font = fontSizeToUse + "px " + fontStyle;
ctx.fillStyle = color;
if (!wrapText) {
ctx.fillText(txt, centerX, centerY);
return;
}
var lines = [];
var chunks = txt.split('\n');
for (var m = 0; m < chunks.length; m++) {
var words = chunks[m].split(' ');
var line;
// Break words up into multiple lines if necessary
for (var n = 0; n < words.length; n++) {
var testLine = (n == 0) ? words[n] : line + ' ' + words[n];
var metrics = ctx.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > elementWidth && n > 0) {
lines.push(line);
line = words[n];
} else {
line = testLine;
}
}
lines.push(line);
}
// Move the center up depending on line height and number of lines
centerY -= ((lines.length-1) / 2) * lineHeight;
// All but last line
for (var n = 0; n < lines.length; n++) {
ctx.fillText(lines[n], centerX, centerY);
centerY += lineHeight;
}
}
}
});
@Cmyker, great solution for chart.js v2
One little enhancement: It makes sense to check for the appropriate canvas id, see the modified snippet below. Otherwise the text (i.e. 75%) is also rendered in middle of other chart types within the page.
Chart.pluginService.register({
beforeDraw: function(chart) {
if (chart.canvas.id === 'doghnutChart') {
let width = chart.chart.width,
height = chart.chart.outerRadius * 2,
ctx = chart.chart.ctx;
rewardImg.width = 40;
rewardImg.height = 40;
let imageX = Math.round((width - rewardImg.width) / 2),
imageY = (height - rewardImg.height ) / 2;
ctx.drawImage(rewardImg, imageX, imageY, 40, 40);
ctx.save();
}
}
});
Since a legend (see: http://www.chartjs.org/docs/latest/configuration/legend.html) magnifies the chart height, the value for height should be obtained by the radius.
This is based on Cmyker's update for Chart.js 2. (posted as another answer as I can't comment yet)
I had an issue with the text alignment on Chrome when the legend is displayed as the chart height does not include this so it's not aligned correctly in the middle. Fixed this by accounting for this in the calculation of fontSize and textY.
I calculated percentage inside the method rather than a set value as I have multiple of these on the page. Assumptions are that your chart only has 2 values (otherwise what is the percentage of? and that the first is the one you want to show the percentage for. I have a bunch of other charts too so I do a check for type = doughnut. I'm only using doughnuts to show percentages so it works for me.
Text color seems a bit hit and miss depending on what order things run in etc so I ran into an issue when resizing that the text would change color (between black and the primary color in one case, and secondary color and white in another) so I "save" whatever the existing fill style was, draw the text (in the color of the primary data) then restore the old fill style. (Preserving the old fill style doesn't seem needed but you never know.)
https://jsfiddle.net/g733tj8h/
Chart.pluginService.register({
beforeDraw: function(chart) {
var width = chart.chart.width,
height = chart.chart.height,
ctx = chart.chart.ctx,
type = chart.config.type;
if (type == 'doughnut')
{
var percent = Math.round((chart.config.data.datasets[0].data[0] * 100) /
(chart.config.data.datasets[0].data[0] +
chart.config.data.datasets[0].data[1]));
var oldFill = ctx.fillStyle;
var fontSize = ((height - chart.chartArea.top) / 100).toFixed(2);
ctx.restore();
ctx.font = fontSize + "em sans-serif";
ctx.textBaseline = "middle"
var text = percent + "%",
textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = (height + chart.chartArea.top) / 2;
ctx.fillStyle = chart.config.data.datasets[0].backgroundColor[0];
ctx.fillText(text, textX, textY);
ctx.fillStyle = oldFill;
ctx.save();
}
}
});
var data = {_x000D_
labels: ["Red","Blue"],_x000D_
datasets: [_x000D_
{_x000D_
data: [300, 50],_x000D_
backgroundColor: ["#FF6384","#36A2EB"],_x000D_
}]_x000D_
};_x000D_
_x000D_
Chart.pluginService.register({_x000D_
beforeDraw: function(chart) {_x000D_
var width = chart.chart.width,_x000D_
height = chart.chart.height,_x000D_
ctx = chart.chart.ctx,_x000D_
type = chart.config.type;_x000D_
_x000D_
if (type == 'doughnut')_x000D_
{_x000D_
var percent = Math.round((chart.config.data.datasets[0].data[0] * 100) /_x000D_
(chart.config.data.datasets[0].data[0] +_x000D_
chart.config.data.datasets[0].data[1]));_x000D_
var oldFill = ctx.fillStyle;_x000D_
var fontSize = ((height - chart.chartArea.top) / 100).toFixed(2);_x000D_
_x000D_
ctx.restore();_x000D_
ctx.font = fontSize + "em sans-serif";_x000D_
ctx.textBaseline = "middle"_x000D_
_x000D_
var text = percent + "%",_x000D_
textX = Math.round((width - ctx.measureText(text).width) / 2),_x000D_
textY = (height + chart.chartArea.top) / 2;_x000D_
_x000D_
ctx.fillStyle = chart.config.data.datasets[0].backgroundColor[0];_x000D_
ctx.fillText(text, textX, textY);_x000D_
ctx.fillStyle = oldFill;_x000D_
ctx.save();_x000D_
}_x000D_
}_x000D_
});_x000D_
_x000D_
var myChart = new Chart(document.getElementById('myChart'), {_x000D_
type: 'doughnut',_x000D_
data: data,_x000D_
options: {_x000D_
responsive: true,_x000D_
legend: {_x000D_
display: true_x000D_
}_x000D_
}_x000D_
});
_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.6/Chart.bundle.js"></script>_x000D_
<canvas id="myChart"></canvas>
_x000D_
First of all, kudos on choosing Chart.js! I'm using it on one of my current projects and I absolutely love it - it does the job perfectly.
Although labels/tooltips are not part of the library yet, you may want to take a look at these three pull requests:
And, as Cracker0dks mentioned, Chart.js uses canvas
for rendering so you may as well just implement your own tooltips by interacting with it directly.
Hope this helps.
@rap-2-h and @Ztuons Ch's answer doesn't allow for the showTooltips
option to be active, but what you can do is create and layer a second canvas
object behind the one rendering the chart.
The important part is the styling required in the divs and for the canvas object itself so that they render on top of each other.
var data = [_x000D_
{value : 100, color : 'rgba(226,151,093,1)', highlight : 'rgba(226,151,093,0.75)', label : "Sector 1"},_x000D_
{value : 100, color : 'rgba(214,113,088,1)', highlight : 'rgba(214,113,088,0.75)', label : "Sector 2"},_x000D_
{value : 100, color : 'rgba(202,097,096,1)', highlight : 'rgba(202,097,096,0.75)', label : "Sector 3"}_x000D_
]_x000D_
_x000D_
var options = { showTooltips : true };_x000D_
_x000D_
var total = 0;_x000D_
for (i = 0; i < data.length; i++) {_x000D_
total = total + data[i].value;_x000D_
}_x000D_
_x000D_
var chartCtx = $("#canvas").get(0).getContext("2d");_x000D_
var chart = new Chart(chartCtx).Doughnut(data, options);_x000D_
_x000D_
var textCtx = $("#text").get(0).getContext("2d");_x000D_
textCtx.textAlign = "center";_x000D_
textCtx.textBaseline = "middle";_x000D_
textCtx.font = "30px sans-serif";_x000D_
textCtx.fillText(total, 150, 150);
_x000D_
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>_x000D_
<script src="//cdnjs.cloudflare.com/ajax/libs/Chart.js/1.0.2/Chart.min.js"></script>_x000D_
<html>_x000D_
<body>_x000D_
<div style="position: relative; width:300px; height:300px;">_x000D_
<canvas id="text" _x000D_
style="z-index: 1; _x000D_
position: absolute;_x000D_
left: 0px; _x000D_
top: 0px;" _x000D_
height="300" _x000D_
width="300"></canvas>_x000D_
<canvas id="canvas" _x000D_
style="z-index: 2; _x000D_
position: absolute;_x000D_
left: 0px; _x000D_
top: 0px;" _x000D_
height="300" _x000D_
width="300"></canvas>_x000D_
</div>_x000D_
</body>_x000D_
</html>
_x000D_
Here's the jsfiddle: https://jsfiddle.net/68vxqyak/1/
Source: Stackoverflow.com