使用JS绘制一个圆环统计图

2019-03-24  本文已影响0人  拾实

无意间翻到了之前某次考核做的一个环形统计图,今天又捣鼓了一下把它封装成了一个类,可以根据不同的数据切换内容,下面分享一下我的实现思路。

0.效果预览

基本功能

1.该示例的完整JS代码pieChart.js

window.onload = function(){
    var data1 = [
        {"id":"item0","text":"这是第一行","num":10,"color":"#1e90ff","isdraw":1},
        {"id":"item1","text":"这是第二行","num":20,"color":"#36cbcb","isdraw":1},
        {"id":"item2","text":"这是第三行","num":30,"color":"#2fc25b","isdraw":1},
        {"id":"item3","text":"这是第四行","num":40,"color":"#ffd700","isdraw":1},
        {"id":"item4","text":"这是第五行","num":50,"color":"#ff3030","isdraw":1},
        {"id":"item5","text":"这是第六行","num":60,"color":"#8a2be2","isdraw":1},
    ];
    var apie = new pieChart('这里是标题',data1,150,40);
    apie.add_data();
    apie.draw();
}

function pieChart(title, data, radius, width){
    this.title = title;
    this.data = data;
    this.width = width;
    this.radius = radius;

    this.add_data = function(){
        var width = 2*radius;
        var chart = document.createElement('div');
        chart.style.width = width+'px';
        var top = document.createElement('div');
        top.setAttribute('style','text-align:center;font-weight:bold;width:'+width+'px');
        top.innerText = title;
        var circle = document.createElement('canvas');
        circle.setAttribute('id','circle');
        circle.setAttribute('width',width+"px");
        circle.setAttribute('height',width+"px");
        var list = document.createElement('div');
        list.setAttribute('id','list');
        var ul = document.createElement('ul');
        ul.setAttribute('style','font-family:Simsun;margin:0;padding:0;list-style:none;');
        for(var i=0; i<data.length; i++){
            var li = document.createElement('li');
            li.setAttribute('id',data[i].id);
            li.setAttribute('style','width:'+width+'px;');
            li.style.color = data[i].color;
            li.innerHTML = "<span>• </span><span style='color:black;'>"+data[i].text+"</span><span style='color:gray;float:right'>"+data[i].num+"</span>";
            li.onclick = this.draw;
            ul.appendChild(li);
        }
        list.appendChild(ul);
        chart.appendChild(top);
        chart.appendChild(circle);
        chart.appendChild(list);
        document.body.appendChild(chart);
    }
    this.draw = function(){
        var len = data.length;
        var id = this.id;
        for(var i=0; i<len; i++){
            if(data[i].id == id){
                data[i].isdraw = data[i].isdraw? 0:1;
                this.style.color = this.style.color == 'gray'? data[i].color:'gray';
                var span = this.children[2];
                span.style.display = span.style.display=='none'? 'inline':'none';  
            }
        }
        var canvas = document.getElementById('circle');
        canvas.height = canvas.height;
        if (canvas.getContext) {
            var ctx = canvas.getContext('2d');
            var PI = Math.PI;
            var start = PI*1.5;
            var gap = 0.01;
            var pros;
            var sum = 0;
            var zero = 0;
            for(var i=0; i<len; i++){
                if(data[i].isdraw == 0){
                    zero++;
                    continue;
                }
                sum+=data[i].num;
            }
            if(zero<len-1) {
                pros = 100-len+zero;
            }
            else{
                pros = 100;
            }
            ctx.strokeStyle = 'white';
            for(var i=0; i<len; i++) {
                var num = data[i].num;
                if(data[i].isdraw == 0){
                    continue;
                }
                var a_color= data[i].color;
                var end = start+2*PI*pros/100*num/sum;
                ctx.beginPath();
                ctx.moveTo(radius, radius);
                ctx.arc(radius, radius, radius, start, end, false);
                ctx.stroke();
                start = end + 2 * PI * gap;
                ctx.fillStyle = a_color;
                ctx.fill();
            }
            ctx.beginPath();
            ctx.arc(radius, radius, radius-width, 0, PI * 2, false);
            ctx.stroke();
            ctx.fillStyle = 'white';
            ctx.fill();
            ctx.fillStyle = 'black';
            ctx.font = (radius/5)+'px Simsun';
            ctx.fillText('总计',(canvas.width - ctx.measureText('总计').width)/2,canvas.height/2-radius/10);
            ctx.fillText(sum, (canvas.width - ctx.measureText(sum).width)/2,canvas.height/2+radius/10);
        }
    };
}

