canvas圖表詳解系列(3):動態餅狀圖(原生Js仿echarts餅狀圖)

 

本章建議學習時間4小時javascript

學習方式:詳細閱讀,並手動實現相關代碼(若是沒有canvas基礎,須要先學習前面的canvas基礎筆記)html

學習目標:此教程將教會你們如何使用canvas繪製各類圖表,詳細分解步驟,本次講解餅狀圖。前端

 

演示地址:  https://sutianbinde.github.io/charts/%E9%A5%BC%E7%8A%B6%E5%9B%BE-%E9%AB%98%E6%B8%85.htmljava

 

源文件下載地址:https://github.com/sutianbinde/chartsgit

 

餅狀圖github


餅狀圖是前端最基本的圖表之一,咱們的案例展現效果以下canvas

功能:圖表能夠根據數據自動變換比例,旋轉繪製的動畫,鼠標移入到對應模塊會實現顏色變化。瀏覽器

 

實現步驟學習


 

--新建Html文件,寫入canvas標籤,而且定義繪製圖表的方法(咱們js中的canvas寬高根據canvas父級標籤的寬高來設置,但願你們寫的時候必定給canvas添加父級div並指定寬高)字體

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <style>
        canvas{
            border: 1px solid #A4E2F9;
        }
    </style>
</head>
<body>
    <div height="400" width="600" style="margin:50px">
        <canvas id="chart"> 你的瀏覽器不支持HTML5 canvas </canvas>
    </div>
    
    <script type="text/javascript">
        function goChart(dataArr){
        
        }
        
        var chartData = [[50,"#2dc6c8","瓜子"], [100,"#b6a2dd", "花生"], [200,"#5ab1ee","土豆"], [700,"#d7797f","南瓜四號"]];
        
        goChart(chartData);


    </script>
</body>
</html>

 

--在 goChart方法中定義須要使用的變量 並獲取 canvas上下文 

            // 聲明所需變量
            var canvas,ctx;
            // 圖表屬性
            var cWidth, cHeight, cMargin, cSpace;
            // 餅狀圖屬性
            var radius,ox,oy;//半徑 圓心
            var tWidth, tHeight;//圖例寬高
            var posX, posY, textX, textY;
            var startAngle, endAngle;
            var totleNb;
            // 運動相關變量
            var ctr, numctr, speed;
            //鼠標移動
            var mousePosition = {};
            
            //線條和文字
            var lineStartAngle,line,textPadding,textMoveDis;
        
            // 得到canvas上下文
            canvas = document.getElementById("chart");
            if(canvas && canvas.getContext){
                ctx = canvas.getContext("2d");
            }

 

--初始化圖表(接着上一步的代碼寫在 goChart方法中 )

            initChart(); 
            
            // 圖表初始化
            function initChart(){
                // 圖表信息
                cMargin = 20;
                cSpace = 40;
                
                canvas.width = canvas.parentNode.getAttribute("width")* 2 ;
                canvas.height = canvas.parentNode.getAttribute("height")* 2;
                canvas.style.height = canvas.height/2 + "px";
                canvas.style.width = canvas.width/2 + "px";
                cHeight = canvas.height - cMargin*2;
                cWidth = canvas.width - cMargin*2;
        
                //餅狀圖信息
                radius = cHeight*2/6;  //半徑  高度的2/6
                ox = canvas.width/2 + cSpace;  //圓心
                oy = canvas.height/2;
                tWidth = 60; //圖例寬和高
                tHeight = 20; 
                posX = cMargin;
                posY = cMargin;   //
                textX = posX + tWidth + 15
                textY = posY + 18;
                startAngle = endAngle = 90*Math.PI/180; //起始弧度 結束弧度
                rotateAngle = 0; //總體旋轉的弧度
    
                //將傳入的數據轉化百分比
                totleNb = 0;
                new_data_arr = [];
                for (var i = 0; i < dataArr.length; i++){
                    totleNb += dataArr[i][0];
                }
                for (var i = 0; i < dataArr.length; i++){
                    new_data_arr.push( dataArr[i][0]/totleNb );
                }
                totalYNomber = 10;
                // 運動相關
                ctr = 1;//初始步驟
                numctr = 50;//步驟
                speed = 1.2; //毫秒 timer速度
                
                //指示線 和 文字
                lineStartAngle = -startAngle;
                line=40;         //畫線的時候超出半徑的一段線長
                textPadding=10;  //文字與線之間的間距
                textMoveDis = 200; //文字運動開始的間距
            }

 

