user687554
user687554

Reputation: 11131

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!

Upvotes: 7

Views: 9078

Answers (5)

Ivan Chaer
Ivan Chaer

Reputation: 7100

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

Upvotes: 4

Kalimah
Kalimah

Reputation: 11437

One way to resolve this is by adding an absolutely positioned element on top of the canvas.

You can add a div element after each canvas element and then set its style to:

.precentage {
  font-family: tahoma;
  font-size: 28px;
  left: 50%;
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
}

Here is a complete JSFiddle.

Upvotes: 0

Dmytro Vyprichenko
Dmytro Vyprichenko

Reputation: 4924

Im not familiar with chart.js, but as far as I understood from the documentation, they provide generateLegend method for your purposes. It returns a data from legendCallback that you can display on the page outside of canvas.

Container of the legend text can be aligned relatively to the canvas's wrapper.

HTML:

<div class="chart" data-legend="">
  <canvas id="standChart"></canvas>
</div>

JS:

var container = canvas.parentElement;
...
container.setAttribute('data-legend', chart.generateLegend());

Fiddle

Upvotes: 0

Dave K.
Dave K.

Reputation: 258

You set your variables incorrectly when you get xCenter and yCenter. Currently they don't get the middle of the canvas.

You have:

var xCenter = (canvas.width / 2) - 20;
var yCenter = canvas.height / 2 - 40;

It should be:

var xCenter = (canvas.width / 2);
var yCenter = (canvas.height / 2);

Upvotes: 3

Vinod Louis
Vinod Louis

Reputation: 4876

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!

Upvotes: 3

Related Questions