在一个空白html文件中导入该js文件即可使用(基本的html,body标签要有),不需导入其他的css文件。新建一个pieChart对象,调用add_data()draw()方法即可。
下面是实现思路的分享。

2.实现思路

使用JS绘制图形,那自然离不开canvas标签,这里我们最终绘制的是一个圆环,我的大体思路是:

  1. 根据数据计算所占比例,再根据所占比例使用较大半径绘制每部分的扇形。
  2. 所有部分绘制完毕,再使用一个较小的半径绘制一个白底的完整的圆。
  3. 填充文本。

也就是说,实际上我先是绘制的一个扇形统计图,然后用一个白底的较小的圆将其覆盖,这样看上去就是一个环形统计图了。
下面结合代码详细讲解;
window.onload外我封装了一个pieChart类,他有四个变量和两个方法:

四个变量

title:统计图的标题。

data:统计图的数据,每一条数据含下面五个内容:
①id:该项数据在html中的id值。
②text:该项数据在统计图下方显示的文本。
③num:该项数据的值(数量)。
④color:该项数据在统计图中对应的颜色。
⑤isdraw:是否绘制该项数据,只能填为1或0,默认填1,表示要绘制(在点击重绘时会用到该值)。

radius:外层圆的半径,单位为px。

width:圆环宽度,单位为px,可理解为外层圆与内层圆半径的差值。

两个方法

add_data()方法:负责添加统计图下方的每行内容。

draw()方法:绘制圆环的方法,同时也会绑定到每一行数据中。

3.方法详解

add_data()

    this.add_data = function(){
        var width = 2*radius;//区域宽度即为直径的长度,画布区域(canvas)的宽高等于直径,下方每一个li的宽度也等于直径。
        /***div 'chart',为整个页面的父div。***/
        var chart = document.createElement('div');
        chart.style.width = width+'px';
        /***div 'top',统计图标题区域***/
        var top = document.createElement('div');
        top.setAttribute('style','text-align:center;font-weight:bold;width:'+width+'px');
        top.innerText = title;
        /***canvas 'circle',圆环区域***/
        var circle = document.createElement('canvas');
        circle.setAttribute('id','circle');
        circle.setAttribute('width',width+"px");
        circle.setAttribute('height',width+"px");
        /***div 'list',数据行区域***/
        var list = document.createElement('div');
        list.setAttribute('id','list');
        var ul = document.createElement('ul');
        ul.setAttribute('style','font-family:Simsun;margin:0;padding:0;list-style:none;');
        /*每次循环添加data中的一条数据*/
        for(var i=0; i<data.length; i++){
            var li = document.createElement('li');
            li.setAttribute('id',data[i].id);
            li.setAttribute('style','width:'+width+'px;');
            li.style.color = data[i].color;
            li.innerHTML = "<span>• </span><span style='color:black;'>"+data[i].text+"</span><span style='color:gray;float:right'>"+data[i].num+"</span>";
            li.onclick = this.draw;//为每一行添加onclick事件
            ul.appendChild(li);
        }
        list.appendChild(ul);
        chart.appendChild(top);
        chart.appendChild(circle);
        chart.appendChild(list);
        document.body.appendChild(chart);
    }

实际上最终的页面结构为一个父div,包含三个部分:标题区域div 'top',图形区域canvas 'circle',数据区域div 'list';数据区域div 'list'包含一个无序列表ul,根据创建对象的数据条数创建一个个li,向其写入数据并绑定onclick事件draw();每个li又含三个span标签,分别代表:数据前缀(指定该行和统计图中哪个颜色对应),数据文本,文本对应的数量。
页面结构如下:

<div>
    <div></div>
    <canvas></canvas>
    <div>
        <ul>
            <li><span></span><span></span><span></span></li>
            <li><span></span><span></span><span></span></li>
            <li><span></span><span></span><span></span></li>
            <!--...-->
        </ul>
    </div>
