canvas图表详解系列(2):折线图
本章建议学习时间4小时
学习方式:详细阅读,并手动实现相关代码(如果没有canvas基础,需要先学习前面的canvas基础笔记)
学习目标:此教程将教会大家如何使用canvas绘制各种图表,详细分解步骤,本次讲解折线图。
源文件下载地址:https://github.com/sutianbinde/charts
折线图
折线图是前端最基本的图表之一,我们的案例展示效果如下
功能:横轴月份,纵轴访问量,图表会根据月份和访问量的多少自动调整高度和间距,高度会有由低到高的运动效果。点击图表会有刷新重载动画效果。
实现步骤
--新建Html文件,写入canvas标签,并且定义绘制图表的方法
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <style> .a{ background: #0bfefb; } </style> </head> <body> <canvas id="chart" height="400" width="600" style="margin:50px"> 你的浏览器不支持HTML5 canvas </canvas> <script type="text/javascript"> function goChart(dataArr){ } var chartData = [["2017/01", 50], ["2017/02", 60], ["2017/03", 100], ["2017/04",200], ["2017/05",350], ["2017/06",600]]; goChart(chartData); </script> </body> </html>
--在 goChart方法中定义需要使用的变量 并获取 canvas上下文 ,并且初始化图表
注:这里我们对高清屏幕显示模糊做了处理,具体的处理方式见代码注释
// 声明所需变量 var canvas,ctx; // 图表属性 var cWidth, cHeight, cMargin, cSpace; var originX, originY; // 折线图属性 var tobalDots, dotSpace, maxValue; var totalYNomber; // 运动相关变量 var ctr, numctr, speed; // 获得canvas上下文 canvas = document.getElementById("chart"); if(canvas && canvas.getContext){ ctx = canvas.getContext("2d"); } initChart(); // 图表初始化 // 图表初始化 function initChart(){ // 图表信息 cMargin = 60; cSpace = 80; /*这里是对高清屏幕的处理, 方法:先将canvas的width 和height设置成本来的两倍(本来希望的宽度为 window的宽度减去100px) 然后将style.height 和 style.width设置成本来的宽高 这样相当于把两倍的东西缩放到原来的大小,这样在高清屏幕上 一个像素的位置就可以有两个像素的值 这样需要注意的是所有的宽高间距,文字大小等都得设置成原来的两倍才可以。 */ canvas.width = Math.floor( (window.innerWidth-100)/2 ) * 2 ; canvas.height = 740; canvas.style.height = canvas.height/2 + "px"; canvas.style.width = canvas.width/2 + "px"; cHeight = canvas.height - cMargin - cSpace; cWidth = canvas.width - cMargin - cSpace; originX = cMargin + cSpace; originY = cMargin + cHeight; // 折线图信息 tobalDots = dataArr.length; dotSpace = parseInt( cWidth/tobalDots ); maxValue = 0; for(var i=0; i<dataArr.length; i++){ var dotVal = parseInt( dataArr[i][1] ); if( dotVal > maxValue ){ maxValue = dotVal; } } maxValue += 50; totalYNomber = 10; // 运动相关 ctr = 1; numctr = 100; speed = 6; ctx.translate(0.5,0.5); // 当只绘制1像素的线的时候,坐标点需要偏移,这样才能画出1像素实线 }
--绘制图表的轴和标记 (接着上一步的代码写在 goChart方法中 )
drawLineLabelMarkers(); // 绘制图表轴、标签和标记 // 绘制图表轴、标签和标记 function drawLineLabelMarkers(){ ctx.font = "24px Arial"; ctx.lineWidth = 2; ctx.fillStyle = "#566a80"; ctx.strokeStyle = "#566a80"; // y轴 drawLine(originX, originY, originX, cMargin); // x轴 drawLine(originX, originY, originX+cWidth, originY); // 绘制标记 drawMarkers(); } // 画线的方法 function drawLine(x, y, X, Y){ ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(X, Y); ctx.stroke(); ctx.closePath(); } // 绘制标记 function drawMarkers(){ ctx.strokeStyle = "#E0E0E0"; // 绘制 y 轴 及中间横线 var oneVal = parseInt(maxValue/totalYNomber); ctx.textAlign = "right"; for(var i=0; i<=totalYNomber; i++){ var markerVal = i*oneVal; var xMarker = originX-5; var yMarker = parseInt( cHeight*(1-markerVal/maxValue) ) + cMargin; ctx.fillText(markerVal, xMarker, yMarker+3, cSpace); // 文字 if(i>0){ drawLine(originX+2, yMarker, originX+cWidth, yMarker); } } // 绘制 x 轴 及中间竖线 ctx.textAlign = "center"; for(var i=0; i<tobalDots; i++){ var markerVal = dataArr[i][0]; var xMarker = originX+i*dotSpace; var yMarker = originY+30; ctx.fillText(markerVal, xMarker, yMarker, cSpace); // 文字 if(i>0){ drawLine(xMarker, originY-2, xMarker, cMargin ); } } // 绘制标题 y ctx.save(); ctx.rotate(-Math.PI/2); ctx.fillText("访问量", -canvas.height/2, cSpace-10); ctx.restore(); // 绘制标题 x ctx.fillText("月份", originX+cWidth/2, originY+cSpace/2+20); };
-- 绘制折线图(接着上一步的代码写在 goChart方法中 )
drawLineAnimate(); // 绘制折线图的动画 //绘制折线图 function drawLineAnimate(){ ctx.strokeStyle = "#566a80"; //"#49FE79"; //连线 ctx.beginPath(); for(var i=0; i<tobalDots; i++){ var dotVal = dataArr[i][1]; var barH = parseInt( cHeight*dotVal/maxValue* ctr/numctr );// var y = originY - barH; var x = originX + dotSpace*i; if(i==0){ ctx.moveTo( x, y ); }else{ ctx.lineTo( x, y ); } } ctx.stroke(); //背景 ctx.lineTo( originX+dotSpace*(tobalDots-1), originY); ctx.lineTo( originX, originY); //背景渐变色 //柱状图渐变色 var gradient = ctx.createLinearGradient(0, 0, 0, 300); gradient.addColorStop(0, 'rgba(133,171,212,0.6)'); gradient.addColorStop(1, 'rgba(133,171,212,0.1)'); ctx.fillStyle = gradient; ctx.fill(); ctx.closePath(); ctx.fillStyle = "#566a80"; //绘制点 for(var i=0; i<tobalDots; i++){ var dotVal = dataArr[i][1]; var barH = parseInt( cHeight*dotVal/maxValue * ctr/numctr ); var y = originY - barH; var x = originX + dotSpace*i; drawArc( x, y ); //绘制点 ctx.fillText(parseInt(dotVal*ctr/numctr), x+15, y-8); // 文字 } if(ctr<numctr){ ctr++; setTimeout(function(){ ctx.clearRect(0,0,canvas.width, canvas.height); drawLineLabelMarkers(); drawLineAnimate(); }, speed); } } //绘制圆点 function drawArc( x, y, X, Y ){ ctx.beginPath(); ctx.arc( x, y, 3, 0, Math.PI*2 ); ctx.fill(); ctx.closePath(); }
--当点击canvas的时候重新刷新图表(接着上一步的代码写在 goChart方法中 )
//点击刷新图表 canvas.onclick = function(){ initChart(); // 图表初始化 drawLineLabelMarkers(); // 绘制图表轴、标签和标记 drawLineAnimate(); // 绘制折线图的动画 };
这样我们整个代码就编写完成了,为了代码更便于阅读,我们可以将所有方法放到后面,把调用方法的代码放到前面,经过调整的全部代码如下
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <style> .a{ background: #0bfefb; } </style> </head> <body> <canvas id="chart" height="400" width="600" style="margin:50px"> 你的浏览器不支持HTML5 canvas </canvas> <script type="text/javascript"> function goChart(dataArr){ // 声明所需变量 var canvas,ctx; // 图表属性 var cWidth, cHeight, cMargin, cSpace; var originX, originY; // 折线图属性 var tobalDots, dotSpace, maxValue; var totalYNomber; // 运动相关变量 var ctr, numctr, speed; // 获得canvas上下文 canvas = document.getElementById("chart"); if(canvas && canvas.getContext){ ctx = canvas.getContext("2d"); } initChart(); // 图表初始化 drawLineLabelMarkers(); // 绘制图表轴、标签和标记 drawLineAnimate(); // 绘制折线图的动画 //点击刷新图表 canvas.onclick = function(){ initChart(); // 图表初始化 drawLineLabelMarkers(); // 绘制图表轴、标签和标记 drawLineAnimate(); // 绘制折线图的动画 }; // 图表初始化 function initChart(){ // 图表信息 cMargin = 60; cSpace = 80; /*这里是对高清屏幕的处理, 方法:先将canvas的width 和height设置成本来的两倍 然后将style.height 和 style.width设置成本来的宽高 这样相当于把两倍的东西缩放到原来的 1/2,这样在高清屏幕上 一个像素的位置就可以有两个像素的值 这样需要注意的是所有的宽高间距,文字大小等都得设置成原来的两倍才可以。 */ canvas.width = Math.floor( (window.innerWidth-100)/2 ) * 2 ; canvas.height = 740; canvas.style.height = canvas.height/2 + "px"; canvas.style.width = canvas.width/2 + "px"; cHeight = canvas.height - cMargin - cSpace; cWidth = canvas.width - cMargin - cSpace; originX = cMargin + cSpace; originY = cMargin + cHeight; // 折线图信息 tobalDots = dataArr.length; dotSpace = parseInt( cWidth/tobalDots ); maxValue = 0; for(var i=0; i<dataArr.length; i++){ var dotVal = parseInt( dataArr[i][1] ); if( dotVal > maxValue ){ maxValue = dotVal; } } maxValue += 50; totalYNomber = 10; // 运动相关 ctr = 1; numctr = 100; speed = 6; ctx.translate(0.5,0.5); // 当只绘制1像素的线的时候,坐标点需要偏移,这样才能画出1像素实线 } // 绘制图表轴、标签和标记 function drawLineLabelMarkers(){ ctx.font = "24px Arial"; ctx.lineWidth = 2; ctx.fillStyle = "#566a80"; ctx.strokeStyle = "#566a80"; // y轴 drawLine(originX, originY, originX, cMargin); // x轴 drawLine(originX, originY, originX+cWidth, originY); // 绘制标记 drawMarkers(); } // 画线的方法 function drawLine(x, y, X, Y){ ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(X, Y); ctx.stroke(); ctx.closePath(); } // 绘制标记 function drawMarkers(){ ctx.strokeStyle = "#E0E0E0"; // 绘制 y 轴 及中间横线 var oneVal = parseInt(maxValue/totalYNomber); ctx.textAlign = "right"; for(var i=0; i<=totalYNomber; i++){ var markerVal = i*oneVal; var xMarker = originX-5; var yMarker = parseInt( cHeight*(1-markerVal/maxValue) ) + cMargin; ctx.fillText(markerVal, xMarker, yMarker+3, cSpace); // 文字 if(i>0){ drawLine(originX+2, yMarker, originX+cWidth, yMarker); } } // 绘制 x 轴 及中间竖线 ctx.textAlign = "center"; for(var i=0; i<tobalDots; i++){ var markerVal = dataArr[i][0]; var xMarker = originX+i*dotSpace; var yMarker = originY+30; ctx.fillText(markerVal, xMarker, yMarker, cSpace); // 文字 if(i>0){ drawLine(xMarker, originY-2, xMarker, cMargin ); } } // 绘制标题 y ctx.save(); ctx.rotate(-Math.PI/2); ctx.fillText("访问量", -canvas.height/2, cSpace-10); ctx.restore(); // 绘制标题 x ctx.fillText("月份", originX+cWidth/2, originY+cSpace/2+20); }; //绘制折线图 function drawLineAnimate(){ ctx.strokeStyle = "#566a80"; //"#49FE79"; //连线 ctx.beginPath(); for(var i=0; i<tobalDots; i++){ var dotVal = dataArr[i][1]; var barH = parseInt( cHeight*dotVal/maxValue* ctr/numctr );// var y = originY - barH; var x = originX + dotSpace*i; if(i==0){ ctx.moveTo( x, y ); }else{ ctx.lineTo( x, y ); } } ctx.stroke(); //背景 ctx.lineTo( originX+dotSpace*(tobalDots-1), originY); ctx.lineTo( originX, originY); //背景渐变色 //柱状图渐变色 var gradient = ctx.createLinearGradient(0, 0, 0, 300); gradient.addColorStop(0, 'rgba(133,171,212,0.6)'); gradient.addColorStop(1, 'rgba(133,171,212,0.1)'); ctx.fillStyle = gradient; ctx.fill(); ctx.closePath(); ctx.fillStyle = "#566a80"; //绘制点 for(var i=0; i<tobalDots; i++){ var dotVal = dataArr[i][1]; var barH = parseInt( cHeight*dotVal/maxValue * ctr/numctr ); var y = originY - barH; var x = originX + dotSpace*i; drawArc( x, y ); //绘制点 ctx.fillText(parseInt(dotVal*ctr/numctr), x+15, y-8); // 文字 } if(ctr<numctr){ ctr++; setTimeout(function(){ ctx.clearRect(0,0,canvas.width, canvas.height); drawLineLabelMarkers(); drawLineAnimate(); }, speed); } } //绘制圆点 function drawArc( x, y, X, Y ){ ctx.beginPath(); ctx.arc( x, y, 3, 0, Math.PI*2 ); ctx.fill(); ctx.closePath(); } } var chartData = [["2017/01", 50], ["2017/02", 60], ["2017/03", 100], ["2017/04",200], ["2017/05",350], ["2017/06",600]]; goChart(chartData); </script> </body> </html>
好了,今天就讲到这里,希望大家把代码都自己敲一遍。
关注公众号,博客更新即可收到推送