canvas图表(3) - 饼图
原文地址:canvas图表(3) - 饼图
这几天把canvas图表都优化了下,动画效果更加出色了,可以说很逼近Echart了。刚刚写完的饼图,非常好的实现了既定的功能,交互的动画效果也是很棒的。
效果请看:饼图https://edwardzhong.github.io/sites/demo/dist/chartpie.html
功能点包括:
- 组织数据;
- 画面绘制;
- 数据动画的实现;
- 鼠标事件的处理。
使用方式
饼图的数据方面要简单很多,因为不用多个分组的数据。把所有的数据相加得出总数,然后每个数据分别求出百分比,有了百分比再相乘360度的弧度得出每个数据在圆盘中对应的要显示的角度。
var con=document.getElementById('container');
var pie=new Pie(con);
pie.init({
title:'网站用户访问来源',
toolTip:'访问来源',
data:[
{value:435, name:'直接访问'},
{value:310, name:'邮件营销'},
{value:234, name:'联盟广告'},
{value:135, name:'视频广告'},
{value:1548, name:'搜索引擎'}
]
});
代码结构
因为为了同时实现新增动画和更新动画,这次的代码结构经过了重构和优化,跟之前的有比较大的区别。
class Line extends Chart{
constructor(container){
super(container);
}
// 初始化
init(opt){
}
// 绑定事件
bindEvent(){
}
// 显示信息
showInfo(pos,arr){
}
// 清除内容再绘制
clearGrid(index){
}
// 执行数据动画
animate(){
}
// 执行
create(){
}
// 组织数据
initData(){
}
// 绘制
draw(){
}
}
组织数据
这次把组织数据的功能单独拎了出来,这样方便重用和修改。然后还要给动画对象增加是否创建的属性create和上次最后更新的度数last,为什么呢?因为我们要同时实现创建和更新图形的动画效果。
initData(){
var that=this,
item,
total=0;
if(!this.data||!this.data.length){return;}
this.legend.length=0;
for(var i=0;i<this.data.length;i++){
item=this.data[i];
// 赋予没有颜色的项
if(!item.color){
var hsl=i%2?180+20*i/2:20*(i-1);
item.color='hsla('+hsl+',70%,60%,1)';
}
item.name=item.name||'unnamed';
this.legend.push({
hide:!!item.hide,
name:item.name,
color:item.color,
x:50,
y:that.paddingTop+40+i*50,
w:80,
h:30,
r:5
});
if(item.hide)continue;
total+=item.value;
}
for(var i=0;i<this.data.length;i++){
item=this.data[i];
if(!this.animateArr[i]){//创建
this.animateArr.push({
i:i,
create:true,
hide:!!item.hide,
name:item.name,
color:item.color,
num:item.value,
percent:Math.round(item.value/total*10000)/100,
ang:Math.round(item.value/total*Math.PI*2*100)/100,
last:0,
cur:0
});
} else {//更新
if(that.animateArr[i].hide&&!item.hide){
that.animateArr[i].create=true;
that.animateArr[i].cur=0;
} else {
that.animateArr[i].create=false;
}
that.animateArr[i].hide=item.hide;
that.animateArr[i].percent=Math.round(item.value/total*10000)/100;
that.animateArr[i].ang=Math.round(item.value/total*Math.PI*2*100)/100;
}
}
}
绘制
饼图的绘制功能很简单,因为不用坐标系,只需要绘制标题和标签列表。
draw(){
var item,ctx=this.ctx;
ctx.fillStyle='hsla(0,0%,30%,1)';
ctx.strokeStyle='hsla(0,0%,20%,1)';
ctx.textBaseLine='middle';
ctx.font='24px arial';
ctx.clearRect(0,0,this.W,this.H);
if(this.title){
ctx.save();
ctx.textAlign='center';
ctx.font='bold 40px arial';
ctx.fillText(this.title,this.W/2,70);
ctx.restore();
}
ctx.save();
for(var i=0;i<this.legend.length;i++){
item=this.legend[i];
// 画分类标签
ctx.textAlign='left';
ctx.fillStyle=item.color;
ctx.strokeStyle=item.color;
roundRect(ctx,item.x,item.y,item.w,item.h,item.r);
ctx.globalAlpha=item.hide?0.3:1;
ctx.fill();
ctx.fillText(item.name,item.x+item.w+20,item.y+item.h-5);
}
ctx.restore();
}
执行绘制饼图动画
动画区分了创建和更新,这样用户很容易就能看出数据的比例关系变化,也就更加的直观。创建就是从0弧度到指定的弧度,只有数值的增加;而更新动画就要区分增加和减少的情况,因为当用户点击某个标签的时候,会隐藏显示某个分类的数据,于是需要重新计算每个分类的比例,那么相应的分类百分比就会增加或减少。我们根据当前最新要达到的比例ang和已经执行完的当前比例last的进行对比,相应执行增加和减少比例,动画原理就是这样。
canvas绘制圆形context.arc(x,y,r,sAngle,eAngle,counterclockwise);只要我们指定开始角度和结束角度就会画出披萨饼一样的效果,所有的披萨饼加起来就是一个圆。
animate(){
var that=this,
ctx=that.ctx,
canvas=that.canvas,
item,startAng,ang,
isStop=true;
(function run(){
isStop=true;
ctx.save();
ctx.translate(that.W/2,that.H/2);
ctx.fillStyle='#fff';
ctx.beginPath();
ctx.arc(0,0,that.H/3+30,0,Math.PI*2,false);
ctx.fill();
for(var i=0,l=that.animateArr.length;i<l;i++){
item=that.animateArr[i];
if(item.hide)continue;
startAng=-Math.PI/2;
that.animateArr.forEach((obj,j)=>{
if(j<i&&!obj.hide){startAng+=obj.cur;}
});
ctx.fillStyle=item.color;
if(item.create){//创建动画
if(item.cur>=item.ang){
item.cur=item.last=item.ang;
} else {
item.cur+=0.05;
isStop=false;
}
} else {//更新动画
if(item.last>item.ang){
ang=item.cur-0.05;
if(ang<item.ang){
item.cur=item.last=item.ang;
}
} else {
ang=item.cur+0.05;
if(ang>item.ang){
item.cur=item.last=item.ang;
}
}
if(item.cur!=item.ang){
item.cur=ang;
isStop=false;
}
}
ctx.beginPath();
ctx.moveTo(0,0);
ctx.arc(0,0,that.H/3,startAng,startAng+item.cur,false);
ctx.closePath();
ctx.fill();
}
ctx.restore();
if(isStop) {
that.clearGrid();
return;
}
requestAnimationFrame(run);
}());
}
交互处理
执行完动画后,我这里再执行了一遍清除绘制,这个也是鼠标触摸标签和饼图时的对应动画方法,会绘制每个分类的名称描述,更方便用户查看。
clearGrid(index){
var that=this,
ctx=that.ctx,
canvas=that.canvas,
item,startAng=-Math.PI/2,
len=that.animateArr.filter(item=>!item.hide).length,
j=0,angle=0,
r=that.H/3;
ctx.clearRect(0,0,that.W,that.H);
that.draw();
ctx.save();
ctx.translate(that.W/2,that.H/2);
for(var i=0,l=that.animateArr.length;i<l;i++){
item=that.animateArr[i];
if(item.hide)continue;
ctx.strokeStyle=item.color;
ctx.fillStyle=item.color;
angle=j>=len-1?Math.PI*2-Math.PI/2:startAng+item.ang;
ctx.beginPath();
ctx.moveTo(0,0);
if(index===i){
ctx.save();
// ctx.shadowColor='hsla(0,0%,50%,1)';
ctx.shadowColor=item.color;
ctx.shadowBlur=5;
ctx.arc(0,0,r+20,startAng,angle,false);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
} else {
ctx.arc(0,0,r,startAng,angle,false);
ctx.closePath();
ctx.fill();
}
//画分类描述
var tr=r+40,tw=0,
tAng=startAng+item.ang/2,
x=tr*Math.cos(tAng),
y=tr*Math.sin(tAng);
ctx.lineWidth=2;
ctx.lineCap='round';
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(x,y);
if(tAng>=-Math.PI/2&&tAng<=Math.PI/2){
ctx.lineTo(x+30,y);
ctx.fillText(item.name,x+40,y+10);
} else {
tw=ctx.measureText(item.name).width;//计算字符长度
ctx.lineTo(x-30,y);
ctx.fillText(item.name,x-40-tw,y+10);
}
ctx.stroke();
startAng+=item.ang;
j++;
}
ctx.restore();
}
事件处理
mousemove的时候,触摸标签和触摸饼图都是基本相同的效果,选中的分类扩大半径,同时增加阴影,以达到凸出来的动画效果,具体实现请看上面的clearGrid方法。判断是否点中都是使用isPointInPath这个api,之前已经介绍过,不再细讲。
mousedown某个击标签就会显示隐藏对应分类,每次触发就会看到饼图的比例变化的动画效果,这个和之前的柱状图和折线图的功能一致。
bindEvent(){
var that=this,
canvas=that.canvas,
ctx=that.ctx;
if(!this.data.length) return;
this.canvas.addEventListener('mousemove',function(e){
var isLegend=false;
var box=canvas.getBoundingClientRect(),
pos = {
x:e.clientX-box.left,
y:e.clientY-box.top
};
// 标签
for(var i=0,item,len=that.legend.length;i<len;i++){
item=that.legend[i];
roundRect(ctx,item.x,item.y,item.w,item.h,item.r);
if(ctx.isPointInPath(pos.x*2,pos.y*2)){
canvas.style.cursor='pointer';
if(!item.hide){
that.clearGrid(i);
}
isLegend=true;
break;
}
canvas.style.cursor='default';
that.tip.style.display='none';
}
if(isLegend) return;
// 图表
var startAng=-Math.PI/2;
for(var i=0,l=that.animateArr.length;i<l;i++){
item=that.animateArr[i];
if(item.hide)continue;
ctx.beginPath();
ctx.moveTo(that.W/2,that.H/2);
ctx.arc(that.W/2,that.H/2,that.H/3,startAng,startAng+item.ang,false);
ctx.closePath();
startAng+=item.ang;
if(ctx.isPointInPath(pos.x*2,pos.y*2)){
canvas.style.cursor='pointer';
that.clearGrid(i);
that.showInfo(pos,that.toolTip,[{name:item.name,num:item.num+' ('+item.percent+'%)'}]);
break;
}
canvas.style.cursor='default';
that.clearGrid();
}
},false);
this.canvas.addEventListener('mousedown',function(e){
e.preventDefault();
var box=that.canvas.getBoundingClientRect();
var pos = {
x:e.clientX-box.left,
y:e.clientY-box.top
};
for(var i=0,item,len=that.legend.length;i<len;i++){
item=that.legend[i];
roundRect(ctx,item.x,item.y,item.w,item.h,item.r);
if(ctx.isPointInPath(pos.x*2,pos.y*2)){
that.data[i].hide=!that.data[i].hide;
that.create();
break;
}
}
},false);
}
最后
所有图表代码请看chart.js
canvas图表(3) - 饼图的更多相关文章
- canvas图表(1) - 柱状图
原文地址:canvas图表(1) - 柱状图 前几天用到了图表库,其中百度的ECharts,感觉做得最好,看它默认用的是canva,canvas图表在处理大数据方面比svg要好.那我也用canvas来 ...
- canvas图表(4) - 散点图
原文地址:canvas图表(4) - 散点图 今天开始完成散点图,做完这一节,我的canvas图表系列就算是完成了,毕竟平时最频繁用到的就是这几类图表了:柱状,折线,饼图,散点.经过编写canvas图 ...
- 超酷HTML5 Canvas图表应用Chart.js自定义提示折线图
超酷HTML5 Canvas图表应用Chart.js自定义提示折线图 效果预览 实例代码 <div class="htmleaf-container"> <div ...
- canvas图表(2) - 折线图
原文地址:canvas图表(2) - 折线图 话说这天气一冷啊, 就患懒癌, 就不想码代码, 就想着在床上舒舒服服看视频. 那顺便就看blender视频, 学习下3D建模, 如果学会了建3D模型, 那 ...
- 基于html5 Canvas图表库 : ECharts
ECharts开源来自百度商业前端数据可视化团队,基于html5 Canvas,是一个纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表.创新的拖拽重计算.数据视图.值 ...
- Chart.js – 效果精美的 HTML5 Canvas 图表库
Chart.js 是一个令人印象深刻的 JavaScript 图表库,建立在 HTML5 Canvas 基础上.目前,它支持6种图表类型(折线图,条形图,雷达图,饼图,柱状图和极地区域区).而且,这是 ...
- canvas图表详解系列(3):动态饼状图(原生Js仿echarts饼状图)
本章建议学习时间4小时 学习方式:详细阅读,并手动实现相关代码(如果没有canvas基础,需要先学习前面的canvas基础笔记) 学习目标:此教程将教会大家如何使用canvas绘制各种图表,详细分解步 ...
- canvas图表详解系列(1):柱状图
本章建议学习时间4小时 学习方式:详细阅读,并手动实现相关代码(如果没有canvas基础,需要先学习前面的canvas基础笔记) 学习目标:此教程将教会大家如何使用canvas绘制各种图表,详细分解步 ...
- canvas图表详解系列(2):折线图
本章建议学习时间4小时 学习方式:详细阅读,并手动实现相关代码(如果没有canvas基础,需要先学习前面的canvas基础笔记) 学习目标:此教程将教会大家如何使用canvas绘制各种图表,详细分解步 ...
随机推荐
- 【实验吧】Once More
<?php if (isset ($_GET['password'])) { if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) = ...
- riot.js教程【二】组件撰写准则、预处理器、标签样式和装配方法
基本要求 一个riot标签,就是展现和逻辑的组合(也就是html和JS): 以下是编写riot标签最基本的规则: 先撰写HTML,再撰写JS,JS代码可以写在<script>标签内部,但这 ...
- 【转载】Retina屏的移动设备如何实现真正1px的线?
文章转载自 酷勤网 http://www.kuqin.com/ 原文链接:http://www.kuqin.com/shuoit/20150530/346322.html 原文摘要:前些日子总被人问起 ...
- linux安装禅道的步骤
linux一键安装禅道:1.禅道帮助文档:http://www.zentao.net/book/zentaopmshelp/90.html 2.修改Apache的端口号:/opt/zbox/zbox ...
- XtraReports 入门教程
一个链接:http://www.cnblogs.com/springSky/tag/XtraReports%20%E5%85%A5%E9%97%A8%E6%95%99%E7%A8%8B/ 与之相同功能 ...
- 解决MySQL中文乱码问题
决解乱码费了我好些时间啊! 乱码原因有 1.mysql未设置为支持汉字 2.没有发送头信息 3.使用的编译器不符合相应的编码 决解的方法是 在mysql里 我用的是Wanmp Server 1.在my ...
- CentOS本地yum源配置
现有一台处在局域网的linux服务器,无法ping通外网,本文是关于本地yum源的配置 环境 : CentOS 6.5 一 .挂载CentOS镜像文件 (1) 创建挂载文件夹,若此文件夹已存在可忽略 ...
- 使用ftp软件上传下载php文件时换行丢失bug(全部变为一行)
文章来源:http://www.piaoyi.org/computer/ftp-php-r-n-bug.html 正 文: 在使用ftp软件上传下载php源文件时,我们偶尔会发现在本地windows下 ...
- LeetCode 238. Product of Array Except Self (去除自己的数组之积)
Given an array of n integers where n > 1, nums, return an array output such that output[i] is equ ...
- 使用fabric解决百度BMR的spark集群各节点的部署问题
前言 和小伙伴的一起参加的人工智能比赛进入了决赛之后的一段时间里面,一直在构思将数据预处理过程和深度学习这个阶段合并起来.然而在合并这两部分代码的时候,遇到了一些问题,为此还特意写了脚本文件进行处理. ...