GVKun编程网logo

Canvas如何做出3D动态的Chart图表(canvas动态绘制简单gif)

10

对于Canvas如何做出3D动态的Chart图表感兴趣的读者,本文将会是一篇不错的选择,我们将详细介绍canvas动态绘制简单gif,并为您提供关于2D动画如何做出3D体积感、3d基础知识研究--如何

对于Canvas如何做出3D动态的Chart图表感兴趣的读者,本文将会是一篇不错的选择,我们将详细介绍canvas动态绘制简单gif,并为您提供关于2D动画如何做出3D体积感、3d基础知识研究--如何用js在canvas2d上做出3d效果、canvas图表详解系列(3):动态饼状图(原生Js仿echarts饼状图)、Cross-platform stock chart library based on the canvas - clchart的有用信息。

本文目录一览:

Canvas如何做出3D动态的Chart图表(canvas动态绘制简单gif)

Canvas如何做出3D动态的Chart图表(canvas动态绘制简单gif)

这次给大家带来Canvas如何做出3D动态的Chart图表,Canvas做出3D动态的Chart图表注意事项有哪些,下面就是实战案例,一起来看一下。

发现现在工业SCADA上或者电信网管方面用图表的特别多,虽然绝大部分人在图表制作方面用的是echarts,他确实好用,但是有些时候我们不能调用别的插件,这个时候就得自己写这些美丽的图表了,然而图表轻易做不成美丽的。。。看到有一个网站上在卖的图表,感觉挺好看的,就用 HT for Web 3D 做了一个小例子,挺简单的,也挺好看的,哈哈~

动态效果图如下:

这个例子用 HT 实现真的很容易,首先创建一个 HT 中最基础的 dm 数据模型,然后将数据模型添加进 g3d 3d 组件中,再设置 3d 中的视角并把 3d 组件添加进 body 元素中:

dm = new ht.DataModel();
g3d = new ht.graph3d.Graph3dView(dm);
g3d.setEye(0, 185, 300);
g3d.addTodoM();
g3d.getView().style.background = '#000';

接着就是造这五个chart图表条了,我的思路是这样的,里层有一个节点,外层一个透明的节点,底部一个 3d 的文字显示当前的百分比。

里层的节点非常容易,我直接用的 HT 封装的 ht.Node 创建了一个新的节点对象,然后通过 node.s 方法来设置 node 节点的样式:

var node = new ht.Node();
node.s({
    'shape3d': cylinderModel,
    'shape3d.color': color,
    '3d.movable': false
});
node.a({
    'myHeight': s3[1],
});
node.p3([p3[0], s3[1]/2, p3[2]]);
node.s3(s3);
dm.add(node);

其中要说明的是,'shape3d':cylinderModel 这个样式的设置,首先,shape3d 属性指定显示为 3d 模型的图标效果,cylinderModel 是用 HT 自定义的一个 3d 模型,可参考 HT for Web 建模手册:

cylinderModel = ht.Default.createCylinderModel(1000, 0, 1000, false, false, true, true);

然后设置了一个动态变化的属性 myHeight,在 HT 中,node.a 方法是预留给用户存储业务数据的,我们可以在这边添加任意多个属性。

接下来我们要创建的是外部的透明节点,这个节点的构造方式基本上与内部节点相同,就是多了一点“透明”的样式设置:

var cNode = new ht.Node();
cNode.s({
    'shape3d': cylinderModel,
    'shape3d.transparent': true,
    'shape3d.opacity': 0.2,    
    'label.color': '#fff',
    '3d.movable': false
});
cNode.p3([p3[0], 50, p3[2]]);
cNode.s3(20, 100, 20);
dm.add(cNode);

要先设置 ‘shape3d.transparent’ 为true,再设置 ‘shape3d.opacity’ 透明度。

最后是 3d 文字,呈现 3d 文字需要一个 json 格式的 typeface 字体,然后通过 ht.Default.loadFontFace 来加载 json 格式的字体到内存中,详情请参考 HT for Web 3D 手册:

ht.Default.loadFontFace('./wenquanyi.json', function(){
    //......
    var text = new ht.Node();
    text.s3([5, 5, 5]);
    text.p3(cNode.p3()[0]-5, -10, 0);
    dm.add(text);
    text.s({
        'shape3d' : 'text',
    'shape3d.text': node.a('myHeight')+'%',
    'shape3d.text.curveSegments': 1,
    '3d.movable': false
    });
});

