Due to business requirements, Echart can not meet the rounded circle pie chart, so custom implementation. The effect is as follows:

Js code:

    function drawCircle(id,arr,options,colors = ["#007FDD"."#379CDD"."#63BAF2"."#C3DCED"."#E9F6FF"]) {
        const {radius, lng, title, titleColor, horizon} = options;
        let dom = document.getElementById(id);
        dom.innerHTML="";
        let c = document.createElement('canvas');
        c.height = dom.clientHeight;
        c.width = dom.clientWidth;
        let x = dom.clientWidth / 2;
        let y = dom.clientHeight / 2;
        dom.appendChild(c);
        let ctx = c.getContext("2d");
        / / o combined
        let sum = 0;
        for(let i=0; i<arr.length; i++){let item = arr[i];
            sum += item.value;
        }
        // Draw the title
        ctx.beginPath();
        ctx.fillStyle=titleColor;
        // The text is centered
        ctx.textAlign = 'center';
        // Center perpendicular to the origin
        ctx.textBaseline="middle";
        // Text style: bold 16 pixel font Arial
        ctx.font = 'Normal 18px Microsoft Yahei';
        ctx.fillText(title,x,y);
        // Add Angle
        let sumAngle = 0;
        // Set the starting position
        let start = 1.5*Math.PI;
        function drawLine(ctx, angle, color, item, rate) {
            let label = item.name + ":"+rate +"%";
            // Cumulative Angle - current half Angle
            let bjAngle = sumAngle-angle;
            ctx.beginPath();
            ctx.globalCompositeOperation = 'destination-over';
            let chgAngle = 0.017453293 * bjAngle;
            // Set the starting state
            let tmp1 = {
                x: x-radius*Math.sin(chgAngle),
                y: y-radius*Math.cos(chgAngle)
            };
            // Set the terminal state
            let tmp2 = {
                x: x-lng*Math.sin(chgAngle),
                y: y-lng*Math.cos(chgAngle)
            };
            ctx.moveTo (tmp1.x, tmp1.y);
            ctx.lineTo (tmp2.x, tmp2.y);
            // Set the line width state
            ctx.lineWidth = 1;

            let tmp3 = {};
            let tmp4 = {};
            // Draw a horizontal line to the left
            if(bjAngle<180){
                tmp3.x = tmp2.x-horizon;
                tmp3.y = tmp2.y;
                tmp4.x = tmp3.x-6*label.length;
                tmp4.y = tmp3.y;
            }else{
                tmp3.x = tmp2.x+horizon;
                tmp3.y = tmp2.y;
                tmp4.x = tmp3.x+6*label.length;
                tmp4.y = tmp3.y;
            }
            ctx.lineTo (tmp3.x, tmp3.y);
            // Draw
            ctx.stroke();
            ctx.beginPath();
            ctx.fillStyle = color;
            ctx.arc(tmp3.x, tmp3.y, 2.0.2*Math.PI, true);
            ctx.fill();

            // Draw the title
            ctx.beginPath();
            ctx.fillStyle= '#fff';
            // Center perpendicular to the origin
            ctx.textBaseline="middle";
            // Text style: bold 16 pixel font Arial
            ctx.font = 'Normal 12px Microsoft Yahei';
            ctx.fillText(label,tmp4.x, tmp4.y);

            if(item.name1){
                // Draw the title
                ctx.beginPath();
                ctx.fillStyle= '#CDFFEF';
                // Center perpendicular to the origin
                ctx.textBaseline="middle";
                // Text style: bold 16 pixel font Arial
                ctx.font = 'Normal 12px Microsoft Yahei';
                ctx.fillText("("+item.name1+")",tmp4.x, tmp4.y+12); }}/ / animation
        function sleep(ms, callback) {
            setTimeout(callback, ms)
        }
        let lastItem = arr[arr.length-1];
        for(let key=0; key<arr.length; key++){let item = arr[key];
            let rate = item.value/sum;
            let curColor = colors[key];
            sleep(80*(key+1), function () {
                ctx.beginPath();
                ctx.lineWidth = 20;
                ctx.strokeStyle = curColor;
                if(item.value==lastItem.value&&item.name==lastItem.name){
                    // Render half of it first
                    let add = rate*Math.PI;
                    ctx.arc(x,y,radius, start, start-add, true);
                    ctx.lineCap = 'round';
                    ctx.globalCompositeOperation = 'source-over'
                    start -= add;
                    ctx.stroke();
                    ctx.beginPath();
                    ctx.globalCompositeOperation = 'destination-over';
                    ctx.arc(x,y,radius, start, start-add, true);
                    ctx.lineCap = 'butt';
                    ctx.stroke();
                } else {
                    let add = rate*2*Math.PI;
                    ctx.arc(x,y,radius, start, start-add, true);
                    ctx.lineCap = 'round';
                    start -= add;
                    ctx.stroke();
                }
                sumAngle += rate*180*2;
                let angle = rate*180;
                drawLine(ctx, angle, colors[key], item, Math.round(rate*100));
                ctx.globalCompositeOperation = 'source-over'}}})Copy the code

