How to add text inside the doughnut chart using Chart.js?
Asked Answered
M

17

154

How to render Text inside the doughnut chart, I am using ChartJs.

Myriagram answered 7/1, 2014 at 8:14 Comment(0)
S
43

You have to modify the code like: in chart.Doughnut.defaults

labelFontFamily : "Arial",
labelFontStyle : "normal",
labelFontSize : 24,
labelFontColor : "#666"

and then in function drawPieSegments

ctx.fillText(data[0].value + "%", width/2 - 20, width/2, 200);

See this pull: https://github.com/nnnick/Chart.js/pull/35

here is a fiddle http://jsfiddle.net/mayankcpdixit/6xV78/ implementing the same.

Supernal answered 3/3, 2014 at 6:41 Comment(5)
Is there anyone else who cannot find this drawPieSegments function?Prosaism
Worth mentioning that it doesn't work with v2. Use included jsfiddle code for easy of useLaxity
How to add the % in a new line? is this supported?Philippi
Still this is the solution?Herbivorous
@ArunC.B - scroll down Ellery
S
247

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/

Doughnut With Dynamic Text in the Middle

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.

Sciatica answered 26/3, 2017 at 7:49 Comment(15)
Works great! Most other options break when the legend is on the right or left.Danikadanila
@Azimuth the text is set by text: 'Desktop', so change Desktop to what you want.Vraisemblance
Excellent answer. Best one actually. If you are using webpack, just import the Chart object and register this plugin on it.Hypno
Font size measurement doesn't work well for one symbol in text.Cutty
@Night5talker Yeah, adding weight isn't hard at all. You just have to modify the plugin a little bit. The two lines that contain ctx.font = ... would just have to be modified to include the weight. The font is set using a CSS compliant string, so it would be [font style][font weight][font size][font face]. For example, ctx.font = "italic bold 36px Arial". Here is more information about it.Sciatica
Hi, chart.js have added this feature or not ? if added then there is no option how to use this, OR how to use your solution with react.js ?Onassis
Possible to split the text onto multiple lines? My text has 6 words and its overflowing between the bounds of the cutoutDisconcert
@Alesana Awesome! Two changes I made: (1) Change to var words = `${txt}`.split(" ");, to prevent "txt.split is not a function" errors when the chart gets really tiny. The chart disappeared for me when this error occurred. (2) Use m or another variable for the second for loop instead of n to prevent webpack/lint "redeclaration" errors.Taejon
Is it possible to dynamically change the text? I am using Angular and to detect changes I must provide a whole new ChartOptions object, which triggers the chart animation as well. So it seems I cannot have the chart animation on data change if I want to dynamically refresh the text in the center. Any solution to this problem?Kumiss
How to make this work with react js component? I'm using component from this github.com/reactchartjs/react-chartjs-2Martinemartineau
@S.Ramjit Nope. I faked it by overlaying the doughnut chart with my own component/element that was positioned directly in the middle of it. This solution is not perfect though, cause the text will not dynamically scale and does not try to fit into the chart if the chart's size changes dynamically or the txt is too wide to fit.Kumiss
In order to get this working in ChartJs V3 you will need to update all references to chart.innerRadius with chart._metasets[chart._metasets.length-1].data[0].innerRadius and update the registration to Chart.register({id:'doughnut-centertext',beforeDraw: function(chart){ ... }}). Updated fiddle can be found here: jsfiddle.net/danieljoeblack/feq1tpgm/6Europa
What's the best way to get this to work with typescript? (specifically the updated version @DanielBlack linked)Quadroon
here is a version that compiles, I changed more code since I needed it to work with a specific type of doughnut chart (cutoffs), so its customized, but should gie you an idea how to get types to work with the above gist.github.com/elm-/d74de2284f65dc815b28f8933dcb829aStefansson
To add to @DanielBlack's comment: I also had to change chart.chart.ctx to chart.ctx. That seems to be the location of the CanvasRenderingContext2D, at least for my setup. chart.chart.ctx doesn't exist.Taejon
T
64

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({
    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 / 114).toFixed(2);
        this.chart.ctx.font = fontSize + "em Verdana";
        this.chart.ctx.textBaseline = "middle";

        var text = "82%",
            textX = Math.round((width - this.chart.ctx.measureText(text).width) / 2),
            textY = height / 2;

        this.chart.ctx.fillText(text, textX, textY);
    }
});

