本章建议学习时间4小时

学习方式:详细阅读,并手动实现相关代码(如果没有canvas基础,需要先学习前面的canvas基础笔记)

学习目标:此教程将教会大家如何使用canvas绘制各种图表,详细分解步骤,本次讲解饼状图。

演示地址:  https://sutianbinde.github.io/charts/%E9%A5%BC%E7%8A%B6%E5%9B%BE-%E9%AB%98%E6%B8%85.html

源文件下载地址:https://github.com/sutianbinde/charts

饼状图


饼状图是前端最基本的图表之一,我们的案例展示效果如下

canvas图表详解系列(3):动态饼状图(原生Js仿echarts饼状图)-LMLPHP

功能:图表可以根据数据自动变换比例,旋转绘制的动画,鼠标移入到对应模块会实现颜色变化。

实现步骤


--新建Html文件,写入canvas标签,并且定义绘制图表的方法(我们js中的canvas宽高根据canvas父级标签的宽高来设置,希望大家写的时候一定给canvas添加父级div并指定宽高)

<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
canvas{
border: 1px solid #A4E2F9;
}
</style>
</head>
<body>
<div height="400" width="600" style="margin:50px">
<canvas id="chart"> 你的浏览器不支持HTML5 canvas </canvas>
</div> <script type="text/javascript">
function goChart(dataArr){ } var chartData = [[50,"#2dc6c8","瓜子"], [100,"#b6a2dd", "花生"], [200,"#5ab1ee","土豆"], [700,"#d7797f","南瓜四号"]]; goChart(chartData); </script>
</body>
</html>

--在 goChart方法中定义需要使用的变量 并获取 canvas上下文

            // 声明所需变量
var canvas,ctx;
// 图表属性
var cWidth, cHeight, cMargin, cSpace;
// 饼状图属性
var radius,ox,oy;//半径 圆心
var tWidth, tHeight;//图例宽高
var posX, posY, textX, textY;
var startAngle, endAngle;
var totleNb;
// 运动相关变量
var ctr, numctr, speed;
//鼠标移动
var mousePosition = {}; //线条和文字
var lineStartAngle,line,textPadding,textMoveDis; // 获得canvas上下文
canvas = document.getElementById("chart");
if(canvas && canvas.getContext){
ctx = canvas.getContext("2d");
}

--初始化图表(接着上一步的代码写在 goChart方法中 )

            initChart();

            // 图表初始化
function initChart(){
// 图表信息
cMargin = 20;
cSpace = 40; canvas.width = canvas.parentNode.getAttribute("width")* 2 ;
canvas.height = canvas.parentNode.getAttribute("height")* 2;
canvas.style.height = canvas.height/2 + "px";
canvas.style.width = canvas.width/2 + "px";
cHeight = canvas.height - cMargin*2;
cWidth = canvas.width - cMargin*2; //饼状图信息
radius = cHeight*2/6; //半径 高度的2/6
ox = canvas.width/2 + cSpace; //圆心
oy = canvas.height/2;
tWidth = 60; //图例宽和高
tHeight = 20;
posX = cMargin;
posY = cMargin; //
textX = posX + tWidth + 15
textY = posY + 18;
startAngle = endAngle = 90*Math.PI/180; //起始弧度 结束弧度
rotateAngle = 0; //整体旋转的弧度 //将传入的数据转化百分比
totleNb = 0;
new_data_arr = [];
for (var i = 0; i < dataArr.length; i++){
totleNb += dataArr[i][0];
}
for (var i = 0; i < dataArr.length; i++){
new_data_arr.push( dataArr[i][0]/totleNb );
}
totalYNomber = 10;
// 运动相关
ctr = 1;//初始步骤
numctr = 50;//步骤
speed = 1.2; //毫秒 timer速度 //指示线 和 文字
lineStartAngle = -startAngle;
line=40; //画线的时候超出半径的一段线长
textPadding=10; //文字与线之间的间距
textMoveDis = 200; //文字运动开始的间距
}

--绘制板块图例

canvas图表详解系列(3):动态饼状图(原生Js仿echarts饼状图)-LMLPHP

            drawMarkers();
