Search code examples
user-interfacegraphchart.jsrounded-cornersdonut-chart

Chart.js Doughnut with rounded edges and text centered


I am trying to achieve rounded corners similar to this article here, but combined with text in the centre, so far i have the code below, but i am not sure how to combine both ideas

Any help would be appreciated!

the image looks like below, donut with text image:

and my code is as follows to produce the text inside the donut.

Chart.types.Doughnut.extend({
   name: "DoughnutTextInside",
   showTooltip: function() {
       this.chart.ctx.save();
       Chart.types.Doughnut.prototype.showTooltip.apply(this, arguments);
       this.chart.ctx.restore();
},
draw: function() {
    Chart.types.Doughnut.prototype.draw.apply(this, arguments);

    var width = this.chart.width,
        height = this.chart.height;

    var fontSize = (height / 114).toFixed(2);
    this.chart.ctx.font = fontSize + "em Lato";
    this.chart.ctx.textBaseline = "middle";

    var text = "40%",
        textX = Math.round((width - this.chart.ctx.measureText(text).width) / 2),
        textY = height / 2;

    this.chart.ctx.fillText(text, textX, textY);
   }
});

var data = [{
   label: "Wins %",
   value: 120,
   color: "#2ecc71"
}, {
   label: "Losses %",
   value: 240,
   color: "#dddddd"
}, {
   value: 0,
   color: "#888888"
}];

var DoughnutTextInsideChart = new Chart($('#myChart')  [0].getContext('2d')).DoughnutTextInside(data, {
   responsive: true,
   segmentShowStroke: false,
   animationEasing: "easeInOutQuint",
});

Solution

  • With v2.1.3, you can use the pluginService to do this


    Preview

    enter image description here


    Script

    // round corners
    Chart.pluginService.register({
        afterUpdate: function (chart) {
            if (chart.config.options.elements.arc.roundedCornersFor !== undefined) {
                var arc = chart.getDatasetMeta(0).data[chart.config.options.elements.arc.roundedCornersFor];
                arc.round = {
                    x: (chart.chartArea.left + chart.chartArea.right) / 2,
                    y: (chart.chartArea.top + chart.chartArea.bottom) / 2,
                    radius: (chart.outerRadius + chart.innerRadius) / 2,
                    thickness: (chart.outerRadius - chart.innerRadius) / 2 - 1,
                    backgroundColor: arc._model.backgroundColor
                }
            }
        },
    
        afterDraw: function (chart) {
            if (chart.config.options.elements.arc.roundedCornersFor !== undefined) {
                var ctx = chart.chart.ctx;
                var arc = chart.getDatasetMeta(0).data[chart.config.options.elements.arc.roundedCornersFor];
                var startAngle = Math.PI / 2 - arc._view.startAngle;
                var endAngle = Math.PI / 2 - arc._view.endAngle;
    
                ctx.save();
                ctx.translate(arc.round.x, arc.round.y);
                console.log(arc.round.startAngle)
                ctx.fillStyle = arc.round.backgroundColor;
                ctx.beginPath();
                ctx.arc(arc.round.radius * Math.sin(startAngle), arc.round.radius * Math.cos(startAngle), arc.round.thickness, 0, 2 * Math.PI);
                ctx.arc(arc.round.radius * Math.sin(endAngle), arc.round.radius * Math.cos(endAngle), arc.round.thickness, 0, 2 * Math.PI);
                ctx.closePath();
                ctx.fill();
                ctx.restore();
            }
        },
    });
    
    // write text plugin
    Chart.pluginService.register({
        afterUpdate: function (chart) {
            if (chart.config.options.elements.center) {
                var helpers = Chart.helpers;
                var centerConfig = chart.config.options.elements.center;
                var globalConfig = Chart.defaults.global;
                var ctx = chart.chart.ctx;
    
                var fontStyle = helpers.getValueOrDefault(centerConfig.fontStyle, globalConfig.defaultFontStyle);
                var fontFamily = helpers.getValueOrDefault(centerConfig.fontFamily, globalConfig.defaultFontFamily);
    
                if (centerConfig.fontSize)
                    var fontSize = centerConfig.fontSize;
                    // figure out the best font size, if one is not specified
                else {
                    ctx.save();
                    var fontSize = helpers.getValueOrDefault(centerConfig.minFontSize, 1);
                    var maxFontSize = helpers.getValueOrDefault(centerConfig.maxFontSize, 256);
                    var maxText = helpers.getValueOrDefault(centerConfig.maxText, centerConfig.text);
    
                    do {
                        ctx.font = helpers.fontString(fontSize, fontStyle, fontFamily);
                        var textWidth = ctx.measureText(maxText).width;
    
                        // check if it fits, is within configured limits and that we are not simply toggling back and forth
                        if (textWidth < chart.innerRadius * 2 && fontSize < maxFontSize)
                            fontSize += 1;
                        else {
                            // reverse last step
                            fontSize -= 1;
                            break;
                        }
                    } while (true)
                    ctx.restore();
                }
    
                // save properties
                chart.center = {
                    font: helpers.fontString(fontSize, fontStyle, fontFamily),
                    fillStyle: helpers.getValueOrDefault(centerConfig.fontColor, globalConfig.defaultFontColor)
                };
            }
        },
        afterDraw: function (chart) {
            if (chart.center) {
                var centerConfig = chart.config.options.elements.center;
                var ctx = chart.chart.ctx;
    
                ctx.save();
                ctx.font = chart.center.font;
                ctx.fillStyle = chart.center.fillStyle;
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                var centerX = (chart.chartArea.left + chart.chartArea.right) / 2;
                var centerY = (chart.chartArea.top + chart.chartArea.bottom) / 2;
                ctx.fillText(centerConfig.text, centerX, centerY);
                ctx.restore();
            }
        },
    })
    

    and then

        ...
        options: {
            elements: {
                arc: {
                    roundedCornersFor: 0
                },
                center: {
                    // the longest text that could appear in the center
                    maxText: '100%',
                    text: '67%',
                    fontColor: '#FF6684',
                    fontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
                    fontStyle: 'normal',
                    // fontSize: 12,
                    // if a fontSize is NOT specified, we will scale (within the below limits) maxText to take up the maximum space in the center
                    // if these are not specified either, we default to 1 and 256
                    minFontSize: 1,
                    maxFontSize: 256,
                }
            }
        }
    };
    

    You can get rid of a bit of code if you don't want it to be generic (eg. if you fix the fontSize, if you fix the index to round, etc.)


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