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

来源:互联网 发布:淘宝皇冠和天猫哪个好? 编辑:程序博客网 时间:2024/05/19 08:37

使用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>

这里写图片描述

原创粉丝点击