本章建议学习时间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

饼状图


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

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

实现步骤


--新建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; //文字运动开始的间距
}

--绘制板块图例

            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方法中 )

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

            //绘制动画
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饼状图)的更多相关文章

  1. canvas图表详解系列(2):折线图

    本章建议学习时间4小时 学习方式:详细阅读,并手动实现相关代码(如果没有canvas基础,需要先学习前面的canvas基础笔记) 学习目标:此教程将教会大家如何使用canvas绘制各种图表,详细分解步 ...

  2. canvas图表详解系列(4):动态散点图

    本章建议学习时间4小时 学习方式:详细阅读,并手动实现相关代码(如果没有canvas基础,需要先学习前面的canvas基础笔记) 学习目标:此教程将教会大家如何使用canvas绘制各种图表,详细分解步 ...

  3. canvas图表详解系列(1):柱状图

    本章建议学习时间4小时 学习方式:详细阅读,并手动实现相关代码(如果没有canvas基础,需要先学习前面的canvas基础笔记) 学习目标:此教程将教会大家如何使用canvas绘制各种图表,详细分解步 ...

  4. canvas图表详解系列(5):雷达(面积)图

    雷达(面积)图 本章建议学习时间4小时 学习方式:详细阅读,并手动实现相关代码(如果没有canvas基础,需要先学习前面的canvas基础笔记) 学习目标:此教程将教会大家如何使用canvas绘制各种 ...

  5. 【转】Android Canvas绘图详解(图文)

    转自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2012/1212/703.html Android Canvas绘图详解(图文) 泡 ...

  6. 源码详解系列(七) ------ 全面讲解logback的使用和源码

    什么是logback logback 用于日志记录,可以将日志输出到控制台.文件.数据库和邮件等,相比其它所有的日志系统,logback 更快并且更小,包含了许多独特并且有用的特性. logback ...

  7. 源码详解系列(八) ------ 全面讲解HikariCP的使用和源码

    简介 HikariCP 是用于创建和管理连接,利用"池"的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制.连接可靠性测试.连接泄露控制.缓存语句等功能,另外,和 dr ...

  8. Mybatis源码详解系列(四)--你不知道的Mybatis用法和细节

    简介 这是 Mybatis 系列博客的第四篇,我本来打算详细讲解 mybatis 的配置.映射器.动态 sql 等,但Mybatis官方中文文档对这部分内容的介绍已经足够详细了,有需要的可以直接参考. ...

  9. Java源码详解系列(十)--全面分析mybatis的使用、源码和代码生成器(总计5篇博客)

    简介 Mybatis 是一个持久层框架,它对 JDBC 进行了高级封装,使我们的代码中不会出现任何的 JDBC 代码,另外,它还通过 xml 或注解的方式将 sql 从 DAO/Repository ...

随机推荐

  1. 第二次项目冲刺(Beta阶段)5.25

    1.提供当天站立式会议照片一张 会议内容: ①检查前一天的任务情况. ②制定新一轮的任务计划. ③对这几日的冲刺进行总结. 2.每个人的工作 (1)工作安排 队员 今日进展 明日安排 王婧 #63(完 ...

  2. 201521123061 《Java程序设计》第十二周学习总结

    201521123061 <Java程序设计>第十二周学习总结 1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多流与文件相关内容. 2. 书面作业 将Student对 ...

  3. 201521123108 《Java程序设计》第6周学习总结

    本周学习总结 1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图,对面向对象思想进行一个总结. 书面作业 2.1. clone方法 1.1 Object对象中的 ...

  4. 201521123033《Java程序设计》第5周学习总结

    1. 本周学习总结 1.1 尝试使用思维导图总结有关多态与接口的知识点. 参考资料: 百度脑图 XMind 2. 书面作业 作业参考文件下载 1.代码阅读:Child压缩包内源代码 1.1 com.p ...

  5. 201521123006 《java程序设计》 第13周学习总结

    1. 本周学习总结 1.以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1. 网络基础 1.1 比较ping www.baidu.com与ping cec.j ...

  6. java程序设计-算术表达式的运算

    1.团队课程设计博客链接 洪亚文博客链接:http://www.cnblogs.com/201521123065hyw/ 郑晓丽博客链接:http://www.cnblogs.com/zxl3066/ ...

  7. vim基础详解

    目录: 什么是vim Vim能做什么 如何学习vim 如何用vim打开一个文件 Vim的三种模式 插入模式 命令模式 扩展命令模式 光标移动 在命令模式下 删除,复制,粘贴 扩展命令模式 可视化模式 ...

  8. java.io.IOException: Stream closed

    今天在做SSH项目的时候,出现了这个错误.百思不得其解,网上的答案都不能解决我的问题-.. 后来,一气之下就重新写,写了之后发现在JSP遍历集合的时候出错了. <s:iterator value ...

  9. JSP第二篇【内置对象的介绍、4种属性范围、应用场景】

    什么是JSP内置对象 JSP引擎在调用JSP对应的jspServlet时,会传递或创建9个与web开发相关的对象供jspServlet使用.JSP技术的设计者为便于开发人员在编写JSP页面时获得这些w ...

  10. 使用Spring的隐式注解和装配以及使用SpringTest框架

    SpringTestConfiguration 1.加入jar 包spring-test-4.3.9.RELEASE.jar 2.写基本的Component 注意级联状态下  需要给需要调用的属性加入 ...