Use:

const options = { radius: 50, lng: 80, title: 'CPU', titleColor: '#73FBFD', horizon: 10 }; Const arr = [{name1: 'all over: 30%, name:' work 'value: 8}, {name1:' all over: 30%, name: 'reading' value: 4}, {name1: 'all over: 30%, name: 'entertainment' value: 2}, {name1: 'all over: 30%, name:' sleep 'value: 8}, {name1:' all over: 30%, name: 'eating' value: 3}]; drawCircle('myCanvas', arr,options)Copy the code

Method statement

  • The main lineWidth is used to form the circle;
  • Ctx. lineCap = ’round’ property to implement rounded corners;
  • Rendering for the final data to two and a half, half is not rounded corners, the other set rounded corners, and CTX. GlobalCompositeOperation = ‘destination – over to realize the last part of the circular arc rounded corners will not be covered;
  • Guide lines should be drawn on the left and right sides; And the position of the guide line is drawn from the middle of each arc;
  • The animation part of the implementation is relatively simple and crude, using each data as a segmentation point, a data a data drawing appeared animation effect;
  • Compatibility: Currently, only Internet Explorer 8 or later is compatible.

To optimize the

  • Rendering for the final data to two and a half, half is not rounded corners, the other set rounded corners, and CTX. GlobalCompositeOperation = ‘destination – over to realize the last part of the circular arc rounded corners will not be covered; (This method will appear when the arc is very small, the coverage is not obvious) as shown below:

The solution is: record the largest data, starting from the largest data to draw a new arc.

  • Another problem is that labels are crowded together.

The solution is: first calculate the position of the label to be drawn, adjust it dynamically until the conditions are met, and then draw.

  1. For these labels, first determine its outsource rectangle.

Height: font size * 2 width: number of characters * Font size Number of characters: Chinese is 1, non-Chinese is 0.5 calculation method:

    / / statistical characters, https://www.cnblogs.com/jkr666666/p/11645070.html
    function getByteLen(val) {
    	let len = 0;
    	for (let i = 0; i < val.length; i++) {
            let a = val.charAt(i);
    		if (a.match(/[^\x00-\xff]/ig) != null) {
    			len += 1;
    		} else {
    			len += 0.5; }}return len;
    }
Copy the code

According to the height and width and the position of the dot can be found out the coordinates of the four points of the rectangle.

  1. An algorithm to calculate the intersection of two rectangles

Reference: www.jianshu.com/p/a2d881847… The following three conditions are met: Max (Xa1,Xb1) <= min(Xa2,Xb2) Max (Ya1,Yb1) <= min(Ya2,Yb2) intersecting rectangle area > 0

  1. If two or more rectangles intersect, adjust the position of the rectangles dynamically. There are two cases of left and right.

For the left-hand arc, record two or more rectangular coordinates that intersect; Keep the positions of the four points of the first rectangle unchanged, and adjust the coordinate positions of the other rectangles according to the height of the rectangle in turn downward (Y-axis coordinates become larger). Iterate over the left circular rectangles until no rectangles intersect each other.

Similar for the right-hand arc.

Git address:Github.com/YY88Xu/draw…

Effect: