canvas画扇形、饼图

画扇形的方法

方法一:起始角度是0,那么第一条线就是line(r,0),通过旋转扇形的角度,第二条线就是line(r,0)

//圆弧
ctx.save();
ctx.translate(100, 100);
ctx.arc(0,0,100,0, 30*Math.PI/180);
ctx.restore();
//第一条线
ctx.save();
ctx.moveTo(100,100);
ctx.lineTo(200,100);
ctx.restore();
//第二条线
ctx.save();
ctx.translate(100, 100);
ctx.moveTo(0,0);
ctx.rotate(30*Math.PI/180);
ctx.lineTo(100,0);
ctx.stroke();
ctx.restore();

第一步为什么是设置原点呢,为什么不用moveTo来设置起始点呢?

  因为画布的默认原点在0,0的位置上,如果用moveTo来设置起始点,原点依然还在0,0的位置,而变换是以原点为基准点的,即使你设置了起始点,但是起始点不是原点的话,图形旋转依然会围绕0,0点旋转然后自转,得到的图形就不知道是什么图形了。

方法二

//将原点设置100,100位置
ctx.translate(100,100);
//原点在100,100,则圆心设为0,0 ——> 100,100的位置
ctx.arc(0,0,100,30*Math.PI/180,60*Math.PI/180);
//save(),restore()是为了防止角度旋转的污染
ctx.save();
ctx.rotate(30*Math.PI/180);
ctx.moveTo(0,0);
ctx.lineTo(100,0);
ctx.restore();
ctx.rotate(60*Math.PI/180);
ctx.moveTo(0,0);
ctx.lineTo(100,0);
ctx.stroke();

关于save()和restore():

  其作用不只是可以防止外面的属性或方法对里面的绘制产生影响,它的本质意思是save()保存当前环境的状态,restore()返回之前保存路径的状态。在这里要注意还原的触点在什么地方,如下例子:

 1 //将原点设置100,100位置
 2 ctx.translate(100,100);
 3 //原点在100,100,则圆心设为0,0 ——> 100,100的位置
 4 ctx.arc(0,0,100,30*Math.PI/180,60*Math.PI/180);
 5 //save(),restore()是为了防止角度旋转的污染
 6 ctx.save();
 7 ctx.rotate(30*Math.PI/180);
 8 ctx.moveTo(0,0);
 9 ctx.lineTo(100,0);
10 ctx.restore();
11 ctx.rotate(60*Math.PI/180);
12 ctx.lineTo(100,0);
13 ctx.stroke();

跟上面的代码比就是少了12行的moveTo(0,0),结果如下图1:

原因是:线在旋转的时候,是从它的起点为圆心旋转的,而上面的代码是,第一条线从圆心开始,到圆弧的起点(旋转过后),自然现在的起点就是圆弧的起点了,然后再rotate(60*Math.PI/180);lineTo(100,0);就是从圆弧的起点画到(100,0)的线了

现在我们将第一条直线的起点设在(r,0)的位置,旋转后就到了圆弧的起始点,然后在画到圆心地方,那现在的起始点就是圆心了,再画一条线到圆弧,就可以了,如下:

//将原点设置100,100位置
ctx.translate(100,100);
//原点在100,100,则圆心设为0,0 ——> 100,100的位置
ctx.arc(0,0,100,30*Math.PI/180,60*Math.PI/180);
//save(),restore()是为了防止角度旋转的污染
ctx.save();
ctx.rotate(30*Math.PI/180);
ctx.moveTo(100,0);
ctx.lineTo(0,0);
ctx.restore();
ctx.rotate(60*Math.PI/180);
ctx.lineTo(100,0);
ctx.stroke();

 方法三:充分使用触点的作用,当我们再画圆弧的时候,画完之后其触点在圆弧的结束位置,为何不直接将这个触点作为起点,画一条到圆心的线,然后再画第二条线。如下代码:

//将原点设置100,100位置
ctx.translate(100,100);
//原点在100,100,则圆心设为0,0 ——> 100,100的位置
ctx.arc(0,0,100,30*Math.PI/180,60*Math.PI/180);
//以圆弧终点为起点画直线,rotate使得直角坐标系旋转了30°
ctx.lineTo(0,0);
ctx.rotate(30*Math.PI/180);
//以0,0为起点画直线
ctx.lineTo(100,0);
ctx.stroke();