因为我们用的 typeface 字体的绘制方式是无数个三角形构成的一个字,会占用很大的内存,所以我把图形的曲线的精细度调得较低,但是还是很清晰,如果你们能找到性能更好的字体,可以使用并且告知我一下,我们目前没找到占用内存小的字体。

最后,要动态的变化 chart 图表中的柱形图,我们得设置动画,并且将 3d 字体也同步更新数值:

setInterval(function(){
    if(node.a('myHeight') < 100){
        node.a('myHeight', (node.getAttr('myHeight')+1));
    node.s3(20, node.a('myHeight'), 20);
    node.p3(p3[0], node.a('myHeight')/2, p3[2]);
    }else{
        node.a('myHeight', 0);
    node.s3([20, 0, 20]);
    node.p3([p3[0], 0, p3[2]]);
    }
    if (text) text.s('shape3d.text', node.a('myHeight')+'%');
}, 100);

这里,我自定义的属性 “myHeight” 就起到了决定性的作用,我用这个属性来存储变量,而且可以任意更改变量的值,这样就能实现动态绑定的效果了。

还有不懂的可以留言,或者直接去我们官网上查看手册 HT for Web,有更多你想不到的效果能快速实现哦~

相信看了本文案例你已经掌握了方法,更多精彩请关注小编网其它相关文章!

推荐阅读:

H5读取文件并上传到服务器的方法

select下拉框的右边怎么增加提示图标

2D动画如何做出3D体积感

2D动画如何做出3D体积感

https://cowlevel.net/article/1959026

 

 

《AngerForce》幕后故事

这篇文章是个老坑,最近有时间开始写,也是对之前项目的一个总结和记录吧。

本篇文章将主要讲述AngerForce: Reloaded中的Boss恐惧破坏者(Terror Cracker)设计和迭代的过程。这个Boss是2016年重制AngerForce时,我们尝试制作的第一个新追加Boss,希望以此测试新工具和制作流程。我们在早期做了一个巨屎的版本,根本没法忍,经过了一次制作流程的迭代后,才有了最终的样子。

前面部分是开发故事,可以直接跳到后面动画制作部分不影响阅读。 (•ิ_•ิ)

文章同载于制作组的知乎专栏:右左下上AB。

c77421fbb061982ecae3cf561ada483a.gif

AngerForce

2014年我们曾制作了一款复古风格的STG游戏,并发布到了iOS平台上,那就是AngerForce: Relaoded的前身AngerForce: Strikers。这款游戏发布后,受到了AppStore北美地区的推荐,并被PocketGamer和TouchArcade主动评测,说明游戏的质量还算不错。

AngerForce - Strikers

AngerForce - Strikers

但说实话,我们在完成这款游戏后,觉得很多细节应该可以做得更好。直到2016年我们决定将这个游戏重做一遍,发布到更多平台上,也就是后来的《愤怒军团:重装》AngerForce: Reloaded。

AngerForce: Reloaded

AngerForce: Reloaded

动画难题

我们团队在组建的时候是个票友团队——没做过游戏。在开发AngerForce初代时我们就遇到了很多难题,其中最大的问题就是动画。我们团队当中没有专职的动画师。

不过运气还算不错,通过我一邻居,在北京西边门头沟的大山里请到了一位可以建模动画一把抓的大腿,帮我们解决了问题。但大腿看我们实在是太Indie(diao si)了,帮我们解决完动画她就又回山里开火锅店去了。

当时我们在北京的郊区,楼门口挂一LED灯24小时滚动播放“氩弧焊、电气焊培训”……

当时我们在北京的郊区,楼门口挂一LED灯24小时滚动播放“氩弧焊、电气焊培训”……

大腿走后,开发组只剩我们5个杂鱼了,我们需要再次面对动画这一难题。

初代《愤怒军团》我们使用Cocos2d-iPhone开发,当时从动画打包、弹幕制作到关卡制作全部由我们自己开发的整套工具链来完成,而我们的动画工具只能支持帧序列。

 

我们决定要重制游戏时,为了解决跨平台问题,已经确定要更换引擎,所以我们需要抛弃曾经的绝大部分开发工具,寻找新的替代品。

1af99a680abaff0345d3265342d14117.jpeg

最终我们决定使用Unity来进行开发,为了能保持效率,还将复用大部分资源,继续保留游戏的2D画面,动画使用第三方工具Spine来制作。

 

传统2D骨骼动画

Spine最近几年非常流行,有许多便捷的功能:曲线调整动画节奏、网格形变动画、允许混合动画播放等等,对主流引擎的支持也很好。

