canvas图表详解系列(3):动态饼状图(原生Js仿echarts饼状图)
本章建议学习时间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饼状图)的更多相关文章
- canvas图表详解系列(2):折线图
本章建议学习时间4小时 学习方式:详细阅读,并手动实现相关代码(如果没有canvas基础,需要先学习前面的canvas基础笔记) 学习目标:此教程将教会大家如何使用canvas绘制各种图表,详细分解步 ...
- canvas图表详解系列(4):动态散点图
本章建议学习时间4小时 学习方式:详细阅读,并手动实现相关代码(如果没有canvas基础,需要先学习前面的canvas基础笔记) 学习目标:此教程将教会大家如何使用canvas绘制各种图表,详细分解步 ...
- canvas图表详解系列(1):柱状图
本章建议学习时间4小时 学习方式:详细阅读,并手动实现相关代码(如果没有canvas基础,需要先学习前面的canvas基础笔记) 学习目标:此教程将教会大家如何使用canvas绘制各种图表,详细分解步 ...
- canvas图表详解系列(5):雷达(面积)图
雷达(面积)图 本章建议学习时间4小时 学习方式:详细阅读,并手动实现相关代码(如果没有canvas基础,需要先学习前面的canvas基础笔记) 学习目标:此教程将教会大家如何使用canvas绘制各种 ...
- 【转】Android Canvas绘图详解(图文)
转自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2012/1212/703.html Android Canvas绘图详解(图文) 泡 ...
- 源码详解系列(七) ------ 全面讲解logback的使用和源码
什么是logback logback 用于日志记录,可以将日志输出到控制台.文件.数据库和邮件等,相比其它所有的日志系统,logback 更快并且更小,包含了许多独特并且有用的特性. logback ...
- 源码详解系列(八) ------ 全面讲解HikariCP的使用和源码
简介 HikariCP 是用于创建和管理连接,利用"池"的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制.连接可靠性测试.连接泄露控制.缓存语句等功能,另外,和 dr ...
- Mybatis源码详解系列(四)--你不知道的Mybatis用法和细节
简介 这是 Mybatis 系列博客的第四篇,我本来打算详细讲解 mybatis 的配置.映射器.动态 sql 等,但Mybatis官方中文文档对这部分内容的介绍已经足够详细了,有需要的可以直接参考. ...
- Java源码详解系列(十)--全面分析mybatis的使用、源码和代码生成器(总计5篇博客)
简介 Mybatis 是一个持久层框架,它对 JDBC 进行了高级封装,使我们的代码中不会出现任何的 JDBC 代码,另外,它还通过 xml 或注解的方式将 sql 从 DAO/Repository ...
随机推荐
- Swing-setAlignmentX()用法-入门
先看下API: public void setAlignmentX(float alignmentX) 设置垂直对齐方式. 参数: alignmentX - 新的垂直对齐方式 网上关于这个函数的详细情 ...
- Java201521123071《Java程序设计》第八周学习总结
第八周-集合与泛型 1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 1. <T extends Comparable>表示T是绑定类型(Compa ...
- 201521123077 《Java程序设计》第1周学习总结
1. 本章学习总结 java语言的历史 dos下运行 java文件 Math ,String ,Scanner,Interger等常用类的使用 jdk,jre,jvm等概念的了解 classpath, ...
- [BT5]信息收集1-2 Dnsmap
0.工具介绍 dnsmap is mainly meant to be used by pentesters during the information gathering/enumeration ...
- 201521123031 《Java程序设计》第12周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多流与文件相关内容. 2. 书面作业 1.将Student对象(属性:int id, String name,int age,dou ...
- Eclipse rap 富客户端开发总结(11) : rcp/rap与spring ibatis集成
1. rcp/rap 与 spring 集成 Activator 是rcp/rap 启动时需要加载的类, 只需要加载一遍,所以与spring 集成的时候一般是在这个类里面加载spring 的Appli ...
- 如何定制 Calico 网络 Policy - 每天5分钟玩转 Docker 容器技术(70)
Calico 默认的 policy 规则是:容器只能与同一个 calico 网络中的容器通信.本节讨论如何定制 policy. calico 能够让用户定义灵活的 policy 规则,精细化控制进出容 ...
- 如何写好git commit message
1.触发事件 我有这样一个版本库,里面包含两个学习用的练习项目:BookStore(以下简称BS)和PictureFriend(以下简称PF) 我在更改PF以后,未进行提交,同时又到BS中优化了一下文 ...
- Java中的流程控制
1.Java中有几种流程控制?分别是什么? 答:有三种流程控制,分别是顺序流程,分支流程和循环流程 2.分支语句if/else有哪三种形式?分别如何使用? 答:if/if-else-/if-else ...
- 渗透相关website
开源安全测试方法论:http://www.isecom.org/research/osstmm.html 信息系统安全评估框架:www.oissg.org/issaf 开放式web应用程序安全项目(O ...