--繪製板塊圖例

            drawMarkers();
            //繪製比例圖及文字
            function drawMarkers(){
                ctx.textAlign="left";
                for (var i = 0; i < dataArr.length; i++){
                    //繪製比例圖及文字
                    ctx.fillStyle = dataArr[i][1];
                    ctx.fillRect(posX, posY + 40 * i, tWidth, tHeight);
                    ctx.moveTo(posX, posY + 40 * i);
                    ctx.font = 'normal 24px 微軟雅黑';    //斜體 30像素 微軟雅黑字體
                    ctx.fillStyle = dataArr[i][1]; //"#000000";
                    var percent = dataArr[i][2] + ":" + parseInt(100 * new_data_arr[i]) + "%";
                    ctx.fillText(percent, textX, textY + 40 * i);
                }
            };

 

 --繪製餅狀圖動畫(接着上一步的代碼寫在 goChart方法中 )

注:繪製餅狀圖動畫的方法連續的可能更利於查看,因此就沒有拆分開,做了必要的註釋,不理解的可留言

            //繪製動畫
            pieDraw();
            function pieDraw(mouseMove){
                
                for (var n = 0; n < dataArr.length; n++){
                    ctx.fillStyle = ctx.strokeStyle = dataArr[n][1];
                    ctx.lineWidth=1;
                    var step = new_data_arr[n]* Math.PI * 2; //旋轉弧度
                    var lineAngle = lineStartAngle+step/2;   //計算線的角度
                    lineStartAngle += step;//結束弧度
                    
                    ctx.beginPath();
                    var  x0=ox+radius*Math.cos(lineAngle),//圓弧上線與圓相交點的x座標
                         y0=oy+radius*Math.sin(lineAngle),//圓弧上線與圓相交點的y座標
                         x1=ox+(radius+line)*Math.cos(lineAngle),//圓弧上線與圓相交點的x座標
                         y1=oy+(radius+line)*Math.sin(lineAngle),//圓弧上線與圓相交點的y座標
                         x2=x1,//轉折點的x座標
                         y2=y1,
                         linePadding=ctx.measureText(dataArr[n][2]).width+10; //獲取文本長度來肯定折線的長度
                         
                         ctx.moveTo(x0,y0);
                         //對x1/y1進行處理,來實現折線的運動
                         yMove = y0+(y1-y0)*ctr/numctr;
                         ctx.lineTo(x1,yMove);
                         if(x1<=x0){
                             x2 -= line;
                             ctx.textAlign="right";
                             ctx.lineTo(x2-linePadding,yMove);
                            ctx.fillText(dataArr[n][2],x2-textPadding-textMoveDis*(numctr-ctr)/numctr,y2-textPadding);
                         }else{
                             x2 += line;
                             ctx.textAlign="left";
                             ctx.lineTo(x2+linePadding,yMove);
                            ctx.fillText(dataArr[n][2],x2+textPadding+textMoveDis*(numctr-ctr)/numctr,y2-textPadding);
                         }
                         
                        ctx.stroke();
                        
                }
                
                
                
                //設置旋轉
                ctx.save();
                ctx.translate(ox, oy);
                ctx.rotate((Math.PI*2/numctr)*ctr/2);
                
                //繪製一個圓圈
                ctx.strokeStyle = "rgba(0,0,0,"+ 0.5*ctr/numctr +")"
                ctx.beginPath();
                ctx.arc(0, 0 ,(radius+20)*ctr/numctr, 0, Math.PI*2, false);
                ctx.stroke();
                
                for (var j = 0; j < dataArr.length; j++){
                    
                    //繪製餅圖
                    endAngle = endAngle + new_data_arr[j]* ctr/numctr * Math.PI * 2; //結束弧度
                    
                    ctx.beginPath();
                    ctx.moveTo(0,0); //移動到到圓心
                    ctx.arc(0, 0, radius*ctr/numctr, startAngle, endAngle, false); //繪製圓弧
                    
                    ctx.fillStyle = dataArr[j][1];
                    if(mouseMove && ctx.isPointInPath(mousePosition.x*2, mousePosition.y*2)){
                        ctx.globalAlpha = 0.8;
                    }
                    
                      ctx.closePath();
                      ctx.fill();
                    ctx.globalAlpha = 1;
                    
                    startAngle = endAngle; //設置起始弧度
                    if( j == dataArr.length-1 ){
                        startAngle = endAngle = 90*Math.PI/180; //起始弧度 結束弧度
                    }
                }
                
                ctx.restore();
                    
                if(ctr<numctr){
                    ctr++;
                    setTimeout(function(){
                        //ctx.clearRect(-canvas.width,-canvas.width,canvas.width*2, canvas.height*2);
                        ctx.clearRect(-canvas.width, -canvas.height,canvas.width*2, canvas.height*2);
                        drawMarkers();
                        pieDraw();
                    }, speed*=1.085);
                }
            }
            

 

 --監聽鼠標移動,以實現移動到當前項做顏色變化(接着上一步的代碼寫在 goChart方法中 )

 

            //監聽鼠標移動
            var mouseTimer = null;
            canvas.addEventListener("mousemove",function(e){
                e = e || window.event;
                if( e.offsetX || e.offsetX==0 ){
                    mousePosition.x = e.offsetX;
                    mousePosition.y = e.offsetY;
                }else if( e.layerX || e.layerX==0 ){
                    mousePosition.x = e.layerX;
                    mousePosition.y = e.layerY;
                }
                
                clearTimeout(mouseTimer);
                mouseTimer = setTimeout(function(){
                    ctx.clearRect(0,0,canvas.width, canvas.height);
                    drawMarkers();
                    pieDraw(true);
                },10);
            });

 

 