</div>

draw()

代码片段一:

        var id = this.id;
        for(var i=0; i<len; i++){
            if(data[i].id == id){
                data[i].isdraw = data[i].isdraw? 0:1;
                this.style.color = this.style.color == 'gray'? data[i].color:'gray';
                var span = this.children[2];
                span.style.display = span.style.display=='none'? 'inline':'none';  
            }
        }

实际上draw()是为数据区域中的每一个li量身定做的,每一次对li标签的点击都会重绘图形,这时this指向的是被点击的li标签,如果它正在统计图中显示将会去除它,前缀变为灰色,数据值隐藏,isdraw标记为0,接下来的重绘将跳过对此项的绘制。
虽说draw()方法是为li服务的,但第一次生成统计图时也不用担心报错,这时的this标签并不会指向任何一个li标签,id赋值为undefined,虽然也会进入for循环,但始终不会进入if语句,对绘制不会产生任何影响。

代码片段二:

canvas.height = canvas.height;

每次重绘都会先清空画布,这里有个小技巧,重新设置画布的宽度或高度都会使画布清空。

代码片段三:

        /*绘制部分*/
        if (canvas.getContext) {
            var ctx = canvas.getContext('2d');
            var PI = Math.PI;
            var start = PI*1.5;//绘制开始位置
            var gap = 0.01;//两项数据间取的间隙,每个间隙占比1%
            var pros;//除去空隙后内容所占比例
            var sum = 0;//总和
            var zero = 0;//isdraw值为0数据个数(重绘过程不显示的数据个数)
            
            /*计算总和(sum)*/
            for(var i=0; i<len; i++){
                if(data[i].isdraw == 0){
                    zero++;
                    continue;
                }
                sum+=data[i].num;
            }
            
            /*在显示数据数大于等于二时,间隙数等于数据数,如只剩一个显示数据,间隙数为0*/
            if(zero<len-1) {
                pros = 100-len+zero;
            }
            else{
                pros = 100;
            }
            ctx.strokeStyle = 'white';
            
            /*一次循环即完成一个扇形的绘制*/
            for(var i=0; i<len; i++) {
                var num = data[i].num;
                if(data[i].isdraw == 0){
                    continue;
                }
                var a_color= data[i].color;
                var end = start+2*PI*pros/100*num/sum;//绘制结束的位置
                ctx.beginPath();
                ctx.moveTo(radius, radius);//移动至圆心坐标
                ctx.arc(radius, radius, radius, start, end, false);
                ctx.stroke();
                start = end + 2 * PI * gap;//下一次绘制开始的位置
                ctx.fillStyle = a_color;
                ctx.fill();
            }

            /*绘制小圆覆盖扇形的部分区域,使之最终为圆环的效果*/
            ctx.beginPath();
            ctx.arc(radius, radius, radius-width, 0, PI * 2, false);
            ctx.stroke();
            ctx.fillStyle = 'white';
            ctx.fill();

            /*填充文本*/
            ctx.fillStyle = 'black';
            ctx.font = (radius/5)+'px Simsun';
            ctx.fillText('总计',(canvas.width - ctx.measureText('总计').width)/2,canvas.height/2-radius/10);
            ctx.fillText(sum, (canvas.width - ctx.measureText(sum).width)/2,canvas.height/2+radius/10);
        }

务必理解到pros这个变量的意义,在之前的预览图里大家可以看到,每两个数据间是有空隙的,每次扇形比例的计算都是在去掉这些空白区域的前提下进行的,pros为【全部内容】在圆环上的比例,即除去空隙部分后剩余部分所占比例。
当数据显示项大于1时,间隙数等于数据数,只剩一项数据显示时,间隙数为0,该项内容占比100%。
若你对canvas的用法不够熟悉,建议先作一定了解,过程中多百度也是很好的选择。

对代码有疑问,欢迎评论;若你有其他的实现一个圆环的方式,欢迎评论;若你发现我文章中的错误,欢迎评论!感激不尽~

上一篇 下一篇

猜你喜欢

热点阅读