[Chartjs]-How to make round segment divider in donut chart in chart.js

3👍

The rounded ends of the arcs in a doughnut or pie chart are covered by the borderRadius option.

If we choose the radius of that rounded end to be 15px we can write that as

    datasets: [{
        //..... data and other dataset options
        borderRadius: [
            {outerStart: 0, outerEnd: 15, innerStart: 0, innerEnd: 15},
            0
        ]
    }]

Obviously, to round the other end, one should set outerStart and innerStart values.
The second value of the borderRadius array (zero) corresponds to second value of the dataset, which stands as the background of the stripe.

Here’s a snippet with your data

const data = {
    datasets: [{
        label: 'Dataset 1',
        data: [50, 50],
        backgroundColor: [
            "#ff3333",
            "#660000",
        ],
        borderRadius: [
            {outerStart: 0, outerEnd: 15, innerStart: 0, innerEnd: 15},
            0
        ],
        borderWidth: 0
    },
    {
        label: 'Dataset 2',
        data: [70, 30],
        backgroundColor: [
            "#00ff00",
            "#003300",
        ],
        borderRadius: [
            {outerStart: 0, outerEnd: 15, innerStart: 0, innerEnd: 15},
            0
        ],
        borderWidth: 0
    },
    {
        label: 'Dataset 3',
        data: [40, 60],
        backgroundColor: [
            "#1991EB",
            "#001a4d",
        ],
        borderRadius: [
            {outerStart: 0, outerEnd: 15, innerStart: 0, innerEnd: 15},
            0
        ],
        borderWidth: 0,
    }]
};

const chart = new Chart(document.querySelector("#myChart"), {
    type: 'doughnut',
    data,
    options: {
        cutout: 60,
        responsive: true,
        radius: "80%"
    }
});
<div style="max-height:350px">
<canvas id="myChart" style="background-color: #000"></canvas>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.3.0/chart.umd.js"
        integrity="sha512-CMF3tQtjOoOJoOKlsS7/2loJlkyctwzSoDK/S40iAB+MqWSaf50uObGQSk5Ny/gfRhRCjNLvoxuCvdnERU4WGg=="
        crossorigin="anonymous" referrerpolicy="no-referrer"></script>

There are a few problems with this:

  • the background is not perfect, under the rounded ends there’s the canvas background, not the second color that’s supposed to e the stripe background
  • the second item, that should act as a background is also producing a tooltip on hover, that needs to be disabled
  • the radius of the rounded end has to be preset, and a resize of the canvas may get to inadequate values (although an update can be implemented).

To cover these cases, I implemented a custom DoughnutController, see the docs, called DoughnutWithRoundEnds, with id doughnut_round_ends, that extends the standard doughnut with three dataset options:

  • roundStart and roundEnd, (default value true for each), a boolean for each value (no need to set the border radius, it is computed automatically taking into account the current size of the arc when drawn)
  • overallBackgroundColor – the inactive background color of the stripe – which will allow to use just one value in combination with setting a circumference
const _drawArcElement = Chart.ArcElement.prototype.draw;
class DoughnutWithRoundEnds extends Chart.DoughnutController{
    static id = "doughnut_round_ends";

    updateElements(arcs, start, count, mode) {
        super.updateElements(arcs, start, count, mode);
        const dataset = this.getDataset();
        const overallBackgroundColor = dataset.overallBackgroundColor ??
            this.options.overallBackgroundColor;
        let roundEnd = this.getDataset().roundEnd ?? this.options.roundEnd ?? true;
        if(!Array.isArray(roundEnd)){
            roundEnd = Array(arcs.length).fill(roundEnd);
        }
        let roundStart = dataset.roundStart ?? this.options.roundStart ?? true;
        if(!Array.isArray(roundStart)){
            roundStart = Array(arcs.length).fill(roundStart);
        }
        if(overallBackgroundColor && roundEnd.length > 0){
            arcs.forEach((arc, i) => {
                let borderRadius = 0;
                if(roundEnd[i] || roundStart[i]){
                    const r = Math.ceil((arc.outerRadius - arc.innerRadius)/2),
                        rStart = roundStart[i] ? r : 0,
                        rEnd = roundEnd[i] ? r : 0;
                    borderRadius = {
                        outerStart: rStart,
                        outerEnd: rEnd,
                        innerStart: rStart,
                        innerEnd: rEnd
                    };
                }
                if(i === 0 && overallBackgroundColor){
                    arc.draw = function(ctx){
                        const backgroundColor = this.options.backgroundColor,
                            startAngle = this.startAngle,
                            endAngle = this.endAngle;
                        this.options.backgroundColor = overallBackgroundColor;
                        this.options.borderRadius = 0;
                        this.startAngle = 0;
                        this.endAngle = 2*Math.PI;
                        _drawArcElement.call(this, ctx);
                        this.options.backgroundColor = backgroundColor;
                        this.startAngle = startAngle;
                        this.endAngle = endAngle;
                        this.options.borderRadius = borderRadius;
                        _drawArcElement.call(this, ctx);
                    }
                }
                else{
                    this.options.borderRadius = borderRadius;
                }
            });
        }
    }
}

Chart.register(DoughnutWithRoundEnds);

const data = {
    datasets: [{
        label: 'Dataset 1',
        data: [50],
        circumference: [50/100*360],
        backgroundColor: [
            "#ff3333"
        ],
        //roundStart: [true], //default
        // roundEnd: [true], // default
        overallBackgroundColor: "#660000",
        borderWidth: 0
    },
    {
        label: 'Dataset 2',
        data: [70],
        circumference: [70/100*360],
        backgroundColor: [
            "#00ff00"
        ],
        overallBackgroundColor: "#003300",
        borderWidth: 0
    },
    {
        label: 'Dataset 3',
        data: [40],
        circumference: [40/100*360],
        backgroundColor: [
            "#1991EB",
        ],
        overallBackgroundColor: "#001a4d",
        borderWidth: 0
    }]
};

const chart = new Chart(document.querySelector("#myChart"), {
    type: 'doughnut_round_ends',
    data,
    options: {
        cutout: 60,
        responsive: true,
        radius: "80%",
        //roundStart: false // for all datasets
    }
});
<div style="max-height:350px">
<canvas id="myChart" style="background-color: #000"></canvas>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.3.0/chart.umd.js"
        integrity="sha512-CMF3tQtjOoOJoOKlsS7/2loJlkyctwzSoDK/S40iAB+MqWSaf50uObGQSk5Ny/gfRhRCjNLvoxuCvdnERU4WGg=="
        crossorigin="anonymous" referrerpolicy="no-referrer"></script>

This version also works with two (or more) values, as in the first case, if the second one is relevant and isn’t just for the background: jsFiddle.

Leave a comment