本章建议学习时间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. 【集美大学1411_助教博客】团队作业5——测试与发布(Alpha版本)

    同学们好像都进入了状态,任务都完成的不错,测试与发布是一个软件的非常重要的环节,每年双11前夕是阿里巴巴加班最严重的时期,这是因为他们在不断的测试,因为他们不想在双11到来之时有任何差池.所以无论你的 ...

  2. Swing-JRadioButton用法-入门

    JRadioButton是Swing中的单选框.所谓单选框是指,在同一个组内虽然有多个单选框存在,然而同一时刻只能有一个单选框处于选中状态.它就像收音机的按钮,按下一个时此前被按下的会自动弹起,故因此 ...

  3. 201521123064 《Java程序设计》第10周学习总结

    1. 本章学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常与多线程相关内容. ① 定义Thread类的子类,覆盖Thread类的run()方法,然后创建该子类的实例(一般不用该方法,开销 ...

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

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

  5. 201521123068 《java程序设计》 第14周学习总结

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

  6. SQL Server安装【转载】

    http://blog.csdn.net/sangjinchao/article/details/62044021?locationNum=6&fps=1

  7. Elipse中发布一个Maven项目到Tomcat

    对于maven初学者的我,经常遇到一个问题就是,maven项目创建成功后,本来已经添加了jar的依赖,但是发布到Tomcat中就是没有jar包存在, 启动Tomcat总是报没有找到jar包,可项目结构 ...

  8. 框架应用:Spring framework (三) - JDBC支持

    Spring框架是一个一站式的框架,也就是对很多技术和框架做了封装,使其应用更加简便. JDBC的代码过程 /STEP 1. Import required packages import java. ...

  9. TomCat系统架构

    1.TomCat总体结构 TomCat有两大核心组件:Connector和Container.Connector组件是可以被替换的,一个Container可以对应多个Connector. 多个Conn ...

  10. 关于MySQL 事务,视图,索引,数据库备份,恢复

      /*创建数据库*/ CREATE DATABASE `mybank`;/*创建表*/USE mybank;CREATE TABLE `bank`(    `customerName` CHAR(1 ...