本章建议学习时间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. 201521123107 《Java程序设计》第14周学习总结

    第14周-数据库 1.本周学习总结 2.书面作业 1. MySQL数据库基本操作 建立数据库,将自己的姓名.学号作为一条记录插入.(截图,需出现自己的学号.姓名) 在自己建立的数据库上执行常见SQL语 ...

  2. 学号:201521123116 《java程序设计》第一周学习总结

    #1. 本章学习总结了解了班级的纪律和期末成绩组成.本周重点学习了java语言的特点具有简单性和结构中立.明白了jdk是java开发人员使用的一种工具,以及编写一个java程序的所需步骤,懂得了jdk ...

  3. 关于百度DNS的解析过程

    if现在我用一台电脑,通过ISP接入互联网,那么ISP就会分配给我一个DNS服务器(非权威服务器). now,我的computer向这台ISPDNS发起请求查询www.baidu.com. 首先,IS ...

  4. 201521123063 《Java程序设计》 第10周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常与多线程相关内容. 2. 书面作业 本次PTA作业题集异常.多线程 finally 题目4-2 1.1 截图你的提交结果(出现学 ...

  5. 201521123106 《Java程序设计》第14周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 2. 书面作业 1. MySQL数据库基本操作 建立数据库,将自己的姓名.学号作为一条记录插入.(截图,需出现自 ...

  6. 201521123117 《Java程序设计》第9周学习总结

    1. 本周学习总结 2. 书面作业 1.常用异常 题目5-1 1.1 截图你的提交结果(出现学号) 1.2 自己以前编写的代码中经常出现什么异常.需要捕获吗(为什么)?应如何避免? 经常出现Numbe ...

  7. 框架应用:Spring framework (五) - Spring MVC技术

    软件开发中的MVC设计模式 软件开发的目标是减小耦合,让模块之前关系清晰. MVC模式在软件开发中经常和ORM模式一起应用,主要作用是将(数据抽象,数据实体传输和前台数据展示)分层,这样前台,后台,数 ...

  8. Java并发之synchronized关键字

         上篇文章我们主要介绍了并发的基本思想以及线程的基本知识,通过多线程我们可以实现对计算机资源的充分利用,但是在最后我们也说明了多线程给程序带来的两种典型的问题,针对它们,synchronize ...

  9. AJAX应用【股票案例】

    股票案例 我们要做的是股票的案例,它能够无刷新地更新股票的数据.当鼠标移动到具体的股票中,它会显示具体的信息. 我们首先来看一下要做出来的效果: 服务器端分析 首先,从效果图我们可以看见很多股票基本信 ...

  10. [02] Servlet获取请求和页面跳转

    1.Tomcat和Servlet的关系 之前提到过,Servlet是运行在Web容器里的,Tomcat作为容器的一种,在这里自然也要大概说说两者之间的大致关系. 首先,如上所述,Tomcat是Web应 ...