方法四:配合beginPath()和closePath()

为什么要用translate,而不要moveTo,是因为如果需要旋转,所以就需要原点,现在不需要旋转,而是正常的画图,那么就不需要原点,就可以用moveTo。

配合beginPath()和closePath(),就会将一个圆弧封闭起来。

ctx.beginPath();
//定义起点
ctx.moveTo(100,100);
//以起点为圆心,画一个半径为100的圆弧
ctx.arc(100,100,100,30*Math.PI/180, 60*Math.PI/180);
ctx.closePath();
ctx.stroke();

 特别注意:

  • 使用arc()绘制图形时,如果没有设置moveTo(),那么会从圆弧的开始的点作为起始点。如果设置了moveTo(),那么该点会连线到圆弧起始点。
  • 如果使用stroke()方法,那么会从开始连线到圆弧的起始位置。 如果是 fill 方法, 会自动闭合路径填充

 

封装函数

CanvasRenderingContext2D.prototype.sector = function(x,y,r,angle1,angle2){
            this.save();
            this.beginPath();
            this.moveTo(x,y);
            this.arc(x,y,r,angle1*Math.PI/180,angle2*Math.PI/180,false);
            this.closePath();    
            this.restore();
            return this;
        }
        ctx.fillStyle = 'red';
        ctx.sector(200,200,100,30,150).fill();
        ctx.fillStyle = 'green';
        ctx.sector(200,200,100,150,270).fill();
        ctx.fillStyle = 'blue';
        ctx.sector(200,200,100,270,390).fill(); 

画饼图

思路:

1)将每块饼的占比以整数形式储存在数组nums中,将每个饼的颜色以字符串形式储存在数组colors中,两个数组的值一一对应。将画布旋转坐标定义在即将绘制圆的中心,定义绘制圆弧(饼)的起始角度start和终止角度end均为0

2)绘制圆饼,6个不同板块,所以有6次for循环,从绘制第二个饼开始,起始角度是在上一个饼的终止角度位置,因此,每次循环开始后,要对当前的终止角度end进行累加一次,绘制圆弧直接以start开始,以end结束,当前绘制完成之后,要对起始角度start完成一次累加;同时每次绘制都给板块填充对应的颜色。封装为函数pieChart();

3)绘制圆饼对应的占比数值,也是6次for循环,为了便于代码易读,这里重新定义了一个函数。并对起始角度start和终止角度end重新利用,nums中储存的是当前数值占100的份数,将其转化为对应角度为nums[i]/50*Math.PI;让其显示在板块角度中线位置nums[i]/50*Math.PI/2;同样每次循环起始角度是在上一个饼的终止角度位置,绘制前后也要进行累加。封装为函数pieNum();

 

<canvas id="can1" width="800" height="600"></canvas>
    <script type="text/javascript">
            var can1 = document.getElementById("can1");
            var ctx = can1.getContext("2d");
            var nums = [26,15,12,5,25,17];
            var colors = ["#983335","#77963f","#5d437c","#35859f","#d1702f","#365e96"];
            var start = 0;
            var end = 0;
            ctx.translate(400,350);
            //绘制圆饼
            function pieChart(){
                for (var i = 0;i < nums.length; i ++){
                    ctx.beginPath();
                    ctx.moveTo(0,0);
                    end += nums[i]/50*Math.PI;//终止角度
                    ctx.strokeStyle = "white";
                    ctx.fillStyle = colors[i];
                    ctx.arc(0,0,200,start,end);
                    ctx.fill();
                    ctx.closePath();
                    ctx.stroke();
                    start += nums[i]/50*Math.PI;//起始角度
                }
            }
            //绘制圆饼上的数值
            function pieNum(){
                for (var i = 0;i < nums.length; i ++){
                    start = nums[i]/50*Math.PI/2;
                    ctx.rotate(end+start);//旋转数值
                    ctx.font = "25px scans-serif";
                    ctx.fillStyle = "#000";
                    ctx.fillText(nums[i]+"%",140,0);
                    end = nums[i]/50*Math.PI/2;
                }
            }
            ctx.rotate(-Math.PI/6);//旋转一定角度更加自然
            pieChart();
            pieNum();
</script>

 

posted @ 2018-04-12 15:47  L_mj  阅读(8215)  评论(0编辑  收藏  举报