【自己的整理】【jQuery插件】 使用canvas创建折线图

使用canvas创建折线图

一个网友的很常规的需求,要根据数据在一个canvas画布元素上画一个折线图,最开始做了一个很土的版本,现在自己想想还是弄个插件吧于是就有了这篇文章。

创建jquery插件标准模板

既然是一个插件就要按照基本法来,所以先要创建一个标准的jquery插件模板:

<!DOCTYPE html>
<html>
    <head>
        <title>用canvas根据数据创建折线图</title>
        <script src="http://code.jquery.com/jquery-latest.js"></script>             
        <script>
        (function($){
            var methods = {
                    init:function(options){
                        var chart = $.extend({
                        'ChartTitle': '三人成绩折线图',//表头
                            'defaultLineColor':'rgba(0,0,0,0.4)',//折线图的坐标以及辅助线颜色
                            'defaultTextColor':'rgba(0,0,0,0.7)',//字体颜色
                            'canvasWidth':800,//画布宽度
                            'canvasHeight':600,//画布高度
                            'chartWidth':600,//图标宽度
                            'chartHeight':400,//图表高度
                            'x_metric':[1,2,3,4,5,6],//月份 下面X轴的计量数
                            'data':[{
                                "name":"小红",
                                "details":[78,89,86,88,79,88],//数据
                                "color":"rgba(255,127,127,0.7)"//连线以及点的颜色
                                },{
                                "name":"小明",
                                "details":[86,77,69,76,60,98],
                                "color":"rgba(127,255,127,0.7)"
                                },//数据二维数组
                                {
                                "name":"小兰",
                                "details":[80,77,66,62,99,65],
                                "color":"rgba(127,127,255,0.7)"
                                }],
                            'startLine':0,//左边开始的时候的数据
                            'endLine':120,//左边最高点的数据
                            'line_number':4,//中间画分成几格子
                            'x_name':'月份',//纵坐标的数量单位
                            'y_name':'分数'//横坐标的数量单位
                        },options);
                        //下面用于检测是否在已经存在的画布上画图,还是重新生成一个。
                        var canvasElement,ctx;
                        if($(this) && $(this).prop("tagName") == "CANVAS"){
                            var jQueryCanvasObject = $(this);
                            console.log(jQueryCanvasObject);
                            canvasElement = jQueryCanvasObject.get(0);
                            console.log(canvasElement);
                            ctx = canvasElement.getContext('2d');
                            canvasElement.width = chart.canvasWidth;
                            canvasElement.height = chart.canvasHeight;
                        }else if($(this) && typeof($(this).context) != 'undefined'){
                            var canvasindex = 0;
                            for(var canvasindex;$('#drawChartCanvas'+canvasindex+'').length > 0;canvasindex ++ ){

                            }
                            $(this).append("<canvas width="+chart.canvasWidth+" height="+chart.canvasHeight+" id='drawChartCanvas"+canvasindex+"'></canvas>");
                            canvasElement = $("#drawChartCanvas"+canvasindex).get(0);
                            ctx = canvasElement.getContext('2d');
                        }
                        else{
                            var canvasindex = 0;
                            for(var canvasindex;$('#drawChartCanvasInBody'+canvasindex+'').length > 0;canvasindex ++ ){
                                console.log(canvasindex);
                            }
                            $('body').append("<canvas width="+chart.canvasWidth+" height="+chart.canvasHeight+" id='drawChartCanvasInBody"+canvasindex+"'></canvas>");
                            canvasElement = $("#drawChartCanvasInBody"+canvasindex).get(0);
                            ctx = canvasElement.getContext('2d');
                        }
                    },
                    destory:function(){},//destory方法 待补充
                    animate:function(){}//其他方法
                };
                $.fn.drawChart = function (method){
                    if(methods[method]){
                        return methods[method].apply(this,Array.prototype.slice.call(arguments,1));
                    }else if(typeof method === 'object' || !method){
                        return methods.init.apply(this,arguments);
                    }else{
                        $.error('方法'+method+'在画折线图插件中不存在');
                    }
                }
            })(jQuery);
        </script>
        <script>
            $(document).ready(function(){$().drawChart()});
        </script>
    </head>
    <body>
    </body>
</html>