c3a43c986047fc20e184bb9d8f68ef80.jpeg

AngerForce: Reloaded虽然叫重装(下文就都以《重装》来称呼),但是我们打算再追加些新的敌人、Boss和关卡,这些都要使用新的工具来制作了。我们尝试使用手绘素材制作了一些动作比较简单的小型敌人,还都比较顺利,接下来就打算试试去制作一个全新的Boss。

 

我们很快完成了设计方案:

设计稿

设计稿

根据我们的设想,这个Boss将完全使用2D手绘的纹理,动画采用骨骼动画和关节原件替换的形式来实现。然后我们就做出了一开始提到的那个巨屎的版本……

 

看着让人尴尬的脸酸…… (;¬_¬)

看着让人尴尬的脸酸…… (;¬_¬) 

其实我们对自己的设计方案还是有信心的,但纯2D方案制作有很多局限性,制作出的效果非常廉价,对于2D动画方案我有一些总结:

①无关节变化的2D纹理只适合做简单动画。

②单纯靠转动、缩放和位移没办法表现出复杂、震撼的大幅度动作,制作精良动画需要引入关节替换。

③纹理的绘制的细节越多,制作这张纹理的其他透视关节就越困难,工作量也就越大——越细越难画准。

④多关节的复杂骨骼动画能制作出非常有表现力的动画效果,但制作调试比较耗时,最适合应用在主角这种登场频率较高的单位身上。

⑤利用前后层遮蔽有一些“偷懒”技巧,不用考虑动作时模型穿透一类的问题。

 

涉及到项目中的这个Boss,在绘制纹理上我们有很大的矛盾——画太细不好做关节,细节少做出来又觉得作为Boss效果不行。

最后,我们决定用3D来辅助。

 

Boss恐惧破坏者

Boss恐惧破坏者

解决老问题

AngerForce项目早期的动画使用3转2帧序列,当时受客观条件限制Boss动画优化很差,全部动画的帧序列打包完毕有3张2048x2048大小的图册(Atlas)。

2012年的主流移动设备甚至都不支持加载如此大的纹理,拆解为1024x1024大小的纹理是大概是12张,若使用32位图片存储,加载后图册单像素占用32位是4字节*1024*1024*12=48M内存占用!

原始版本3转2图册

原始版本3转2图册

Boss的一个形态就占这么多内存,这没法忍。虽说《重装》计划在PC和主机平台发布,但我们也不打算再用帧序列了。

我们这次计划全部用2D工艺——使用自由网格形变(Free Form Deformation就是上一篇文章提到的FFD)、关节替换配合骨骼动画来制作。

优化后的动画图册

优化后的动画图册

手绘关节太痛苦了,我们给单位建了模,想要各个透视角度的关节直接单独渲出来,精细纹理下的关节绘制问题就解决了。

 

FFD减少关键帧

建模后,渲染关键帧很方便了,但我们也不打算滥用。对于大多数平常的小幅度动画,我们都尝试使用FFD来制作。

458dfd20276a146b345e7f829f9c1864.jpg

FFD是最近几年比较流行的技术,原理是在一张2D的纹理上定义网格,然后通过改变网格顶点的位置来变形这张纹理。使用这个技术,可以通过形变来得到单张纹理近似的其他透视角度,从而达到减少关键帧的效果。

25801e508a2a51ae74254c0a0273d332.jpg

这里简单摆个3D模型来说明:Boss的造型简单概括就是一个易拉罐后面背俩柱子,在我们的眼睛由摄像机位置①到③的过程中,关节透视如图中的样子来变化的。

我们根据这种透视变化的规律,用FFD形变就得到了Boss躯干关节的其他透视角度了:

e0bb2ea4c4b890e1d08c14f09e1be672.jpg

使用FFD还有个好处就是,在形变的时候你可以得到平滑的过渡动画,合理的使用能产生神奇的效果,欺骗玩家的眼睛。

39a67bdde672963d096bc8bcfdeb184f.gif

GIF中的动画没有使用任何其他的关键帧,只是使用同样的纹理,似乎有了3D的效果。这里面应用FFD的只有躯干和肩甲两个关节,其他关节只有缩放、旋转和位移。

比如躯干后的两个火箭包,只是通过简单的位移加小角度旋转,却让人感觉似乎和躯干一起侧转了起来。

d3096e6d9e3ad46a27a1cd76a8bfd441.jpg

其实这个动画体积感最强的地方是在头部和肩膀的区域,这里有一个小技巧:利用2D动画的不同关节的遮蔽关系,适当的设计一些小的结构,在关节位移的时候显露出来,能让人感觉内部似乎隐藏着复杂的结构和体积。