--這樣咱們整個代碼就編寫完成了

所有代碼以下:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <style>
        canvas{
            border: 1px solid #A4E2F9;
        }
    </style>
</head>
<body>
    <div height="400" width="600" style="margin:50px">
        <canvas id="chart"> 你的瀏覽器不支持HTML5 canvas </canvas>
    </div>
    
    <script type="text/javascript">
        function goChart(dataArr){
            
            // 聲明所需變量
            var canvas,ctx;
            // 圖表屬性
            var cWidth, cHeight, cMargin, cSpace;
            // 餅狀圖屬性
            var radius,ox,oy;//半徑 圓心
            var tWidth, tHeight;//圖例寬高
            var posX, posY, textX, textY;
            var startAngle, endAngle;
            var totleNb;
            // 運動相關變量
            var ctr, numctr, speed;
            //鼠標移動
            var mousePosition = {};
            
            //線條和文字
            var lineStartAngle,line,textPadding,textMoveDis;
        
            // 得到canvas上下文
            canvas = document.getElementById("chart");
            if(canvas && canvas.getContext){
                ctx = canvas.getContext("2d");
            }
            initChart(); 
            
            // 圖表初始化
            function initChart(){
                // 圖表信息
                cMargin = 20;
                cSpace = 40;
                
                canvas.width = canvas.parentNode.getAttribute("width")* 2 ;
                canvas.height = canvas.parentNode.getAttribute("height")* 2;
                canvas.style.height = canvas.height/2 + "px";
                canvas.style.width = canvas.width/2 + "px";
                cHeight = canvas.height - cMargin*2;
                cWidth = canvas.width - cMargin*2;
        
                //餅狀圖信息
                radius = cHeight*2/6;  //半徑  高度的2/6
                ox = canvas.width/2 + cSpace;  //圓心
                oy = canvas.height/2;
                tWidth = 60; //圖例寬和高
                tHeight = 20; 
                posX = cMargin;
                posY = cMargin;   //
                textX = posX + tWidth + 15
                textY = posY + 18;
                startAngle = endAngle = 90*Math.PI/180; //起始弧度 結束弧度
                rotateAngle = 0; //總體旋轉的弧度
    
                //將傳入的數據轉化百分比
                totleNb = 0;
                new_data_arr = [];
                for (var i = 0; i < dataArr.length; i++){
                    totleNb += dataArr[i][0];
                }
                for (var i = 0; i < dataArr.length; i++){
                    new_data_arr.push( dataArr[i][0]/totleNb );
                }
                totalYNomber = 10;
                // 運動相關
                ctr = 1;//初始步驟
                numctr = 50;//步驟
                speed = 1.2; //毫秒 timer速度
                
                //指示線 和 文字
                lineStartAngle = -startAngle;
                line=40;         //畫線的時候超出半徑的一段線長
                textPadding=10;  //文字與線之間的間距
                textMoveDis = 200; //文字運動開始的間距
            }
        
            drawMarkers();
            //繪製比例圖及文字
            function drawMarkers(){
                ctx.textAlign="left";
                for (var i = 0; i < dataArr.length; i++){
                    //繪製比例圖及文字
                    ctx.fillStyle = dataArr[i][1];
                    ctx.fillRect(posX, posY + 40 * i, tWidth, tHeight);
                    ctx.moveTo(posX, posY + 40 * i);
                    ctx.font = 'normal 24px 微軟雅黑';    //斜體 30像素 微軟雅黑字體
                    ctx.fillStyle = dataArr[i][1]; //"#000000";
                    var percent = dataArr[i][2] + "" + parseInt(100 * new_data_arr[i]) + "%";
                    ctx.fillText(percent, textX, textY + 40 * i);
                }
            };
            
            //繪製動畫
            pieDraw();
            function pieDraw(mouseMove){
                
                for (var n = 0; n < dataArr.length; n++){
                    ctx.fillStyle = ctx.strokeStyle = dataArr[n][1];
                    ctx.lineWidth=1;
                    var step = new_data_arr[n]* Math.PI * 2; //旋轉弧度
                    var lineAngle = lineStartAngle+step/2;   //計算線的角度
                    lineStartAngle += step;//結束弧度
                    
                    ctx.beginPath();
                    var  x0=ox+radius*Math.cos(lineAngle),//圓弧上線與圓相交點的x座標
                         y0=oy+radius*Math.sin(lineAngle),//圓弧上線與圓相交點的y座標
                         x1=ox+(radius+line)*Math.cos(lineAngle),//圓弧上線與圓相交點的x座標
                         y1=oy+(radius+line)*Math.sin(lineAngle),//圓弧上線與圓相交點的y座標
                         x2=x1,//轉折點的x座標
                         y2=y1,
                         linePadding=ctx.measureText(dataArr[n][2]).width+10; //獲取文本長度來肯定折線的長度
                         
                         ctx.moveTo(x0,y0);
                         //對x1/y1進行處理,來實現折線的運動
                         yMove = y0+(y1-y0)*ctr/numctr;
                         ctx.lineTo(x1,yMove);
                         if(x1<=x0){
                             x2 -= line;
                             ctx.textAlign="right";
                             ctx.lineTo(x2-linePadding,yMove);
                            ctx.fillText(dataArr[n][2],x2-textPadding-textMoveDis*(numctr-ctr)/numctr,y2-textPadding);
                         }else{
                             x2 += line;
                             ctx.textAlign="left";
                             ctx.lineTo(x2+linePadding,yMove);
                            ctx.fillText(dataArr[n][2],x2+textPadding+textMoveDis*(numctr-ctr)/numctr,y2-textPadding);
                         }
                         
                        ctx.stroke();
                        
                }
                
                //設置旋轉
                ctx.save();
                ctx.translate(ox, oy);
                ctx.rotate((Math.PI*2/numctr)*ctr/2);
                
                //繪製一個圓圈
                ctx.strokeStyle = "rgba(0,0,0,"+ 0.5*ctr/numctr +")"
                ctx.beginPath();
                ctx.arc(0, 0 ,(radius+20)*ctr/numctr, 0, Math.PI*2, false);
                ctx.stroke();
                
                for (var j = 0; j < dataArr.length; j++){
                    
                    //繪製餅圖
                    endAngle = endAngle + new_data_arr[j]* ctr/numctr * Math.PI * 2; //結束弧度
                    
                    ctx.beginPath();
                    ctx.moveTo(0,0); //移動到到圓心
                    ctx.arc(0, 0, radius*ctr/numctr, startAngle, endAngle, false); //繪製圓弧
                    
                    ctx.fillStyle = dataArr[j][1];
                    if(mouseMove && ctx.isPointInPath(mousePosition.x*2, mousePosition.y*2)){
                        ctx.globalAlpha = 0.8;
                    }
                    
                      ctx.closePath();
                      ctx.fill();
                    ctx.globalAlpha = 1;
                    
                    startAngle = endAngle; //設置起始弧度
                    if( j == dataArr.length-1 ){
                        startAngle = endAngle = 90*Math.PI/180; //起始弧度 結束弧度
                    }
                }
                
                ctx.restore();
                    
                if(ctr<numctr){
                    ctr++;
                    setTimeout(function(){
                        //ctx.clearRect(-canvas.width,-canvas.width,canvas.width*2, canvas.height*2);
                        ctx.clearRect(-canvas.width, -canvas.height,canvas.width*2, canvas.height*2);
                        drawMarkers();
                        pieDraw();
                    }, speed*=1.085);
                }
            }
            
            //監聽鼠標移動
            var mouseTimer = null;
            canvas.addEventListener("mousemove",function(e){
                e = e || window.event;
                if( e.offsetX || e.offsetX==0 ){
                    mousePosition.x = e.offsetX;
                    mousePosition.y = e.offsetY;
                }else if( e.layerX || e.layerX==0 ){
                    mousePosition.x = e.layerX;
                    mousePosition.y = e.layerY;
                }
                
                clearTimeout(mouseTimer);
                mouseTimer = setTimeout(function(){
                    ctx.clearRect(0,0,canvas.width, canvas.height);
                    drawMarkers();
                    pieDraw(true);
                },10);
            });
            
        }
        
        var chartData = [[50,"#2dc6c8","瓜子"], [100,"#b6a2dd", "花生"], [200,"#5ab1ee","土豆"], [700,"#d7797f","南瓜四號"]];
        
        goChart(chartData);


    </script>
</body>
</html>

 

 

 

 

好了,今天就講到這裏,但願你們把代碼都本身敲一遍。

 

 

關注公衆號,博客更新便可收到推送

相關文章
相關標籤/搜索