Chart.js Doughnut with rounded edges

23,781

Solution 1

I made some changes in the @potatopeeling snippet, I made compatibility with the newer (2.9.x) version of chart.js also fixed where the "startArc" should be rendered and the color from the previous segment to match this "startArc", so we can have more than 2 segments. This is the result:

enter image description here

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;
        Chart.helpers.each(arcs, function(arc, i) {
            arc.transition(easingDecimal).draw();

            var pArc   = arcs[i === 0 ? arcs.length - 1 : i - 1];
            var pColor = pArc._view.backgroundColor;

            var vm         = arc._view;
            var radius     = (vm.outerRadius + vm.innerRadius) / 2;
            var thickness  = (vm.outerRadius - vm.innerRadius) / 2;
            var startAngle = Math.PI - vm.startAngle - Math.PI / 2;
            var angle      = Math.PI - vm.endAngle - Math.PI / 2;

            ctx.save();
            ctx.translate(vm.x, vm.y);

            ctx.fillStyle = i === 0 ? vm.backgroundColor : pColor;
            ctx.beginPath();
            ctx.arc(radius * Math.sin(startAngle), radius * Math.cos(startAngle), thickness, 0, 2 * Math.PI);
            ctx.fill();

            ctx.fillStyle = vm.backgroundColor;
            ctx.beginPath();
            ctx.arc(radius * Math.sin(angle), radius * Math.cos(angle), thickness, 0, 2 * Math.PI);
            ctx.fill();

            ctx.restore();
        });
    }
});

window.onload = function() {
    new Chart(document.getElementById('usersChart'), {
        type   : 'RoundedDoughnut',
        data   : {
            datasets: [
                {
                    data           : [40, 20, 20, 20],
                    backgroundColor: [
                        '#e77099',
                        '#5da4e7',
                        '#8f75e7',
                        '#8fe768'
                    ],
                    borderWidth    : 0
                }]
        },
        options: {
            cutoutPercentage: 70
        }
    });
};
<script src="https://github.com/chartjs/Chart.js/releases/download/v2.9.3/Chart.min.js"></script>
<link rel="stylesheet" href="https://github.com/chartjs/Chart.js/releases/download/v2.9.3/Chart.min.css">
<div style="width: 200px; height: 200px;">
<canvas id="usersChart" width="1" height="1"></canvas>
</div>

Solution 2

You can extend the chart to do this


Preview

enter image description here


Script

Chart.defaults.RoundedDoughnut = Chart.helpers.clone(Chart.defaults.doughnut);
Chart.controllers.RoundedDoughnut = Chart.controllers.doughnut.extend({
    draw: function (ease) {
        var ctx = this.chart.chart.ctx;

        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.translate(vm.x, vm.y);
            ctx.beginPath();
            ctx.arc(radius * Math.sin(angle), radius * Math.cos(angle), thickness, 0, 2 * Math.PI);
            ctx.arc(radius * Math.sin(Math.PI), radius * Math.cos(Math.PI), thickness, 0, 2 * Math.PI);
            ctx.closePath();
            ctx.fill();
            ctx.restore();
        });
    },
});

and then

...
type: 'RoundedDoughnut',
...

Stack Snippet

Chart.defaults.RoundedDoughnut = Chart.helpers.clone(Chart.defaults.doughnut);
        Chart.controllers.RoundedDoughnut = Chart.controllers.doughnut.extend({
            draw: function (ease) {
            		var ctx = this.chart.chart.ctx;
                
                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.translate(vm.x, vm.y);
                    ctx.beginPath();
                    ctx.arc(radius * Math.sin(angle), radius * Math.cos(angle), thickness, 0, 2 * Math.PI);
                    ctx.arc(radius * Math.sin(Math.PI), radius * Math.cos(Math.PI), thickness, 0, 2 * Math.PI);
                    ctx.closePath();
                    ctx.fill();
                    ctx.restore();
                });
            },
        });

        var deliveredData = {
            labels: [
                "Value"
            ],
            datasets: [
                {
                    data: [85, 15],
                    backgroundColor: [
                        "#3ec556",
                        "rgba(0,0,0,0)"
                    ],
                    hoverBackgroundColor: [
                        "#3ec556",
                        "rgba(0,0,0,0)"
                    ],
                    borderWidth: [
                        0, 0
                    ]
                }]
        };

        var deliveredOpt = {
            cutoutPercentage: 88,
            animation: {
                animationRotate: true,
                duration: 2000
            },
            legend: {
                display: false
            },
            tooltips: {
                enabled: false
            }
        };

        var chart = new Chart($('#openedCanvas'), {
            type: 'RoundedDoughnut',
            data: deliveredData,
            options: deliveredOpt
        });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.0.1/Chart.bundle.min.js"></script>