设计上的一些小技巧

设计上的一些小技巧

 

替换关节完成大幅度动画

FFD在小幅度的动画里能发挥神奇的效果,但想要做出幅度大表现力强的动作,还需要配合使用关节替换。

a486417b0dccfc5136491018f2aaedd2.gif

这个GIF得动作幅度就比较大了,躯干部分基本做了一个接近90度的转体,角度太大FFD就不好使了,但仍可以替换一些关键关节,再组合使用FFD、位移、旋转和缩放用很少的纹理作出很好的效果。

d9019298e5a83a2468c84f8229a5c52f.jpg

这里面状态1到状态2,躯干的大幅转动使用关节替换加FFD完成。在状态2到状态3的时候,陆续将远端的手臂和肩甲两个关键关节替换为合适的角度,而主躯干使用FFD再配合其他关节的旋转和位移——搞定。这个动画所用纹理都已经列在上面了。

总结

这个Boss的整个制作让我们大致了解到了,自己用各种方案实现动画效果能达到的“天花板”:

1.纯2D工艺能达到的效果确实有限。

2.FFD+关节替换能制作出惊人的效果。

3.2D方案需要衡量好投入和产出——花费30分的力气能达到70分的效果;但如果想达到90分的效果可能要付出100分的努力,所以某些细节尽力而为就好。

4.复杂的2D动画投入精力很大,适宜用在出场时间多和获得关注较高的单位上。

5.适当用3D辅助可以减少工作量。

6.2D动画追求的还是2D的特性,如果更希望追求3D的效果请用3D制作。

83a393b01903e896c3d5be29a5857bec.gif

3d基础知识研究--如何用js在canvas2d上做出3d效果