到目前为止只是创建了一个默认的折线图数据对象chart,其中包含了诸多信息,比如表头,表的线条颜色等,可以根据自己的喜好改变其中某部分的设置。
我们要开始画一个折线图的时候,最开始画的应该是横竖两条x轴y轴辅助线,加上代表数量的辅助线,以及单位名称、度量等。我们在插件中创建一个新的方法把它叫做drawAxis(),这是一个画辅助线的方法,并不需要用到数据

function drawAxis(ctx,chart){
                    ctx.font = "30px microsoft yahei";
                    ctx.fillStyle = chart.defaultTextColor;
                    ctx.fillText(chart.ChartTitle,chart.canvasWidth/2-100,40);
                    ctx.lineWidth = 4;
                    ctx.strokeStyle = chart.defaultLineColor;
                    ctx.lineCap = "round";
                    var bottomLeft = {'x':(chart.canvasWidth - chart.chartWidth)/2,
                                      'y':(chart.canvasHeight - chart.chartHeight)/2+chart.chartHeight
                                    };//左下角点坐标
                    var topRight = {'x':chart.canvasWidth - ((chart.canvasWidth - chart.chartWidth)/2),
                                    'y':(chart.canvasHeight-chart.chartHeight)/2};//右上角点坐标
                    ctx.moveTo((chart.canvasWidth-chart.chartWidth)/2,(chart.canvasHeight-chart.chartHeight)/2+chart.chartHeight);//坐标轴原点
                    ctx.lineTo((chart.canvasWidth-chart.chartWidth)/2,(chart.canvasHeight-chart.chartHeight)/2);//坐标轴y轴上部点
                    var y_metric = chart.chartHeight/chart.line_number;
                    var between = (chart.endLine - chart.startLine)/(chart.line_number);
                    for(var i = 0; i < chart.line_number; i++){
                        ctx.moveTo(bottomLeft.x,bottomLeft.y-i*y_metric);
                        ctx.lineTo(bottomLeft.x+chart.chartWidth,bottomLeft.y-i*y_metric);
                        ctx.font = '20px microsoft yahei';
                        ctx.fillText(Math.floor(between * i + chart.startLine),bottomLeft.x-40,bottomLeft.y-i*y_metric);
                    };
                    for(var i = 0;i < chart.x_metric.length; i++){
                        ctx.moveTo(
                            i*(chart.chartWidth/chart.x_metric.length)+(chart.canvasWidth-chart.chartWidth)/2+(chart.chartWidth/chart.x_metric.length)/2,
                            chart.chartHeight + (chart.canvasHeight-chart.chartHeight)/2
                        );
                        ctx.lineTo(
                            i*(chart.chartWidth/chart.x_metric.length)+(chart.canvasWidth-chart.chartWidth)/2+(chart.chartWidth/chart.x_metric.length)/2,
                            chart.chartHeight + (chart.canvasHeight-chart.chartHeight)/2 + 5
                        );
                        ctx.font = "20px microsoft yahei";
                        ctx.fillText(chart.x_metric[i],
                            i*(chart.chartWidth/chart.x_metric.length)+(chart.canvasWidth-chart.chartWidth)/2+(chart.chartWidth/chart.x_metric.length)/2 - 6,
                            chart.chartHeight + (chart.canvasHeight-chart.chartHeight)/2 + 25
                        );
                    }
                    ctx.stroke();
                    ctx.font = "22px microsoft yahei";
                    ctx.fillText(chart.x_name,topRight.x + 3,bottomLeft.y);
                    ctx.fillText(chart.y_name,bottomLeft.x - 3,topRight.y - 10);
                    for(var i = 0;i < chart.data.length; i++){
                        ctx.strokeStyle = chart.data[i].color;
                        ctx.beginPath();
                        ctx.moveTo(chart.canvasWidth-(chart.canvasWidth-chart.chartWidth)/2, (chart.canvasHeight-chart.chartHeight)/2 +i*25);
                        ctx.lineTo(chart.canvasWidth-(chart.canvasWidth-chart.chartWidth)/2 + 18, (chart.canvasHeight-chart.chartHeight)/2 +i*25);
                        ctx.stroke();
                        ctx.fillText(chart.data[i].name,
                                     chart.canvasWidth-(chart.canvasWidth-chart.chartWidth)/2 + 20, 
                                     (chart.canvasHeight-chart.chartHeight)/2 +i*25 +6
                                    );
                    }
                }

然后我们需要在插件的初始化方法中加入这个方法

init:function(options){
    //...省略代码
    drawAxis(ctx,chart);
}

