在Cocos中画动态雷达图怎么样做,方法是什么
Admin 2022-06-15 群英技术资讯 325 次浏览
雷达图(Radar Chart) 也称为网络图、星图或蜘蛛网图。
是以从同一点开始的轴上表示的三个或更多个定量变量的二维图表的形式显示多元数据的图形方法。
适用于显示三个或更多的维度的变量。
雷达图常用于数据统计或对比,对于查看哪些变量具有相似的值、变量之间是否有异常值都很有用。
同时在不少游戏中都有雷达图的身影,可以很直观地展示并对比一些数据。
例如王者荣耀中的对战资料中就用到了:
那么在本篇文章中,皮皮就来分享下在 Cocos Creator 中如何利用 Graphics 组件来绘制炫酷的雷达图~
文中会对原始代码进行一定的削减以保证阅读体验。
雷达图组件:https://gitee.com/ifaswind/eazax-ccc/blob/master/components/RadarChart.ts
先来看看效果吧~
在线预览:https://ifaswind.gitee.io/eazax-cases/?case=radarChart
两条数据
缓动数据
花里胡哨
艺术就是爆炸
逐渐偏离主题
在我们正式开始制作雷达图之前,让我们先来大概了解一下 Cocos Creator 引擎中的 Graphics 组件。
Graphics 组件继承于 cc.RenderComponent
,利用该组件我们可以实现画板和表格之类的功能。
下面是我们本次将会用到的属性:
lineCap
:设置或返回线条两端的样式(无、圆形线帽或方形线帽)lineJoin
:设置或返回两条线相交时的拐角样式(斜角、圆角或尖角)lineWidth
:设置或返回当前画笔的粗细(线条的宽度)strokeColor
:设置或返回当前画笔的颜色fillColor
:设置或返回填充用的颜色(油漆桶)下面是我们本次将会用到的函数:
moveTo(x, y)
:抬起画笔并移动到指定位置(不创建线条)lineTo(x, y)
:放下画笔并创建一条直线至指定位置circle(cx, cy, r)
:在指定位置(圆心)画一个圆close()
:闭合已创建的线条(相当于 lineTo(起点)
)stroke()
:绘制已创建(但未被绘制)的线条(将线条想象成默认透明的,此行为则是赋予线条颜色)fill()
:填充当前线条包围的区域(如果线条没有闭合则会尝试”模拟闭合“起点和终点)clear()
:擦掉当前画板上的所有东西Graphics 组件文档:http://docs.cocos.com/creator/manual/zh/components/graphics.html?h=graphics
先来看看一个标准的雷达图有啥特点:
发现了吗?雷达图的基本特点如下:
先算出轴之间的夹角度数 [ 360 ÷ 轴数
],再计算所有轴的角度:
this.angles = []; // 轴间夹角 const iAngle = 360 / this.axes; for (let i = 0; i < this.axes; i++) { // 计算 const angle = iAngle * i; this.angles.push(angle); }
雷达图至少拥有 3 条轴,且每条轴上都应有 1 个或以上的刻度(不包含中心点):
所以我们需使用一个二维数组来保存所有刻度的坐标,从最外层(即轴线的末端)的刻度开始记录,方便我们绘制时读取:
// 创建一个二维数组 let scalesSet: cc.Vec2[][] = []; for (let i = 0; i < 轴上刻度个数; i++) { // 用来保存当前层上的刻度坐标 let scales = []; // 计算刻度在轴上的位置 const length = 轴线长度 - (轴线长度 / 轴上刻度个数 * i); for (let j = 0; j < this.angles.length; j++) { // 将角度转为弧度 const radian = (Math.PI / 180) * this.angles[j]; // 根据三角公式计算刻度相对于中心点(0, 0)的坐标 const pos = cc.v2(length * Math.cos(radian), length * Math.sin(radian)); // 推进数组 scales.push(pos); } // 推进二维数组 scalesSet.push(scales); }
轴线
连接中心点 (0, 0)
和最外层 scalesSet[0]
的刻度即为轴线:
// 遍历全部最外层的刻度 for (let i = 0; i < scalesSet[0].length; i++) { // 画笔移动至中心点 this.graphics.moveTo(0, 0); // 创建线条 this.graphics.lineTo(scalesSet[0][i].x, scalesSet[0][i].y); }
外网格线
连接所有轴上最外层 scalesSet[0]
的刻度即形成外网格线:
// 画笔移动至第一个点 this.graphics.moveTo(scalesSet[0][0].x, scalesSet[0][0].y); for (let i = 1; i < scalesSet[0].length; i++) { // 创建线条 this.graphics.lineTo(scalesSet[0][i].x, scalesSet[0][i].y); } // 闭合当前线条(外网格线) this.graphics.close();
填充并绘制
这里需要注意先填充颜色再绘制线条,要不然轴线和网格线就被挡住了:
// 填充线条包围的空白区域 this.graphics.fill(); // 绘制已创建的线条(轴线和外网格线) this.graphics.stroke();
于是现在我们就有了这么个玩意儿:
当刻度大于 1 个时就需要绘制内网格线,从刻度坐标集的下标 1 开始绘制:
// 刻度大于 1 个时才绘制内网格线 if (scalesSet.length > 1) { // 从下边 1 开始(下标 0 是外网格线) for (let i = 1; i < scalesSet.length; i++) { // 画笔移动至第一个点 this.graphics.moveTo(scalesSet[i][0].x, scalesSet[i][0].y); for (let j = 1; j < scalesSet[i].length; j++) { // 创建线条 this.graphics.lineTo(scalesSet[i][j].x, scalesSet[i][j].y); } // 闭合当前线条(内网格线) this.graphics.close(); } // 绘制已创建的线条(内网格线) this.graphics.stroke(); }
就这样我们雷达图的底子就画好啦:
编写画线逻辑之前,先确定一下我们需要的数据结构:
具体的数据结构如下(导出类型方便外部使用):
/** * 雷达图数据 */ export interface RadarChartData { /** 数值 */ values: number[]; /** 线的宽度 */ lineWidth?: number; /** 线的颜色 */ lineColor?: cc.Color; /** 填充的颜色 */ fillColor?: cc.Color; /** 节点的颜色 */ joinColor?: cc.Color; }
绘制数据比较简单,我们只需要算出数据点在图表中的位置,并将数据连起来就好了。
在 draw
函数中我们接收一份或以上的雷达图数据,并按照顺序遍历绘制出来(️长代码警告):
/** * 绘制数据 * @param data 数据 */ public draw(data: RadarChartData | RadarChartData[]) { // 处理数据 const datas = Array.isArray(data) ? data : [data]; // 开始绘制数据 for (let i = 0; i < datas.length; i++) { // 装填染料 this.graphics.strokeColor = datas[i].lineColor || defaultOptions.lineColor; this.graphics.fillColor = datas[i].fillColor || defaultOptions.fillColor; this.graphics.lineWidth = datas[i].lineWidth || defaultOptions.lineWidth; // 计算节点坐标 let coords = []; for (let j = 0; j < this.axes; j++) { const value = datas[i].values[j] > 1 ? 1 : datas[i].values[j]; const length = value * this.axisLength; const radian = (Math.PI / 180) * this.angles[j]; const pos = cc.v2(length * Math.cos(radian), length * Math.sin(radian)) coords.push(pos); } // 创建线条 this.graphics.moveTo(coords[0].x, coords[0].y); for (let j = 1; j < coords.length; j++) { this.graphics.lineTo(coords[j].x, coords[j].y); } this.graphics.close(); // 闭合线条 // 填充包围区域 this.graphics.fill(); // 绘制线条 this.graphics.stroke(); // 绘制数据节点 for (let j = 0; j < coords.length; j++) { // 大圆 this.graphics.strokeColor = datas[i].lineColor || defaultOptions.lineColor; this.graphics.circle(coords[j].x, coords[j].y, 2); this.graphics.stroke(); // 小圆 this.graphics.strokeColor = datas[i].joinColor || defaultOptions.joinColor; this.graphics.circle(coords[j].x, coords[j].y, .65); this.graphics.stroke(); } } }
到这里我们已经成功制作了一个可用的雷达图:
但是!我们的征途是星辰大海!必须加点料!
完全静态的雷达图实在是太无趣太普通,得想想办法让它动起来!
我们的雷达图数据的数值是数组形式,想到怎么样才能让这些数值动起来了吗?
得益于 Cocos Creator 为我们提供的 Tween 缓动系统,让复杂的数据动起来变得异常简单!
我们只需要这样,这样,然后那样,是不是很简单?
cc.tween
支持缓动任意对象的任意属性缓动系统:http://docs.cocos.com/creator/manual/zh/scripting/tween.html
另外我在《一个全能的挖孔 Shader》中也是使用了缓动系统来让挖孔动起来~
在线预览:https://ifaswind.gitee.io/eazax-cases/?case=newGuide
我的思路是:
this.curDatas
中cc.tween
对 this.curData
的属性进行缓动update
中调用 draw
函数,每帧都重新绘制 this.curDatas
中的数据// 当前雷达图数据 private curDatas: RadarChartData[] = []; protected update() { if (!this.keepUpdating) return; // 绘制当前数据 this.draw(this.curDatas); }
/** * 缓动绘制 * @param data 目标数据 * @param duration 动画时长 */ public to(data: RadarChartData | RadarChartData[], duration: number) { // 处理重复调用 this.unscheduleAllCallbacks(); // 包装单条数据 const datas = Array.isArray(data) ? data : [data]; // 打开每帧更新 this.keepUpdating = true; // 动起来! for (let i = 0; i < datas.length; i++) { // 数值动起来! // 遍历数据中的全部数值,逐个让他们动起来! for (let j = 0; j < this.curDatas[i].values.length; j++) { // 限制最大值为 1(即 100%) const value = datas[i].values[j] > 1 ? 1 : datas[i].values[j]; cc.tween(this.curDatas[i].values) .to(duration, { [j]: value }) .start(); } // 样式动起来! // 没有指定则使用原来的样式! cc.tween(this.curDatas[i]) .to(duration, { lineWidth: datas[i].lineWidth || this.curDatas[i].lineWidth, lineColor: datas[i].lineColor || this.curDatas[i].lineColor, fillColor: datas[i].fillColor || this.curDatas[i].fillColor, joinColor: datas[i].joinColor || this.curDatas[i].joinColor }) .start(); } this.scheduleOnce(() => { // 关闭每帧更新 this.keepUpdating = false; }, duration); }
数值和样式都动起来了:
雷达图组件:https://gitee.com/ifaswind/eazax-ccc/blob/master/components/RadarChart.ts
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
JavaScript中如何实现清除session?在实际的项目中,清除session的需求是常见的,这篇就给大家来分享一下如何实现当前页面并清除session,实现代码如下,感兴趣的朋友可以参考,接下来就跟随小编一起了解看看吧。
本文主要介绍了Vue绑定class和绑定内联样式的实现方法,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
unknown类型比较谦虚,就和他本身的意思一样,他从不祸害到其他的变量,但是any类型就是那种恶霸,属于什么都不管,谁也不敢管的类型,这篇文章主要给大家介绍了关于TypeScript中unknown与any区别的相关资料,需要的朋友可以参考下
这篇文章主要为大家介绍了Vue3 中有场景是 reactive 能做而 ref 做不了的使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
这篇文章主要为大家详细介绍了js实现列表循环滚动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008