• canvas图表详解系列(3):动态饼状图(原生Js仿echarts饼状图)


    本章建议学习时间4小时

    学习方式:详细阅读,并手动实现相关代码(如果没有canvas基础,需要先学习前面的canvas基础笔记)

    学习目标:此教程将教会大家如何使用canvas绘制各种图表,详细分解步骤,本次讲解饼状图。

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

    源文件下载地址:https://github.com/sutianbinde/charts

    饼状图


    饼状图是前端最基本的图表之一,我们的案例展示效果如下

    功能:图表可以根据数据自动变换比例,旋转绘制的动画,鼠标移入到对应模块会实现颜色变化。

    实现步骤


    --新建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>

    好了,今天就讲到这里,希望大家把代码都自己敲一遍。

    关注公众号,博客更新即可收到推送

  • 相关阅读:
    老话题之C#写邮件发送
    MVC readioButtonList的创作过程及运用
    asp.net发送短信
    关于MVC打印问题,打印指定的内容
    Java WebSocket HttpSession与WebSocket Session的关联
    JUnit accuracy/failure/stress test区别
    JUnit pass/failure/error区别
    Eclipse 重构
    Java 注释
    Java API概述
  • 原文地址:https://www.cnblogs.com/chengduxiaoc/p/7688794.html
Copyright © 2020-2023  润新知