Is there a way to give a pie chart in chart.js a different color outer border than the border color between segments?

Viewed 379

I'm using chart.js to draw some pie charts on a webpage I'm building. I want the pies to look like this normally, with a visible border around the pie and between segments.

But occasionally, the underlying data throws datapoints with zero values into the mix, meaning the chart looks like this circle with a single line.

Whilst I can turn off the borderWidth property when there are datapoints that are zero values, that turns off the property for the whole pie, meaning they look like this.

So, is there some way to make it so that when there is only one datapoint with actual data, there is still a border around the pie itself?

Or perhaps a way where whenever there is only one datapoint with actual data, to set the border width property to 0 and then draw a circle onto of the pie in the canvas?

2 Answers

You can use a custom inline plugin for this in combination with a scriptable option for the borderWidth itself.

Plugin:

{
    id: 'circleAroundPieDoughnut',
    beforeDatasetsDraw: (chart, args, options) => {
      if (chart.data.datasets[0].data.length > 1) return;

      const {
        ctx,
        canvas
      } = chart;
      const {
        top,
        left,
        width,
        height
      } = chart.chartArea;
      const x = left + width / 2;
      const y = top + height / 2;
      const outerRadius = chart._metasets[0].data[0].outerRadius;

      ctx.beginPath()
      ctx.arc(x, y, outerRadius + ((options.width || 0) / 2), 0, 2 * Math.PI);
      ctx.lineWidth = options.width || 1
      ctx.strokeStyle = options.color || '#000';
      ctx.stroke();
    }
  }

Live Example:

var options = {
  type: 'doughnut',
  data: {
    labels: ["Red"],
    datasets: [{
      label: '# of Votes',
      data: [12],
      borderWidth: (ctx) => (ctx.chart.data.datasets[0].data.length === 1 ? 0 : 3),
      backgroundColor: ["Red"]
    }]
  },
  options: {
    radius: '98%',
    plugins: {
      legend: {
        display: false
      },
      circleAroundPieDoughnut: {
        width: 6, // Width of the border, defaults to 1
        color: 'blue' // Color of the border, defaults to black
      }
    }
  },
  plugins: [{
    id: 'circleAroundPieDoughnut',
    beforeDatasetsDraw: (chart, args, options) => {
      if (chart.data.datasets[0].data.length > 1) return;

      const {
        ctx,
        canvas
      } = chart;
      const {
        top,
        left,
        width,
        height
      } = chart.chartArea;
      const x = left + width / 2;
      const y = top + height / 2;
      const outerRadius = chart._metasets[0].data[0].outerRadius;

      ctx.beginPath()
      ctx.arc(x, y, outerRadius + ((options.width || 0) / 2), 0, 2 * Math.PI);
      ctx.lineWidth = options.width || 1
      ctx.strokeStyle = options.color || '#000';
      ctx.stroke();
    }
  }]
}

var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
  <canvas id="chartJSContainer" width="600" height="400"></canvas>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.3.0/chart.js"></script>
</body>

Fiddle: https://jsfiddle.net/Leelenaleee/us52em7f/18/

That's brilliant - I've implemented this, but seem to be getting a slightly boxy circle that extends beyond the bounds of the canvas in circumstances where the function draws the border where there's only one datapoint. Any ideas on how to fix?

    function makepie(a,b,c,d){
 if (b <= 0) {n = 'n'} else {n = 'y'}
  if (c <= 0) {o = 'n'} else {o = 'y'}
  if (d <= 0) {p = 'n'} else {p = 'y'}
  q = n + o + p
  if (q == 'nnn') {s = [a];r = ['Indianred'];u = ['Emails']}
  if (q == 'yyy') {s = [a, b, c, d];r = ['Indianred', 'salmon', 'pink', 'mistyrose'];u = ['Emails','Calls','Meetings','Working']}
  if (q == 'ynn') {s = [a, b];r = ['Indianred', 'salmon'];u = ['Emails','Calls']}
  if (q == 'nyn') {s = [a, c];r = ['Indianred', 'pink', ];u = ['Emails','Working']}
  if (q == 'nny') {s = [a, d];r = ['Indianred', 'mistyrose'];u = ['Emails','Working']}
  if (q == 'yyn') {s = [a, b, c];r = ['Indianred', 'salmon', 'pink'];u = ['Emails','Calls','Meetings']}
  if (q == 'yny') {s = [a, b, d];r = ['Indianred', 'salmon', 'mistyrose'];u = ['Emails','Calls','Working']}
  if (q == 'nyy') {s = [a, c, d];r = ['Indianred', 'pink', 'mistyrose'];u = ['Emails','Meetings','Working']}
  var options = {
    type: 'pie',
    data: {
      labels: u,
      datasets: [{
        data: s,
        borderColor: '#6f6f6f',
        borderWidth: (ctx) => (ctx.chart.data.datasets[0].data.length === 1 ? 0 : 3),
        backgroundColor: r
      }]
    },
    options: {
        hover: {mode: null},
        animation: {duration: (Math.floor(Math.random() * 30) + 20) * 100},
        legend: {display: false,labels: {display: false}},
        rotation: (Math.floor(Math.random() * 5)+1),
        tooltips: {enabled: false},
        plugins: {
        legend: {display: false},
        circleAroundPieDoughnut: {
          width: 3,
          color: '#6f6f6f'
        }
      }
    },
    plugins: [{
      id: 'circleAroundPieDoughnut',
      beforeDatasetsDraw: (chart, args, options) => {
        if (chart.data.datasets[0].data.length > 1) return;

        var {
          ctx,
          canvas
        } = chart;
        var {
          top,
          left,
          width,
          height
        } = chart.chartArea;
        var x = left + width / 2;
        var y = top + height / 2;
        var outerRadius = chart._metasets[0].data[0].outerRadius;

        ctx.beginPath()
        ctx.arc(x, y, outerRadius + ((options.width || 0) / 2), 0, 2 * Math.PI);
        ctx.lineWidth = options.width || 1
        ctx.strokeStyle = options.color || '#000';
        ctx.stroke();
      }
    }],

  }

  var ctx = document.getElementById('chartJSContainer').getContext('2d');
  new Chart(ctx, options);
}

Fiddle: https://jsfiddle.net/theboyler/mp016df2/17/

Related