这样完成之后我们就可以看到还没有折线的一个图标,只有辅助线、表头、图例、以及单位等。
这里写图片描述
在我们在自己的本子上画折线图的时候,首先是要画各个点,然后再把这些点用线连起来,那么我们就可以按照这个思路来操作。
最开始我们先计算一下点的位置:

function initPointPosition(chart){
    var points = [];
    for(var g = 0;g < chart.data.length;g++){
        var pointsGroup = [];
        for(var i = 0;i < chart.data[g].details.length; i++ ){
            //获取数据
            var tempNumber = chart.data[g].details[i];
            //算出百分比高度位置
            var yPercentHeight = ((tempNumber - chart.startLine)/(chart.endLine-chart.startLine))*chart.chartHeight;
            //确定位置
            var pointPosition = {
                'detail':chart.data[g].details[i],
                'x':chart.chartWidth/(chart.x_metric.length*2)+i*(chart.chartWidth/chart.x_metric.length)+(chart.canvasWidth-chart.chartWidth)/2,
                'y':chart.chartHeight+(chart.canvasHeight-chart.chartHeight)/2-yPercentHeight ,
                'color':chart.data[g].color
            };
            pointsGroup.push(pointPosition);
        }
        points.push(pointsGroup);
    }
    return points;
}

这个方法可以根据数据来返回一个二维对象数组,数组内的一级元素表示了有多少组点,而二级对象中包含了点的位置,颜色,以及数据等信息。当然这还只是数据层面的,我们还需要把这些点画出来,所以需要加入一个可以把这个二维数组的点画出来的方法:

function drawPoints(ctx,points){
    for(var i = 0; i < points.length; i ++){
        for(var j = 0; j < points[i].length; j++){
            ctx.fillStyle = points[i][j].color;
            ctx.beginPath();
            ctx.arc(points[i][j].x,points[i][j].y,4,0,Math.PI*2,true);
            ctx.closePath();
            ctx.fill();
        }
    }
};

我们需要在init(初始化)方法的最后调用这个方法:

init:function(options){
    //省略的代码
    drawAxis(ctx,chart);
    var points = initPointPosition(chart);
    drawPoints(ctx,points);
}

此时我们再刷新一下浏览器,可以看到点已经呈现在折线图上面了:
这里写图片描述
这个时候就要把点都连起来,因为之前点的位置已经存在了,所以连点并不难,我们在插件中加入一个将二维数组的点连起来的方法:

function drawLine(ctx,points){
    ctx.lineWidth = 3.5;
    for(var i = 0; i < points.length; i++){
        ctx.beginPath();
        for(var j = 0; j < points[i].length-1; j++){
            ctx.strokeStyle = points[i][j].color;
            ctx.moveTo(points[i][j].x,points[i][j].y);
            ctx.lineTo(points[i][j+1].x,points[i][j+1].y);
        }
        ctx.stroke();
    }
};

然后同样在init方法的最后加入:

init:function(options){
    drawAxis(ctx,chart);
    var points = initPointPosition(chart);
    drawPoints(ctx,points);
    drawLine(ctx,points);
}

此时我们再刷新一下浏览器,可以看到点之间的连线也连好了。
这里写图片描述
这个时候可以看到连线已经完成,但是在折线图中看不到具体的数值,这个大小的比较还是有些看不出来,所以我们还要在点的旁边画上数据,同样新建一个方法用来绘制具体数值:

function drawData(ctx,points,chart){
    ctx.fillStyle = chart.defaultTextColor;
    ctx.font = "18px microsoft yahei";
    for(var i = 0;i < points.length; i++){
        for(var j = 0;j< points[i].length;j++){
            ctx.fillText(points[i][j].detail,points[i][j].x,points[i][j].y);
        }
    }
}

同理要在init方法最后加入绘制具体数值的方法:

init:function(options){
    //省略的内容
    drawAxis(ctx,chart);
    var points = initPointPosition(chart);
    drawPoints(ctx,points);
    drawData(ctx,points,chart);
}

最后再次刷新一下浏览器,可以看到一个较为完整的折线图:
这里写图片描述
看起来有些紧凑,想要把它变得不那么紧凑的话,可以改变一些设置:

<script>
    $().drawChart({"startLine":50,"endLine":100});
</script>

这里写图片描述

测试地址

canvas绘制折线图插件测试 可能失效日期2019-1-12

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值