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