5
votes

I'm using Chart.js and I'm trying to move the labels on my Pie chart outside of the pie area (see red X's):

enter image description here

This my code right now:

<div class="container" id="pieContainer">
    <h4 class="title">Title</h4>
    <center><canvas id="pie"></canvas></center>
</div>

<script>                                  
    var pieData = [
    {
        value: 39,
        color:"#335478",
        label: "Blue"
    },
    {
        value : 4,
        color : "#7f7f7f",
        label: "Grey"
    },
    {
        value : 57,
        color : "#99cb55",
        label: "Green"
    }
    ];

    var optionsPie = {
        responsive : true,
        tooltipEvents: [],
        showTooltips: true,
        onAnimationComplete: function() {
             this.showTooltip(this.segments, true);
        },
        tooltipTemplate: "<%= label %> - <%= value %>%"
    };
    new Chart(document.getElementById("pie").getContext("2d")).Pie(pieData, optionsPie);
</script>

I don't want to use legends and I couldn't find a built-in method to move labels. Is there a way to do that without changing chart.js? What's the best way to achieve my goal?

5
Does this answer your question? chart.js: Show labels outside pie chartggorlen

5 Answers

5
votes

Just extend the chart to do this. If your labels are static, it might be simpler to just change the tooltipPosition method instead.


Preview

enter image description here


Script

Chart.types.Pie.extend({
    name: "PieAlt",
    initialize: function(data){
        Chart.types.Pie.prototype.initialize.apply(this, arguments);

        var requiredSpace = 0;
        for (var i = 0; i < data.length; i++)
            requiredSpace = Math.max(ctx.measureText(Chart.helpers.template(this.options.tooltipTemplate, data[i])).width, requiredSpace);
        this.outerRadius -= (requiredSpace + 20);
    },
    draw: function(data){
        Chart.types.Pie.prototype.draw.apply(this, arguments);

        var self = this;
        ctx.save();
        ctx.font = Chart.helpers.fontString(self.options.scaleFontSize, self.options.scaleFontStyle, self.options.scaleFontFamily);
                ctx.textBaseline = "middle";
        self.segments.forEach(function (segment) {
            var outerEdge = Chart.Arc.prototype.tooltipPosition.apply({
                x: this.chart.width / 2,
                y: this.chart.height / 2,
                startAngle: segment.startAngle,
                endAngle: segment.endAngle,
                outerRadius: segment.outerRadius * 2 + 20,
                innerRadius: 0
            })

            var normalizedAngle = (segment.startAngle + segment.endAngle) / 2;
            while (normalizedAngle > 2 * Math.PI) {
                normalizedAngle -= (2 * Math.PI)
            }

            if (normalizedAngle < (Math.PI * 0.4) || (normalizedAngle > Math.PI * 1.5))
                ctx.textAlign = "start";
            else if (normalizedAngle > (Math.PI * 0.4) && (normalizedAngle < Math.PI * 0.6)) {
                outerEdge.y += 5;
                ctx.textAlign = "center";
            }
            else if (normalizedAngle > (Math.PI * 1.4) && (normalizedAngle < Math.PI * 1.6)) {
                outerEdge.y - 5;
                ctx.textAlign = "center";
            }
            else
                ctx.textAlign = "end";

            ctx.fillText(Chart.helpers.template(self.options.tooltipTemplate, segment), outerEdge.x, outerEdge.y);
        });

        ctx.restore();
    }
});

and then

new Chart(ctx).PieAlt(data, {
    showTooltips: false
});

Fiddle - http://jsfiddle.net/h8rggkhp/

2
votes

I recently ran into the same issue, and chartsjs-plugin-labels solved the issue for me.

Example:

import {Chart} from 'chartjs';
import 'chartjs-plugin-labels';

let options = {
  plugins: {
    labels: {
      position: 'outside',
      render: (args) => {
        return `${args.label}: ${args.value}%`;
      }
    }
  }
}

let data = {
  datasets: [
    {
      data: [39, 4, 57],
      labels: ['Blue', 'Gray', 'Green'],
    },
  ],
}

new Chart(ctx, {
  type: 'pie',
  data: data,
  options: options
}
1
votes

It looks like you control the x and y position of the tooltips:

var myPieChart = new Chart(ctx).Pie(data, {
    customTooltips: function(tooltip) {

        // tooltip will be false if tooltip is not visible or should be hidden
        if (!tooltip) {
            return;
        }

        // Otherwise, tooltip will be an object with all tooltip properties like:

        // tooltip.caretHeight
        // tooltip.caretPadding
        // tooltip.chart
        // tooltip.cornerRadius
        // tooltip.fillColor
        // tooltip.font...
        // tooltip.text
        // tooltip.x
        // tooltip.y
        // etc...

    };
});

take a look at http://www.chartjs.org/docs/#doughnut-pie-chart-chart-options

0
votes

If you want pointed lines with outlabels like this

enter image description here

then use chartjs-plugin-piechart-outlabels npm link

0
votes

I can't find an exact plugin but I make one.

      const data = {
        labels: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
        datasets: [
          {
            data: [1, 2, 3, 4, 5, 6],
            backgroundColor: [
              "#316065",
              "#1A7F89",
              "#2D9CA7",
              "#2D86A7",
              "#1167A7",
              "#142440",
            ],
            borderColor: [
              "#316065",
              "#1A7F89",
              "#2D9CA7",
              "#2D86A7",
              "#1167A7",
              "#142440",
            ],
          },
        ],
      };

      // pieLabelsLine plugin
      const pieLabelsLine = {
        id: "pieLabelsLine",
        afterDraw(chart) {
          const {
            ctx,
            chartArea: { width, height },
          } = chart;

          const cx = chart._metasets[0].data[0].x;
          const cy = chart._metasets[0].data[0].y;

          const sum = chart.data.datasets[0].data.reduce((a, b) => a + b, 0);

          chart.data.datasets.forEach((dataset, i) => {
            chart.getDatasetMeta(i).data.forEach((datapoint, index) => {
              const { x: a, y: b } = datapoint.tooltipPosition();

              const x = 2 * a - cx;
              const y = 2 * b - cy;

              // draw line
              const halfwidth = width / 2;
              const halfheight = height / 2;
              const xLine = x >= halfwidth ? x + 20 : x - 20;
              const yLine = y >= halfheight ? y + 20 : y - 20;

              const extraLine = x >= halfwidth ? 10 : -10;

              ctx.beginPath();
              ctx.moveTo(x, y);
              ctx.arc(x, y, 2, 0, 2 * Math.PI, true);
              ctx.fill();
              ctx.moveTo(x, y);
              ctx.lineTo(xLine, yLine);
              ctx.lineTo(xLine + extraLine, yLine);
              // ctx.strokeStyle = dataset.backgroundColor[index];
              ctx.strokeStyle = "black";
              ctx.stroke();

              // text
              const textWidth = ctx.measureText(chart.data.labels[index]).width;
              ctx.font = "12px Arial";
              // control the position
              const textXPosition = x >= halfwidth ? "left" : "right";
              const plusFivePx = x >= halfwidth ? 5 : -5;
              ctx.textAlign = textXPosition;
              ctx.textBaseline = "middle";
              // ctx.fillStyle = dataset.backgroundColor[index];
              ctx.fillStyle = "black";

              ctx.fillText(
                ((chart.data.datasets[0].data[index] * 100) / sum).toFixed(2) +
                  "%",
                xLine + extraLine + plusFivePx,
                yLine
              );
            });
          });
        },
      };
      // config
      const config = {
        type: "pie",
        data,
        options: {
          maintainAspectRatio: false,
          layout: {
            padding: 30,
          },
          scales: {
            y: {
              display: false,
              beginAtZero: true,
              ticks: {
                display: false,
              },
              grid: {
                display: false,
              },
            },
            x: {
              display: false,
              ticks: {
                display: false,
              },
              grid: {
                display: false,
              },
            },
          },
          plugins: {
            legend: {
              display: false,
            },
          },
        },
        plugins: [pieLabelsLine],
      };

      // render init block
      const myChart = new Chart(document.getElementById("myChart"), config);

https://codepen.io/BillDou/pen/oNoGBXb