美文网首页
HTML5 Canvas(实战:绘制饼图)

HTML5 Canvas(实战:绘制饼图)

作者: Sue1024 | 来源:发表于2018-05-13 21:42 被阅读0次

    有了canvas之后,我们可以很容易地创建一个简单图标,不需要任何插件,不过,有的小伙伴觉得它很难,笔者仔细思考一番之后,只能吐嘈一下他们的绘图技能...
    于是在开始绘制之前,我们首先画一下草图~

    Make It Reusable

    为了创建一个可以重用,并且可以灵活地重用的饼图,笔者决定最终的创建饼图方法接收两个参数,分别是要显示的数据data,绘制参数options

    Data

    Data From Server

    在实际应用场景中,我们从后端拿到的往往是诸如几个年份的产量一类的数据,比如(这里,我们为了简化代码,将颜色也放到了后台返回的数据中):

    var data = [
                  {
                    data: 10,
                    color: "red",
                    label: "2016"
                  },
                  {
                    data: 15,
                    color: "grey",
                    label: "2017"
                  },
                  {
                    data: 15,
                    color: "black",
                    label: "2018"
                  }
    ];
    
    To Process Data

    而绘制饼图时, 我们需要根据比例"分饼", 并且在某些地方显示出实际的数据(比如tooltip),因此我们需要一个如下的数据处理函数:

    function calculateData(data) {
      if(data instanceof Array) {
        var sum = data.reduce(function(a, b) {
          return a + b.data;
        }, 0);
        var map = data.map(function(a) {
          return {
            label: a.label,
            data: a.data,
            color: a.color,
            portion: a.data/sum
          }
        });
        return map;
      }      
    }
    

    Options

    另外,即使我们可以根据不同的数据绘制不同的图表,恐怕也只能满足个别需求,毕竟每个人的喜好都不一样,我们需要创建一个可以显示不同数据,又可以拥有不同排版、不同布局的图表,实现上述目标,我们需要如下参数列表:

    var options = {
        legend: {
            font: {
              size: 18,
              family: 'Arial',
              weight: 'bold'
            }
        },
        title: {
            text: 'Pie Chart',
            font: {
              size: 18,
              family: 'Arial',
              weight: 'bold'
            }
        },
        tooltip: {
            template: '<div>Year: {{label}}</div><div>Production: {{data}}</div>',
            font: {
              size: 18,
              family: 'Arial',
              weight: 'bold'
            }
        }
    }
    

    Canvas

    我们的工具函数不应该可以提前知道用户想要用来绘制图表的canvas,用户可能想在页面中的多个canvas上绘制图表,因此工具函数应该可以接受一个参数,用来确定绘制图表的canvas,很多开源库都使用id作为识别canvas的标识,笔者认为接收element更好一些,因为不是所有的用户都愿意给canvas添加ID属性, 有的时候,用户想给拥有某一个class属性的所有canvas批量绘图,并根据它们的dataset属性动态的生成数据。
    综上,最后我们的工具函数应该长成下面这个样子:

    function drawPie(canvas, data, option) {
    // To Do
    }
    

    Start Coding

    Get Context

    首先获取绘图上下文,仍要注意先判断是否存在getContext()方法。

    var canvas = document.getElementById("canvas");
    if(canvas.getContext) {
      var ctx = canvas.getContext("2d");
    }
    

    Generate Options

    然后,我们需要将自定义的参数和默认参数合并在一起,组成一个新的完整的参数列表,原则就是没有自定义的都采用默认值。

    function mergeJSON(source1,source2){
      var mergedJSON = JSON.parse(JSON.stringify(source2));
      for (var attrname in source1) {
        if(mergedJSON.hasOwnProperty(attrname)) {
          if ( source1[attrname]!=null && source1[attrname].constructor==Object ) {
            mergedJSON[attrname] = mergeJSON(source1[attrname], mergedJSON[attrname]);
          }
        } else {
          mergedJSON[attrname] = source1[attrname];
        }
      }
        return mergedJSON;
    }
    
    function generateOptions(givenOptions, defaultOptions) {
      return mergeJSON(defaultOptions, givenOptions);
    }
    

    Draw Title

    把标题绘制在画布顶部的中间,距离页面顶部留有20像素的空隙,并且根据参数,绘制具有特定内容和样式的标题。

    var width = canvas.width,
        height = canvas.height,
        op = generateOptions(options, defaultOptions),
        title_text = op.title.text,
        title_position = {};
    ctx.font = op.title.font.weight + " " + op.title.font.size+"px " + op.title.font.family;
    title_position .x = (width - title_width)/2;
    title_position.y = 20 + op.title.font.size;
    title_width = ctx.measureText(title_text).width, title_height = op.title.font.size;
    ctx.fillText(title_text, title_position.x, title_position.y);
    

    Radius & Center

    笔者决定使饼图距离标题有30像素的空隙,距离左边框和底部分别留有20像素的空隙,因此它的半径和圆心分别是:

    var radius = (height - title_height - title_position.y - 20) / 2 ;
    var center = {
      x: radius + 20,
      y: radius + 30 + title_position.y
    };
    

    Legend

    图例的高设置为图例字体大小的1.2倍,宽设置为图例字体大小的2.5倍,距离饼图40像素的间隙,第一个图例顶部距离页面顶端80像素,文字距离图例5像素,垂直居中,于是图例的大体信息总结如下:

    var legend_width = op.legend.font.size * 2.5, 
        legend_height = op.legend.font.size * 1.2,
        legend_posX = center.x * 2 +20, 
        legend_posY = 80,
        legend_textX = legend_posX + legend_width + 5, 
        legend_textY = legend_posY + op.legend.font.size * 0.9;
    

    Draw Pie & Legends

    Border

    先给图表加一个边框

      ctx.strokeStyle = 'grey';
      ctx.lineWidth = 3;
      ctx.strokeRect(0, 0, canvas.width, canvas.height);
    
    Pie & Legends

    遍历数据绘图。

    var data_c = calculateData(data);
    var startAngle = 0, endAngle = 0;
    for(var i=0, len=data.length; i<len; i++) {
        endAngle += data_c[i].portion * 2*Math.PI;
        ctx.fillStyle = data_c[i].color;
        ctx.beginPath();
        ctx.moveTo(center.x, center.y);
        ctx.arc(center.x, center.y, radius, startAngle, endAngle, false);
        ctx.closePath();
        ctx.fill();
        startAngle = endAngle;
        ctx.fillRect(legend_posX, legend_posY + (10 + legend_height) * i, legend_width, legend_height);
        ctx.font = 'bold 12px Arial';
        var percent = data_c[i].label + ' : ' + (data_c[i].portion*100).toFixed(2) + '%';
        ctx.fillText(percent, legend_textX, legend_textY + (10 + legend_height) * i);
    }
    
    

    Let's try it!

    我们的工具函数已经做到一半啦,可以画出一个带有图例的饼图,并且标题和图例文字大小 粗细 字体均可配置,下面试一下灵不灵~

    var init = function(){
        var data = [
                  {
                    data: 10,
                    color: "red",
                    label: "2016"
                  },
                  {
                    data: 15,
                    color: "grey",
                    label: "2017"
                  },
                  {
                    data: 15,
                    color: "black",
                    label: "2018"
                  }
        ];
        var options = {
            title: {
                text: 'Production By Year',
                font: {
                      size: 30
                }
            }
        }
        drawCircle(data, document.getElementById("drawing"), options);
    };
    init();
    

    画出来的饼图长这个样子~

    下一篇笔者会加上Tooltip的绘制哦,那部分比较复杂,默默地给自己加油~

    相关文章

      网友评论

          本文标题:HTML5 Canvas(实战:绘制饼图)

          本文链接:https://www.haomeiwen.com/subject/sixprftx.html