var data = [{
    value: 30,
    color: "#F7464A"
}, {
    value: 50,
    color: "#E2EAE9"
}, {
    value: 100,
    color: "#D4CCC5"
}, {
    value: 40,
    color: "#949FB1"
}, {
    value: 120,
    color: "#4D5360"
}];

var DoughnutTextInsideChart = new Chart($('#myChart')[0].getContext('2d')).DoughnutTextInside(data, {
    responsive: true
});
<html>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/Chart.js/1.0.2/Chart.min.js"></script>
<body>
    <canvas id="myChart"></canvas>
</body>
</html>

UPDATE 17.06.16:

Same functionality but for chart.js version 2:

https://jsfiddle.net/cmyker/ooxdL2vj/

var data = {
  labels: [
    "Red",
    "Blue",
    "Yellow"
  ],
  datasets: [
    {
      data: [300, 50, 100],
      backgroundColor: [
        "#FF6384",
        "#36A2EB",
        "#FFCE56"
      ],
      hoverBackgroundColor: [
        "#FF6384",
        "#36A2EB",
        "#FFCE56"
      ]
    }]
};

Chart.pluginService.register({
  beforeDraw: function(chart) {
    var width = chart.chart.width,
        height = chart.chart.height,
        ctx = chart.chart.ctx;

    ctx.restore();
    var fontSize = (height / 114).toFixed(2);
    ctx.font = fontSize + "em sans-serif";
    ctx.textBaseline = "middle";

    var text = "75%",
        textX = Math.round((width - ctx.measureText(text).width) / 2),
        textY = height / 2;

    ctx.fillText(text, textX, textY);
    ctx.save();
  }
});

var chart = new Chart(document.getElementById('myChart'), {
  type: 'doughnut',
  data: data,
  options: {
  	responsive: true,
    legend: {
      display: false
    }
  }
});
<script src="//cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.6/Chart.bundle.js"></script>
<canvas id="myChart"></canvas>
Transilluminate answered 22/1, 2016 at 13:5 Comment(17)
Im getting Uncaught TypeError: Cannot read property 'extend' of undefined any ideas?Raymund
Also, you can change the text color using ctx.fillStyle = 'black';Cynth
this keeps on writing over the text again and again if the chart is refreshed by providing new data paramsTosha
Have you figured out the solution for that?Transilluminate
@Transilluminate the CSS/HTML solution could help but that does not export the center text when chart is exported as a PNG.Any ideas to rectify this?This was a complete solution just it doesn't clear the center area when the chart is refreshed.Tosha
@Transilluminate I tried to write a blank string right before ctx.fillText(text, textX, textY); i.e ctx.fillText(" ", textX, textY); ctx.fillText(text, textX, textY); but that did not work outTosha
@Transilluminate also if there are more then one instances of charts on same page this updates the center of all of them.Tosha
This one you can workaround by checking if chart instance is the one you need toTransilluminate
Any idea if I can change the font?Hightower
and how can i change the color of that piece of text?Hightower
What about reusing the same chart instance?Pleach
The text is not centered if the legend is on the right.Danikadanila
The textY setting didn't work for me as I am using multiple datasets. Using the chart.chartArea for the text x & y from the accepted answer worked better.Pindling
any way to change the inner-circle background color?Bargeboard
Note that if you display a legend on top, left or right, this solution does not work.Smiley
This solution worked for me where others hadn't, thanks!Supplication
Good solution. As others have noted, it only works if the legend is hidden or not to the right or left. I had to modify mine because I had multiple charts. Using your code in the chart init with: "plugins: [{ beforeDraw: function(chart) { ... } }]" worked for me, though I'm sure a more elegant global plugin solution would be better.Polygnotus
O
45

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:

enter image description here

Onieonion answered 7/8, 2014 at 13:34 Comment(6)
Why not? Just add @media-queries to your classes.Onieonion
It doesnt work for me, im using a responsive way, so I canot use fixed values. :(Grope
@Grope have you tried using media queries for the various resolutions? Or modifying the css I wrote to use % instead of px?Onieonion
@Onieonion this was a nice solution but the central text does not get exported when the chart is downloaded as a PNG.Tosha
@Tosha valid point, didn't take that into account since the question didn't mention exporting at all.Onieonion
Elegant solution, software development is about that, since it`s so complicated working with chart, specially when the parameters are not compatible between versions ...Minnieminnnie
S
43

You have to modify the code like: in chart.Doughnut.defaults

labelFontFamily : "Arial",
labelFontStyle : "normal",
labelFontSize : 24,
labelFontColor : "#666"

and then in function drawPieSegments

ctx.fillText(data[0].value + "%", width/2 - 20, width/2, 200);

See this pull: https://github.com/nnnick/Chart.js/pull/35

here is a fiddle http://jsfiddle.net/mayankcpdixit/6xV78/ implementing the same.

Supernal answered 3/3, 2014 at 6:41 Comment(5)
Is there anyone else who cannot find this drawPieSegments function?Prosaism
Worth mentioning that it doesn't work with v2. Use included jsfiddle code for easy of useLaxity
How to add the % in a new line? is this supported?Philippi
Still this is the solution?Herbivorous
@ArunC.B - scroll down Ellery
C
39

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>

enter image description here

Courante answered 21/8, 2014 at 11:20 Comment(7)
I used your solution because it was the quickest one and it does its job. Thanks!Leif
@Amrik Singh this is a good solution indeed but the center text wont get exported if the chart is downloaded as a PNG image.Tosha
Nice solution, It doesn't need much of bothering.Heritor
you are my dude! I cant find solution in angularjs and never though doing like this. Amazing.Bug
lovely, quick and clean fixWeasel
A CLOSING DIV IS MISSINGAmetropia
Fastest and most functional solution so far.Nigro
C
29

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>
Chagall answered 25/5, 2018 at 10:13 Comment(8)
beautiful solution :)Sweeting
Super Work bro you made my daySheena
As of 2021 and using v3 of chart.js (beta version), this worked out of the box for meDownright
and I think it also works for mobile devices too (as it does not use any fixed values). At least using Google Chrome and mobile simulation workedDownright
This is great but technically it vertical aligns text based on the parent, not doughnut. So if you have a legend at the top, your text will be in the vertical middle of the doughnut and legend. So perfect if you don't have a legend.Cida
Easiest and working solutionFacsimile
This is, by far, the best proposed solutionTwain
This isn't responsive unfortunately.Clove
B
12

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

Beaner answered 24/9, 2014 at 10:48 Comment(3)
This should be the accepted answer in the case of a need for responsiveness (my case).Camass
If you have tooltips, the text disappears on hover.Prosaism
Error "jQuery.Deferred exception: Chart is not defined"Haggadah
E
8

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
});

DEMO IN JSFIDDLE

enter image description here

Eyelash answered 15/3, 2016 at 21:19 Comment(0)
V
8

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 = {
  labels: ["Red","Blue"],
  datasets: [
    {
      data: [300, 50],
      backgroundColor: ["#FF6384","#36A2EB"],
    }]
};

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 myChart = new Chart(document.getElementById('myChart'), {
  type: 'doughnut',
  data: data,
  options: {
  	responsive: true,
    legend: {
      display: true
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.6/Chart.bundle.js"></script>
<canvas id="myChart"></canvas>
Volute answered 21/7, 2016 at 12:17 Comment(0)
P
7

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

Pressure answered 10/7, 2014 at 8:37 Comment(3)
Good but the text disappears on hoverHonorary
Correct, text disappears on hover... :( Would be great it the text did not disappear, any idea how to do this? ThanksLeif
@Leif @maud may be by using the save methodTosha
S
4

@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 = [
    {value : 100, color : 'rgba(226,151,093,1)', highlight : 'rgba(226,151,093,0.75)', label : "Sector 1"},
    {value : 100, color : 'rgba(214,113,088,1)', highlight : 'rgba(214,113,088,0.75)', label : "Sector 2"},
    {value : 100, color : 'rgba(202,097,096,1)', highlight : 'rgba(202,097,096,0.75)', label : "Sector 3"}
]

var options = { showTooltips : true };
     
var total = 0;
for (i = 0; i < data.length; i++) {
     total = total + data[i].value;
}

var chartCtx = $("#canvas").get(0).getContext("2d");
var chart = new Chart(chartCtx).Doughnut(data, options);

var textCtx = $("#text").get(0).getContext("2d");
textCtx.textAlign = "center";
textCtx.textBaseline = "middle";
textCtx.font = "30px sans-serif";
textCtx.fillText(total, 150, 150);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/Chart.js/1.0.2/Chart.min.js"></script>
<html>
<body>
<div style="position: relative; width:300px; height:300px;">
    <canvas id="text" 
            style="z-index: 1; 
                   position: absolute;
                   left: 0px; 
                   top: 0px;" 
            height="300" 
            width="300"></canvas>
    <canvas id="canvas" 
            style="z-index: 2; 
                   position: absolute;
                   left: 0px; 
                   top: 0px;" 
            height="300" 
            width="300"></canvas>
</div>
</body>
</html>

Here's the jsfiddle: https://jsfiddle.net/68vxqyak/1/

Sampler answered 21/7, 2015 at 8:9 Comment(0)
L
3

The world has changed a bit since this question was asked. Thankfully, today we have flexbox, and something responsive can be achieved with the following few lines:

<!-- In your html/template files -->
<section class="container">
  <canvas id="chartJsChart"></canvas>      
  <p class="label">Some text</p>
</section>

<style>
.container {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
.label {
  position: absolute;
}
</style>

I hope it's helpful to someone else!

Lezlie answered 16/7, 2022 at 14:55 Comment(0)
R
3

This has been answered on Chart.js's Youtube channel without any 3rd party plugin.

https://www.youtube.com/watch?v=c2mzQKpd_DI

        const centerText = {
        id: 'centerText',
        beforeDatasetsDraw(chart, args, pluginOptions) {
            const { ctx, data } = chart;

            const text = data.labels[data.labels.length - 1];

            ctx.save();
            const x = chart.getDatasetMeta(0).data[0].x;
            const y = chart.getDatasetMeta(0).data[0].y;

            //ctx.fillRect(x - 10, y - 10, 20, 20);
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.font = 'bold 30px sans-serif';
            ctx.fillText(text, x, y);
        }
    }

    const data = {
        labels: [
            'Red',
            'Blue',
            'Yellow'
        ],
        datasets: [{
            label: 'My First Dataset',
            data: [300, 50, 100],
            backgroundColor: [
            'rgb(255, 99, 132)',
            'rgb(54, 162, 235)',
            'rgb(255, 205, 86)'
            ],
            borderWidth: 1,
            hoverOffset: 4,
        }]
    };

    //const myChart = new Chart(ctx, {
    myChart = new Chart(ctx, {
        type: 'doughnut',
        data: data,
        options: {
            title: {
                display: true,
                text: "World Wide Wine Production 2018"
            },
            cutout: '80%'
        },
        plugins: [centerText]           
    });
Rainarainah answered 10/7, 2023 at 10:56 Comment(0)
V
1

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;
      }
    }
  }
});
Valley answered 30/4, 2020 at 14:20 Comment(0)
D
0

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.

Demoniac answered 7/1, 2014 at 8:32 Comment(0)
G
0

I know the answer is old, but maybe someone will come in handy. The simplest way is to use of onAnimationProgress event.

Like this.

var myDoughnutChart = new Chart(ctx).Doughnut(data, {
    onAnimationProgress: function() {
        
      const dataset = this.data.datasets[0];
      const model = dataset._meta[Object.keys(dataset._meta)[0]]?.data[0]?._model;
      if (!model) { return; }
      // model.x and model.y is the center of the chart
      this.ctx.fillText('00%', model.x, model.y); 
    }
});
Gallicize answered 26/9, 2021 at 14:12 Comment(0)
R
0

If the chart has a fixed size, you can just put the text above the chart with absolute and relative. like this (Syntext React):

<div style={{
      position: "relative",
      width: "100px",
      height: "100px",
    }}
  >
     <div style={{
        position: "absolute",
        top: "22px",
        left: "30px",
          }}> Your text</div>
     <Doughnut
     ...
    />
</div>

enter image description here

Rogovy answered 24/7, 2023 at 10:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.