//绘制比例图及文字
function drawMarkers(){
ctx.textAlign="left";
for (var i = 0; i < dataArr.length; i++){
//绘制比例图及文字
ctx.fillStyle = dataArr[i][1];
ctx.fillRect(posX, posY + 40 * i, tWidth, tHeight);
ctx.moveTo(posX, posY + 40 * i);
ctx.font = 'normal 24px 微软雅黑'; //斜体 30像素 微软雅黑字体
ctx.fillStyle = dataArr[i][1]; //"#000000";
var percent = dataArr[i][2] + ":" + parseInt(100 * new_data_arr[i]) + "%";
ctx.fillText(percent, textX, textY + 40 * i);
}
};

--绘制饼状图动画(接着上一步的代码写在 goChart方法中 )

注:绘制饼状图动画的方法连续的可能更利于查看,所以就没有拆分开,作了必要的注释,不理解的可留言

canvas图表详解系列(3):动态饼状图(原生Js仿echarts饼状图)-LMLPHP

            //绘制动画
pieDraw();
function pieDraw(mouseMove){ for (var n = 0; n < dataArr.length; n++){
ctx.fillStyle = ctx.strokeStyle = dataArr[n][1];
ctx.lineWidth=1;
var step = new_data_arr[n]* Math.PI * 2; //旋转弧度
var lineAngle = lineStartAngle+step/2; //计算线的角度
lineStartAngle += step;//结束弧度 ctx.beginPath();
var x0=ox+radius*Math.cos(lineAngle),//圆弧上线与圆相交点的x坐标
y0=oy+radius*Math.sin(lineAngle),//圆弧上线与圆相交点的y坐标
x1=ox+(radius+line)*Math.cos(lineAngle),//圆弧上线与圆相交点的x坐标
y1=oy+(radius+line)*Math.sin(lineAngle),//圆弧上线与圆相交点的y坐标
x2=x1,//转折点的x坐标
y2=y1,
linePadding=ctx.measureText(dataArr[n][2]).width+10; //获取文本长度来确定折线的长度 ctx.moveTo(x0,y0);
//对x1/y1进行处理,来实现折线的运动
yMove = y0+(y1-y0)*ctr/numctr;
ctx.lineTo(x1,yMove);
if(x1<=x0){
x2 -= line;
ctx.textAlign="right";
ctx.lineTo(x2-linePadding,yMove);
ctx.fillText(dataArr[n][2],x2-textPadding-textMoveDis*(numctr-ctr)/numctr,y2-textPadding);
}else{
x2 += line;
ctx.textAlign="left";
ctx.lineTo(x2+linePadding,yMove);
ctx.fillText(dataArr[n][2],x2+textPadding+textMoveDis*(numctr-ctr)/numctr,y2-textPadding);
} ctx.stroke(); } //设置旋转
ctx.save();
ctx.translate(ox, oy);
ctx.rotate((Math.PI*2/numctr)*ctr/2); //绘制一个圆圈
ctx.strokeStyle = "rgba(0,0,0,"+ 0.5*ctr/numctr +")"
ctx.beginPath();
ctx.arc(0, 0 ,(radius+20)*ctr/numctr, 0, Math.PI*2, false);
ctx.stroke(); for (var j = 0; j < dataArr.length; j++){ //绘制饼图
endAngle = endAngle + new_data_arr[j]* ctr/numctr * Math.PI * 2; //结束弧度 ctx.beginPath();
ctx.moveTo(0,0); //移动到到圆心
ctx.arc(0, 0, radius*ctr/numctr, startAngle, endAngle, false); //绘制圆弧 ctx.fillStyle = dataArr[j][1];
if(mouseMove && ctx.isPointInPath(mousePosition.x*2, mousePosition.y*2)){
ctx.globalAlpha = 0.8;
} ctx.closePath();
ctx.fill();
ctx.globalAlpha = 1; startAngle = endAngle; //设置起始弧度
if( j == dataArr.length-1 ){
startAngle = endAngle = 90*Math.PI/180; //起始弧度 结束弧度
}
} ctx.restore(); if(ctr<numctr){
ctr++;
setTimeout(function(){
//ctx.clearRect(-canvas.width,-canvas.width,canvas.width*2, canvas.height*2);
ctx.clearRect(-canvas.width, -canvas.height,canvas.width*2, canvas.height*2);
drawMarkers();
pieDraw();
}, speed*=1.085);
}
}

