效果图

两个步骤:

第一步画一个扇形
第二步把扇形画满360°
  • 来看第一个步骤,怎么画扇形?

看下面的示意图

原理示意图

  • 要画扇形图,首先需要知道圆心坐标,然后需要知道半径,这样就可以画出一条线,然后在用画圆函数obj.arc在线的终点坐标根据起始角度和终止角度画出一段弧,最后在闭合图形,就能画出一条弧,图中cx、cy是圆心坐标,#1是画出的第一段线,x、y是线的终点坐标,然后以线的终点坐标为弧的起点坐标,即x、y,加上起始角度(图中ang1)和终止角度(图中ang2)画出一段弧(图中#2),最后闭合路径(obj.closePath())即可以画出图中所示的一小段弧。
  • 画扇形步骤开始:

先获取canvas对象:

let c1 = document.getElementById('can');
let gd = c1.getContext('2d');
  1. 给一个圆心坐标cx、cy,半径r:
let cx=200,cy=200,r=150;
  1. 开始路径:
gd.beginPath();
  1. 画笔移到圆心:
gd.moveTo(cx,cy);
  1. 然后移动到x、y画出一条线

那x、y怎么求呢?再来看这张图
原理示意图
已知圆心cx、cy、r和起始角度ang1要求图中x、y,利用三角函数知识可知,图中的红边a=cos(ang1)r,蓝色边b=sin(ang1)r;而x、y的值就是圆心坐标分别加上a和b,就是我们求的线的终点坐标即弧的起点坐标。
假设我们令起始角度ang1=30°,终止角度ang2=80°,由于Math.cos和Math,sin的参数是弧度制,因此我们还需要一个函数把角度制转成弧度制:

function d2a(n){//角度转弧度
      return n*Math.PI/180;
 }
function a2d(n){//弧度转角度
      return n*180/Math.PI;
}
            
let startAng=30,endAng=80;
let x=cx+Math.cos(d2a(startAng)),y=cy+Math.sin(d2a(startAng));

把线画过去

gd.lineTo(x,y);
  1. 然后画弧:
gd.arc(cx,cy,r,d2a(startAng),d2a(endAng),false);
  1. 最后闭合路径、填充:
gd.closePath();
gd.fillStyle='orange';
gd.fill();

扇形就画出来了:
扇形

  • 接下来第二个步骤,把扇形画满360°

在解决这个问题之前,先要解决一个问题:怎么把一份数据用饼状图表示出来?
再来看那个图:
原理示意图
假设一份数据包含200个男的,100个女的和50个其他,那么200/(200+100+50)≈0.571就是性别为男的占比,用0.571*360=205.56就是200个男的在饼状图中占了多少度,这个角度就是我们要用的起始角度,终止角度就是起始角度+弧长就自己给我们画出来了

  • 那么需要画多少个扇形呢?

有多少份数据就画多少个扇形
例:

let datas=[     
                {name:'菠菜',data:300,color:'#f5c'},
                {name:'玉米',data:200,color:'#dd0'},
                {name:'花生',data:20,color:'#0fc'},
                {name:'大豆',data:180,color:'#f85'}
]

这是我们的数据,菠菜300斤,饼图颜色etc.现在需要用饼状图把他表示出来
我们把前面画扇形的函数封装一下,然后用一个循环遍历数组就可以画出来了
封装:

function pie(startAng, endAng, color) {
                let x = cx + r*Math.cos(d2a(startAng)), y = cy + r*Math.sin(d2a(startAng));

                gd.beginPath();
                //#1
                gd.moveTo(cx, cy);
                gd.lineTo(x, y);
                //#2
                gd.arc(cx, cy, r, d2a(startAng), d2a(endAng), false);
                //#3
                gd.closePath();

                gd.fillStyle = color;
                gd.fill();         
            }

原理示意图

先求出总共有多少斤:

let sum=0;
datas.forEach(data=>{
sum+=data.data;
})

在画饼图的时候我们还需要一个变量now,用来记录当前的角度,然后用now+我们算出来的在饼状图占多少度就是我们要画的一个扇形

let now=0;

开始画:

datas.forEach(data=>{
                let ang=360*data.data/sum;//求出需要画多少度
                pie(now,now+ang,data.color);//画扇形
                
                //添加名字
                let mid=(now+now+ang)/2;//取每一块扇图的中间位置为字体的坐标
                let x = cx + r*Math.cos(d2a(mid)), y = cy + r*Math.sin(d2a(mid));//算出字体的x、y坐标
                gd.font='bold 20px 宋体';//添加字体
                if(mid<180){//如果小于180°就让坐标大一点,文字往下走
                    gd.fillText(data.name,x+30,y+30);
                }else{//如果大于180°就让坐标小一点,文字往上走
                    gd.fillText(data.name,x-30,y-30);
                }
                
                now+=ang;//更新now

            })

完整代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        body {
            background: gray;
            text-align: center;
        }
    </style>
    <script>
        window.onload = function () {
            let c1 = document.getElementsByTagName('canvas')[0];
            let gd = c1.getContext('2d');
            let cx = 400, cy = 300, r = 150;

            function d2a(n) {//角度转弧度
                return n * Math.PI / 180;
            }
            function a2d(n) {//弧度转角度
                return n * 180 / Math.PI;
            }

            function pie(startAng, endAng, color) {
                let x = cx + r*Math.cos(d2a(startAng)), y = cy + r*Math.sin(d2a(startAng));

                gd.beginPath();
                //#1
                gd.moveTo(cx, cy);
                gd.lineTo(x, y);
                //#2
                gd.arc(cx, cy, r, d2a(startAng), d2a(endAng), false);
                //#3
                gd.closePath();

                gd.fillStyle = color;
                gd.fill();         
            }

            let datas=[//数据
                {name:'菠菜',data:300,color:'#f5c'},
                {name:'玉米',data:200,color:'#dd0'},
                {name:'花生',data:20,color:'#0fc'},
                {name:'大豆',data:180,color:'#f85'}
            ]
            let sum=0,now=0;
            datas.forEach(data=>{
                sum+=data.data;//求出总和
            })

            datas.forEach(data=>{
                let ang=360*data.data/sum;//求出需要画多少度
                pie(now,now+ang,data.color);//画扇形
                
                //添加名字
                let mid=(now+now+ang)/2;//取每一块扇图的中间位置为字体的坐标
                let x = cx + r*Math.cos(d2a(mid)), y = cy + r*Math.sin(d2a(mid));//算出字体的x、y坐标
                gd.font='bold 20px 宋体';//添加字体
                if(mid<180){//如果小于180°就让坐标大一点,文字往下走
                    gd.fillText(data.name,x+30,y+30);
                }else{//如果大于180°就让坐标小一点,文字往上走
                    gd.fillText(data.name,x-30,y-30);
                }  
                now+=ang;//更新now
            })
        }
    </script>
</head>
<body>
    <canvas width="800" height="600" style="background:white;"></canvas>
</body>
</html>

原理示意图


逸轩
8 声望1 粉丝

我也想成为大佬啊


« 上一篇
canvas小记(1)