<canvas id="openedCanvas" height="230" width="680"></canvas>
Share:
23,781
jpenna
Author by

jpenna

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Updated on July 14, 2022

Comments

  • jpenna
    jpenna almost 2 years

    I created a donut chart with Chart.js and I want it to have rounded edges at both ends. I want it to be like this:

    The way I want it

    But I have it like this, with sharp edges:

    I have this

    The best I found was this answer: How to put rounded corners on a Chart.js Bar chart, but it is for bar charts, and I have no clue of how to adapt it for doughnuts..

    Here is my code:

    HTML

    <div class="modal-div-canvas js-chart">
      <div class="chart-canvas">
         <canvas id="openedCanvas" width="1" height="1"></canvas>
            <div class="chart-background"></div>
                <span class="chart-unique-value">
                     <span class="js-count">
                        85
                     </span>
                     <span class="cuv-percent">%</span>
                </span>
            </div>
      </div>
    

    JS

    var deliveredData = {
            labels: [
                "Value"
            ],
            datasets: [
                {
                    data: [85, 15)],
                    backgroundColor: [
                        "#3ec556",
                        "rgba(0,0,0,0)"
                    ],
                    hoverBackgroundColor: [
                        "#3ec556",
                        "rgba(0,0,0,0)"
                    ],
                    borderWidth: [
                        0, 0
                    ]
                }]
        };
    
        var deliveredOpt = {
            cutoutPercentage: 88,
            animation: {
                animationRotate: true,
                duration: 2000
            },
            legend: {
                display: false
            },
            tooltips: {
                enabled: false
            }
        };
    
       var chart = new Chart($('#openedCanvas'), {
            type: 'doughnut',
            data: deliveredData,
            options: deliveredOpt
        });
    }};
    

    Someone know how to do this?

  • jpenna
    jpenna almost 8 years
    In fact, with value = 0, there is a green ball on the top of the graph, but it should show nothing. I solved it with a statement saying that if value == 0, then use 'doughnut', else 'RoundedDoughnut'. Again, thanks a lot!
  • potatopeelings
    potatopeelings almost 8 years
    Ah, I hadn't considered 0. Cheers!
  • Dmonix
    Dmonix almost 8 years
    This is great. I would need such effect for more datasets. Let's take basic example of [300, 50, 100] they use on documentation page. I get something like this take.ms/JRqgh, any idea @potatopeelings how to achieve that? My goal is something like this take.ms/thRIb
  • Dimitrios Filippou
    Dimitrios Filippou almost 6 years
    In typescript i get this error TS2339: Property 'helpers' does not exist on type 'typeof Chart'.
  • Megaroeny
    Megaroeny almost 6 years
    Does this solution still work for the current version of chart.js?
  • Gabriel Bueno
    Gabriel Bueno over 5 years
    This doesn't work in this new version. Does anyone knows how to achieve the same result?
  • Jordy van den Aardweg
    Jordy van den Aardweg over 5 years
    @GabrielBueno @jaminroe To get it working in the current version of chart.js: replace this.getDataset().metaData with this.getMeta().data
  • A Friend
    A Friend almost 4 years
    Really nice additions there, one thing you haven't fixed (which the old version suffered from too) is the arc/ball still appearing on charts where a data value might be 0. Apart from that, great work!
  • Preethi Rajaraman
    Preethi Rajaraman over 3 years
    i would like to plot based on the values of the datasets.. like green pink and blue has 2 each. would like only that to come. and not the purple. with the abv code i get blue as one dot .
  • Max Medina
    Max Medina over 3 years
    how would you add spacing between each one?
  • Vikhyath Maiya
    Vikhyath Maiya over 3 years
    any way we can make this work with borderWidth also ?
  • Vikhyath Maiya
    Vikhyath Maiya over 3 years
    any way we can make this work with borderWidth also ?
  • Mr.Toxy
    Mr.Toxy about 3 years
    As of chartjs 3.x you can simply do: new Chart({ type: 'doughnut', options: { borderRadius: 10 } ... })
  • Towkir
    Towkir almost 3 years
    any idea how to make this work with the latest v3 versions of chartjs ?
  • Towkir
    Towkir almost 3 years
    @AFriend simply replace Chart.helpers.each(arcs, function(arc, i) { with Chart.helpers.each(arcs.filter(item => item.$context.parsed > 0), function(arc, i) { to not render colors for 0 data
  • A Friend
    A Friend almost 3 years
    @Towkir Haven't had to test it but this looks great. Appreciate the help!