--监听鼠标移动,以实现移动到当前项作颜色变化(接着上一步的代码写在 goChart方法中 )

            //监听鼠标移动
var mouseTimer = null;
canvas.addEventListener("mousemove",function(e){
e = e || window.event;
if( e.offsetX || e.offsetX==0 ){
mousePosition.x = e.offsetX;
mousePosition.y = e.offsetY;
}else if( e.layerX || e.layerX==0 ){
mousePosition.x = e.layerX;
mousePosition.y = e.layerY;
} clearTimeout(mouseTimer);
mouseTimer = setTimeout(function(){
ctx.clearRect(0,0,canvas.width, canvas.height);
drawMarkers();
pieDraw(true);
},10);
});

--这样我们整个代码就编写完成了

全部代码如下:

<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
canvas{
border: 1px solid #A4E2F9;
}
</style>
</head>
<body>
<div height="400" width="600" style="margin:50px">
<canvas id="chart"> 你的浏览器不支持HTML5 canvas </canvas>
</div> <script type="text/javascript">
function goChart(dataArr){ // 声明所需变量
var canvas,ctx;
// 图表属性
var cWidth, cHeight, cMargin, cSpace;
// 饼状图属性
var radius,ox,oy;//半径 圆心
var tWidth, tHeight;//图例宽高
var posX, posY, textX, textY;
var startAngle, endAngle;
var totleNb;
// 运动相关变量
var ctr, numctr, speed;
//鼠标移动
var mousePosition = {}; //线条和文字
var lineStartAngle,line,textPadding,textMoveDis; // 获得canvas上下文
canvas = document.getElementById("chart");
if(canvas && canvas.getContext){
ctx = canvas.getContext("2d");
}
initChart(); // 图表初始化
function initChart(){
// 图表信息
cMargin = 20;
cSpace = 40; canvas.width = canvas.parentNode.getAttribute("width")* 2 ;
canvas.height = canvas.parentNode.getAttribute("height")* 2;
canvas.style.height = canvas.height/2 + "px";
canvas.style.width = canvas.width/2 + "px";
cHeight = canvas.height - cMargin*2;
cWidth = canvas.width - cMargin*2; //饼状图信息
radius = cHeight*2/6; //半径 高度的2/6
ox = canvas.width/2 + cSpace; //圆心
oy = canvas.height/2;
tWidth = 60; //图例宽和高
tHeight = 20;
posX = cMargin;
posY = cMargin; //
textX = posX + tWidth + 15
textY = posY + 18;
startAngle = endAngle = 90*Math.PI/180; //起始弧度 结束弧度
rotateAngle = 0; //整体旋转的弧度 //将传入的数据转化百分比
totleNb = 0;
new_data_arr = [];
for (var i = 0; i < dataArr.length; i++){
totleNb += dataArr[i][0];
}
for (var i = 0; i < dataArr.length; i++){
new_data_arr.push( dataArr[i][0]/totleNb );
}
totalYNomber = 10;
// 运动相关
ctr = 1;//初始步骤
numctr = 50;//步骤
speed = 1.2; //毫秒 timer速度 //指示线 和 文字
lineStartAngle = -startAngle;
line=40; //画线的时候超出半径的一段线长
textPadding=10; //文字与线之间的间距
textMoveDis = 200; //文字运动开始的间距
} drawMarkers();
//绘制比例图及文字
function drawMarkers(){
ctx.textAlign="left";
for (var i = 0; i < dataArr.length; i++){
//绘制比例图及文字
ctx.fillStyle = dataArr[i][1];
ctx.fillRect(posX, posY + 40 * i, tWidth, tHeight);
ctx.moveTo(posX, posY + 40 * i);
ctx.font = 'normal 24px 微软雅黑'; //斜体 30像素 微软雅黑字体
ctx.fillStyle = dataArr[i][1]; //"#000000";
var percent = dataArr[i][2] + ":" + parseInt(100 * new_data_arr[i]) + "%";
ctx.fillText(percent, textX, textY + 40 * i);
}
}; //绘制动画
pieDraw();
function pieDraw(mouseMove){ for (var n = 0; n < dataArr.length; n++){
ctx.fillStyle = ctx.strokeStyle = dataArr[n][1];
ctx.lineWidth=1;
var step = new_data_arr[n]* Math.PI * 2; //旋转弧度
var lineAngle = lineStartAngle+step/2; //计算线的角度
lineStartAngle += step;//结束弧度 ctx.beginPath();
var x0=ox+radius*Math.cos(lineAngle),//圆弧上线与圆相交点的x坐标
y0=oy+radius*Math.sin(lineAngle),//圆弧上线与圆相交点的y坐标
x1=ox+(radius+line)*Math.cos(lineAngle),//圆弧上线与圆相交点的x坐标
y1=oy+(radius+line)*Math.sin(lineAngle),//圆弧上线与圆相交点的y坐标
x2=x1,//转折点的x坐标
y2=y1,
linePadding=ctx.measureText(dataArr[n][2]).width+10; //获取文本长度来确定折线的长度 ctx.moveTo(x0,y0);
//对x1/y1进行处理,来实现折线的运动
yMove = y0+(y1-y0)*ctr/numctr;
ctx.lineTo(x1,yMove);
if(x1<=x0){
x2 -= line;
ctx.textAlign="right";
ctx.lineTo(x2-linePadding,yMove);
ctx.fillText(dataArr[n][2],x2-textPadding-textMoveDis*(numctr-ctr)/numctr,y2-textPadding);
}else{
x2 += line;
ctx.textAlign="left";
ctx.lineTo(x2+linePadding,yMove);
ctx.fillText(dataArr[n][2],x2+textPadding+textMoveDis*(numctr-ctr)/numctr,y2-textPadding);
} ctx.stroke(); } //设置旋转
ctx.save();
ctx.translate(ox, oy);
ctx.rotate((Math.PI*2/numctr)*ctr/2); //绘制一个圆圈
ctx.strokeStyle = "rgba(0,0,0,"+ 0.5*ctr/numctr +")"
ctx.beginPath();
ctx.arc(0, 0 ,(radius+20)*ctr/numctr, 0, Math.PI*2, false);
ctx.stroke(); for (var j = 0; j < dataArr.length; j++){ //绘制饼图
endAngle = endAngle + new_data_arr[j]* ctr/numctr * Math.PI * 2; //结束弧度 ctx.beginPath();
ctx.moveTo(0,0); //移动到到圆心
ctx.arc(0, 0, radius*ctr/numctr, startAngle, endAngle, false); //绘制圆弧 ctx.fillStyle = dataArr[j][1];
if(mouseMove && ctx.isPointInPath(mousePosition.x*2, mousePosition.y*2)){
ctx.globalAlpha = 0.8;
} ctx.closePath();
ctx.fill();
ctx.globalAlpha = 1; startAngle = endAngle; //设置起始弧度
if( j == dataArr.length-1 ){
startAngle = endAngle = 90*Math.PI/180; //起始弧度 结束弧度
}
} ctx.restore(); if(ctr<numctr){
ctr++;
setTimeout(function(){
//ctx.clearRect(-canvas.width,-canvas.width,canvas.width*2, canvas.height*2);
ctx.clearRect(-canvas.width, -canvas.height,canvas.width*2, canvas.height*2);
drawMarkers();
pieDraw();
}, speed*=1.085);
}
} //监听鼠标移动
var mouseTimer = null;
canvas.addEventListener("mousemove",function(e){
e = e || window.event;
if( e.offsetX || e.offsetX==0 ){
mousePosition.x = e.offsetX;
mousePosition.y = e.offsetY;
}else if( e.layerX || e.layerX==0 ){
mousePosition.x = e.layerX;
mousePosition.y = e.layerY;
} clearTimeout(mouseTimer);
mouseTimer = setTimeout(function(){
ctx.clearRect(0,0,canvas.width, canvas.height);
drawMarkers();
pieDraw(true);
},10);
}); } var chartData = [[50,"#2dc6c8","瓜子"], [100,"#b6a2dd", "花生"], [200,"#5ab1ee","土豆"], [700,"#d7797f","南瓜四号"]]; goChart(chartData); </script>
</body>
</html>

好了,今天就讲到这里,希望大家把代码都自己敲一遍。

关注公众号,博客更新即可收到推送

canvas图表详解系列(3):动态饼状图(原生Js仿echarts饼状图)-LMLPHP

04-30 00:17