3d基础知识研究--如何用js在canvas2d上做出3d效果

    逛 TypeScript 官网时发现了一个 js canvas 做的 3D 灯光渲染效果(详见参考1),一看源代码,竟然是用 getContext("2d") 做的,而且代码并不长,才两百多行,当时就被震惊了!!!区区这点代码就能白手起家写出三维光线追踪渲染效果?!WTF!还有这种操作?!感觉这一定是涉及非常基础、核心的 3D 相关原理的代码。虽然之前用过很多 3D 软件,也学过一点 Direct3D 和 WebGL,但是对 3D 基础知识始终是一知半解的,向量、矩阵变换等计算也不是太清楚,于是立即决定深入研究一下。

    折腾了一阵原代码,发现他的坐标空间是这样的,X正朝右,Y正朝上,Z正深入画布:

    

    让我恍然大悟的是透视的处理方式,并不是每个物体的每个部分去计算离相机有多远,而是从相机中心点发射出许多射线(每像素一个射线),射线撞到的最近的物体就取这个物体的颜色,这样自然就能产生近大远小的效果。为了直观地理解摄像机的工作原理,我用 MaxScript 在 3DsMax 里建立了射线的模型,下面会说创建过程,先看结果:

    

    这里设置了画布大小为 10 * 10 像素,每个像素对应一根射线,起点就是相机所在位置。

    来看下近大远小的透视是如何形成的。为了清晰这里用了 5 * 5 像素的画布,同样在 3DsMax 里建立射线模型,并添加一个矩形平面,从顶视图中可以看见,矩形靠近摄像机的边交叉了 5 根射线,而远离摄像机的边只交叉了 3 根射线,每根射线对应一个像素,那么靠近摄像机的边在画布上会占了 5 个像素,远离摄像机的边则只占 3 个像素,近大远小的透视效果就这样自然而然地出来了。

    理解了摄像机的射线,灯光渲染也就容易了,无非就是在这基础上,增加了法线、反射、RGB颜色混合、距离衰减等计算,这些其实都是基于向量的。

 

    由于射线数量太多,手动创建太麻烦,幸好 3DsMax 提供了 MaxScript 这个脚本语言,我就用 js 拼接出 MaxScript 来使用。这个是 js 方法

    源码是在 getPoint 方法里生成摄像机射线的,然后就在 getPoint 调用结果后面,把生成的射线存到 MaxScript 里,最后 log 出来。

    控制台里输出的结果是这样的

    打开 3DsMax 按 F11 调出 MaxScript 编辑器,把 js 结果贴入并全选,然后按 shift+enter 执行。

    关闭 MaxScript 编辑器,按 Z 自动缩放一下,就可以看到射线已经生成了。  注意 3DsMax 的坐标系统和源代码不一样,3DsMax 里是这样的,X正同样朝右,但Y正深入画布,Z正朝上。其实和源码的坐标系统也很好转换,只要交换YZ即可。

 

    

 

    到此对原码已经有了个系统的认识了,但是必须得自己敲点代码、算点公式才能称得上真正掌握。源码使用了球形和平面来演示,但是一般3D游戏等不会直接计算球形、平面,而是所有的东西都是用三角型拼出来的。那目标就很明确了,就是自己尝试做一个三角面。
    第一步是复制源码的 Plane 类,改名为 Triangle,然后就是写 intersect 方法,一开始想自己做射线与三角面交叉的计算,初步思路是先计算射线与三角面所在平面的交叉点,然后把这点转换成平面所在坐标系,再判断这点是否落在三角形内,但因为涉及坐标系转换等计算太复杂,最后没有自己写,而是在网上找到了一个现成的、优化的非常好的代码(详见参考2)。里面也提到了一种老式算法没他好,这个老式算法基本和我一开始的思路一致。他给出了新算法的 C 代码,我转了一下 js,结果如下

    注意这里只包含单面的交叉检测,即只有正面是看得见的(会和射线碰撞),背面是透明的(射线会穿透)。正反面的判断是这样的,三个点如此排列,上面为正面

    反之用 1 3 2 的顺序创建 Triangle 的话,下面为正面。

    在源码 draw 方法的 things 数组里加入我们的 Triangle, 调整摄像机的位置和注视点参数,让他能看见三角形正面(同样可以先在 3DsMax 里摆好位置,然后取各点的位置参数放到源码里,注意YZ要互换),就可以看到结果了,透视效果 OK 符合预期

   

    至此静态模型的创建就算基本OK了,有了三角面,我们就可以拼出任意形状的模型。颜色的话前面分析过也并不是太难。当然深入的话还有反锯齿、光滑组、法线贴图、折射、粒子等等,那太高深了,留待后续研究。

    现在不妨先来点动态的玩玩。源码只给了静态的,那动态的要怎么做呢?答案是矩阵变换。参考3里给出了关于矩阵非常好的例子,甚至 js 方法都给了现成的了。只要知道用矩阵从右到左的去叉乘点就可以让一个点的位置进行变换就行了。比如  X轴移动矩阵 × Y轴旋转矩阵 × X轴旋转矩阵 × 点 ,就是把这个点先根据X轴旋转,再根据Y轴旋转,最后在X轴上移动。可以把三角形的三个点同时都转一下,也可以只转摄像机的位置点。配合鼠标事件,我们的动态旋转效果就出来了。

  

 

    在线演示 :   http://gonnavis.com/3d_raytracer

    gitoschina源码: http://git.oschina.net/gonnavis/create3dfrom2d/tree/master

 

    当然用 canvas2d 做 3d 效果只是纯粹为了研究原理,没有显卡加速,画布稍微大一点就卡得几乎不能动了。实际要做的话,肯定还是要用 canvas3d WebGL 的,或者直接用更高级的 BabylonJS ThreeJS 等库。

    http://gonnavis.com/3d/babylonjs/rotate_obj_fallout_monster_2.html

 

参考

  1. https://www.typescriptlang.org/play/index.html 左侧下拉选择 Building a Raytracer 并点击右侧 Run.
  2. http://www.cs.virginia.edu/~gfx/Courses/2003/ImageSynthesis/papers/Acceleration/Fast%20MinimumStorage%20RayTriangle%20Intersection.pdf
  3. https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Matrix_math_for_the_web

 

canvas图表详解系列(3):动态饼状图(原生Js仿echarts饼状图)

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.stroke+ 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>

 

 

 

 

好了,今天就讲到这里,希望大家把代码都自己敲一遍。

 

 

关注公众号,博客更新即可收到推送

 

Cross-platform stock chart library based on the canvas - clchart

Cross-platform stock chart library based on the canvas - clchart

总结

以上是小编为你收集整理的Cross-platform stock chart library based on the canvas - clchart全部内容。

如果觉得小编网站内容还不错,欢迎将小编网站推荐给好友。

关于Canvas如何做出3D动态的Chart图表canvas动态绘制简单gif的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于2D动画如何做出3D体积感、3d基础知识研究--如何用js在canvas2d上做出3d效果、canvas图表详解系列(3):动态饼状图(原生Js仿echarts饼状图)、Cross-platform stock chart library based on the canvas - clchart等相关知识的信息别忘了在本站进行查找喔。

本文标签: