Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chart.js - Positioning Donut Label

I have a web page that is using Chart.js. In this page, I am rendering three donut charts. In the middle of each chart, I want to show the percentage of the donut that is filled. Currently, I have the following code, which can be seen in this Fiddle.

function setupChart(chartId, progress) {
  var canvas = document.getElementById(chartId);
  var context = canvas.getContext('2d');

  var remaining = 100 - progress;
  var data = {
    labels: [ 'Progress', '', ],
    datasets: [{
      data: [progress, remaining],
      backgroundColor: [
        '#8FF400',
        '#FFFFFF'
      ],
      borderColor: [
        '#8FF400',
        '#408A00'
      ],
      hoverBackgroundColor: [
        '#8FF400',
        '#FFFFFF'
      ]
    }]
  };

  var options = {
    responsive: true,
    maintainAspectRatio: false,
    scaleShowVerticalLines: false,

    cutoutPercentage: 80,
    legend: {
      display: false
    },
    animation: {
      onComplete: function () {
        var xCenter = (canvas.width / 2) - 20;
        var yCenter = canvas.height / 2 - 40;

        context.textAlign = 'center';
        context.textBaseline = 'middle';

        var progressLabel = data.datasets[0].data[0] + '%';
        context.font = '20px Helvetica';
        context.fillStyle = 'black';
        context.fillText(progressLabel, xCenter, yCenter);
      }
    }
  };

  Chart.defaults.global.tooltips.enabled = false;
  var chart = new Chart(context, {
    type: 'doughnut',
    data: data,
    options: options
  });
}

The chart "runs". But the problem is with the rendering of the label. The label is not vertically and horizontally centered within the middle of the donut. The position of the label changes based on the screen resolution too. You can see the label change position by resizing the frame that the charts are rendered in within the Fiddle.

My question is, how do I consistently position the percentage in the middle of the donut? My page is a responsive page so, having consistent positioning is important Yet, I'm not sure what else to do. I feel like the textAlign and textBaseline properties aren't working, or I'm misunderstanding their usage.

Thanks!

like image 765
user687554 Avatar asked Nov 30 '16 13:11

user687554


2 Answers

According to this answer, you could do it with a plugin in a further version. I don't know if you noticed, but if you increase the width and reduce it over and over again on your fiddle, the height of your canvas gets bigger and bigger (the chart goes further and further down).

On the version you are using, you could extend the doughnut controller like this:

http://jsfiddle.net/ivanchaer/cutkqkuz/1/

Chart.defaults.DoughnutTextInside = Chart.helpers.clone(Chart.defaults.doughnut);
Chart.controllers.DoughnutTextInside = Chart.controllers.doughnut.extend({
    draw: function(ease) {

        var ctx = this.chart.chart.ctx;
        var data = this.getDataset();

        var easingDecimal = ease || 1;
        Chart.helpers.each(this.getDataset().metaData, function(arc, index) {
            arc.transition(easingDecimal).draw();

            var vm = arc._view;
            var radius = (vm.outerRadius + vm.innerRadius) / 2;
            var thickness = (vm.outerRadius - vm.innerRadius) / 2;
            var angle = Math.PI - vm.endAngle - Math.PI / 2;

            ctx.save();
            ctx.fillStyle = vm.backgroundColor;

            ctx.font = "20px Verdana";
            ctx.textAlign = 'center';
            ctx.textBaseline = "middle";

            var text = data.data[0] + '%';
            ctx.fillStyle = '#000';
            ctx.fillText(text, $(ctx.canvas).width()/2, $(ctx.canvas).height()/2);
        });
        ctx.fillStyle = '#fff';
        ctx.fill();
        ctx.restore();

    }

});


function setupChart(chartId, progress) {
    var canvas = document.getElementById(chartId);
    var context = canvas.getContext('2d');
    var remaining = 100 - progress;

    var deliveredData = {
        labels: [
            "Value"
        ],
        datasets: [{
            data: [progress, remaining],
            backgroundColor: [
                '#8FF400',
                '#FFFFFF'
            ],
            borderColor: [
                '#8FF400',
                '#408A00'
            ],
            hoverBackgroundColor: [
                '#8FF400',
                '#FFFFFF'
            ]
        }]
    };

    var deliveredOpt = {
        cutoutPercentage: 88,

        animation: false,
        legend: {
            display: false
        },
        tooltips: {
            enabled: false
        }
    };

    var chart = new Chart(canvas, {
        type: 'DoughnutTextInside',
        data: deliveredData,
        options: deliveredOpt
    });

}

setupChart('standChart', 33);
setupChart('moveChart', 66);
setupChart('exerciseChart', 99);
like image 162
Ivan Chaer Avatar answered Oct 01 '22 06:10

Ivan Chaer


See this http://jsfiddle.net/uha5tseb/2/

It can be handled with getting width/height of each chart by this reference

  onComplete: function (event) {
    console.log(this.chart.height);
    var xCenter = this.chart.width/2;
    var yCenter = this.chart.height/2;

    context.textAlign = 'center';
    context.textBaseline = 'middle';

    var progressLabel = data.datasets[0].data[0] + '%';
    context.font = '20px Helvetica';
    context.fillStyle = 'black';
    context.fillText(progressLabel, xCenter, yCenter);
  }

Hope this solves your problem!

like image 27
Vinod Louis Avatar answered Oct 01 '22 05:10

Vinod Louis