javascriptjquerychart.jsdonut-chart

Donut with rounded edges in one direction and white border between the segements


Chart js Donut segment is not displayed in one direction. First and last segments are not working as expected enter image description here

Expectation : Need chart js donut rounded segments displayed in one direction with white border between the segments enter image description here

Code

    // Create a custom Doughnut type with rounded segments
    Chart.defaults.RoundedDoughnut = Chart.helpers.clone(Chart.defaults.doughnut);
    Chart.controllers.RoundedDoughnut = Chart.controllers.doughnut.extend({
      draw: function(ease) {
        var ctx = this.chart.ctx;
        var easingDecimal = ease || 1;
        var arcs = this.getMeta().data;
        var borderWidth = 20; // Width of the white border for space between segments
        Chart.helpers.each(arcs, function(arc, i) {
          var vm = arc._view;
          var startAngle = vm.startAngle;
          var endAngle = vm.endAngle;
          var radius = (vm.outerRadius + vm.innerRadius) / 2;
          var thickness = (vm.outerRadius - vm.innerRadius) / 2;
          ctx.save();
          ctx.translate(vm.x, vm.y);
          // Draw each arc segment with a white border to create spacing
          ctx.beginPath();
          ctx.arc(0, 0, radius, startAngle, endAngle);
          ctx.lineWidth = thickness * 2 + borderWidth; // Increase width to add border
          ctx.strokeStyle = '#FFFFFF'; // Set border color to white
          ctx.lineCap = 'round'; // Ensure all segments are rounded on both ends
          ctx.stroke();
          // Draw inner colored arc over the white border to make it look like a gap
          ctx.beginPath();
          ctx.arc(0, 0, radius, startAngle, endAngle);
          ctx.lineWidth = thickness * 2;
          ctx.strokeStyle = vm.backgroundColor; // Set segment color
          ctx.stroke();

          ctx.restore();
        });
      }
    });
    // Initialize the chart
    window.onload = function() {
      new Chart(document.getElementById('usersChart'), {
        type: 'RoundedDoughnut',
        data: {
          datasets: [{
            data: [10, 10, 10, 10, 10, 10, 10, 10], // Adjust data values for even segments
            backgroundColor: [
              '#5da4e7', '#8fbbe7', '#addbf0', '#4b8de7',
              '#4da466', '#8ec486', '#b3dba8', '#63b571'
            ],
            borderWidth: 0
          }]
        },
        options: {
          cutoutPercentage: 70,
          tooltips: {
            enabled: false
          } // Optional: Disable tooltips to prevent hover issues
        }
      });
    };
<canvas id="usersChart" width="400" height="400"></canvas>

Solution

  • You should specify the chart.js version you are using, in case it is not the latest, and it doesn't appear to be, since the code of your custom controller is not compatible with the latest v4 version of chart.js.

    In any case, the problem is conceptual, the idea of circular "deck of cards" with each card partially covered by the next, can't be implemented by drawing full cards, since this approach is bound to have one card fully visible and one card covered at both ends.

    An alternative would be to draw half-cards, and this would work, except in the case when the sizes of the sectors are very small - then the visible half-sector is too much and sectors will overlap incorrectly.

    The best solution seems to actually draw the overlapping part, using context.fill function, filling half circles, rather than context.stroke of very thick arcs with rounded ends.

    Here's the implementation of that idea, using latest chart.js v4 compatible controller; it should be easy to adapt it to older versions:

    class RoundedDoughnut extends Chart.controllers.doughnut {
        static id = "roundedDoughnut";
        draw(...args) {
            super.draw(...args);
    
            const ctx = this.chart.ctx;
            const arcs = this.getMeta().data;
            const borderWidth = 10;
            const borderColor = 'white';
    
            if(arcs.length === 0){
                return;
            }
    
            ctx.save();
    
            const arc = arcs[0];
            ctx.strokeStyle = borderColor;
            ctx.lineWidth = borderWidth;
            ctx.beginPath();
            ctx.arc(arc.x, arc.y, arc.innerRadius, 0, 2*Math.PI);
            ctx.stroke();
            ctx.beginPath();
            ctx.arc(arc.x, arc.y, arc.outerRadius, 0, 2*Math.PI);
            ctx.stroke();
    
            for(const arc of arcs){
                const startAngle = arc.startAngle,
                    radius = (arc.outerRadius + arc.innerRadius) / 2,
                    thickness = (arc.outerRadius - arc.innerRadius) / 2 - borderWidth;
                const x0 = arc.x + radius * Math.cos(startAngle),
                    y0 = arc.y + radius * Math.sin(startAngle);
                ctx.beginPath();
                ctx.arc(x0, y0, thickness, 0, 2*Math.PI);
                ctx.fillStyle = arc.options.backgroundColor;
                ctx.strokeStyle = arc.options.backgroundColor;
                ctx.lineWidth = borderWidth;
                ctx.fill();
                ctx.stroke();
    
                ctx.beginPath();
                ctx.arc(x0, y0, thickness + borderWidth, Math.PI + startAngle, 2*Math.PI + startAngle);
                ctx.strokeStyle = borderColor;
                ctx.stroke();
            }
            ctx.restore();
    
        }
    }
    
    Chart.registry.controllers.register(RoundedDoughnut)
    
    new Chart(document.getElementById('usersChart'), {
        type: 'roundedDoughnut',
        data: {
            datasets: [{
                data: [10, 10, 10, 10, 10, 10, 10, 10], // Adjust data values for even segments
                backgroundColor: [
                    '#5da4e7', '#8fbbe7', '#addbf0', '#4b8de7',
                    '#4da466', '#8ec486', '#b3dba8', '#63b571'
                ],
                borderWidth: 0
            }]
        },
        options: {
            cutout: "70%",
            layout:{
                padding: 10
            },
            plugins: {
                tooltip: {
                    enabled: false
                } // Optional: Disable tooltips to prevent hover issues
            }
        }
    });
    <div style="width:400px; height: 400px">
    <canvas id="usersChart"></canvas>
    </div>
    
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

    Also in a jsFiddle, with the features positioned at the other ends of the segments ("clockwise"), as requested.