如何在CocosCreator里画个炫酷的雷达图


Posted in Javascript onApril 16, 2021

前言

雷达图(Radar Chart) 也称为网络图、星图或蜘蛛网图。

是以从同一点开始的轴上表示的三个或更多个定量变量的二维图表的形式显示多元数据的图形方法。

适用于显示三个或更多的维度的变量。

如何在CocosCreator里画个炫酷的雷达图

雷达图常用于?数据统计或对比,对于查看哪些变量具有相似的值、变量之间是否有异常值都很有用。

同时在不少游戏中都有雷达图的身影,可以很直观地展示并对比一些数据。

例如王者荣耀中的对战资料中就用到了:

如何在CocosCreator里画个炫酷的雷达图

那么在本篇文章中,皮皮就来分享下在 Cocos Creator 中如何利用 Graphics 组件来绘制炫酷的雷达图~

文中会对原始代码进行一定的削减以保证阅读体验。

雷达图组件:https://gitee.com/ifaswind/eazax-ccc/blob/master/components/RadarChart.ts


预览

先来看看效果吧~

在线预览:https://ifaswind.gitee.io/eazax-cases/?case=radarChart

两条数据

如何在CocosCreator里画个炫酷的雷达图

缓动数据

如何在CocosCreator里画个炫酷的雷达图

花里胡哨

如何在CocosCreator里画个炫酷的雷达图

艺术就是爆炸

如何在CocosCreator里画个炫酷的雷达图

逐渐偏离主题

如何在CocosCreator里画个炫酷的雷达图

正文

Graphics 组件

在我们正式开始制作雷达图之前,让我们先来大概了解一下 Cocos Creator 引擎中的 Graphics 组件。

Graphics 组件继承于 cc.RenderComponent,利用该组件我们可以实现画板和表格之类的功能。

属性(Properties)

下面是我们本次将会用到的属性:

  • lineCap:设置或返回线条两端的样式(无、圆形线帽或方形线帽)
  • lineJoin:设置或返回两条线相交时的拐角样式(斜角、圆角或尖角)
  • lineWidth:设置或返回当前画笔的粗细(线条的宽度)
  • strokeColor:设置或返回当前画笔的颜色
  • fillColor:设置或返回填充用的颜色(油漆桶)

函数(Functions)

下面是我们本次将会用到的函数:

  • 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

画网格

先来看看一个标准的雷达图有啥特点:

如何在CocosCreator里画个炫酷的雷达图

发现了吗?雷达图的基本特点如下:

  • 有 3 条或以上的轴线
  • 轴与轴之间的夹角相同
  • 每条轴上除中心点外应至少有 1 个刻度
  • 每条轴上都有相同的刻度
  • 刻度与刻度之间的距离也相同
  • 轴之间的刻度相连形成网格线

计算轴线角度

先算出轴之间的夹角度数 [ 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 个或以上的刻度(不包含中心点)

如何在CocosCreator里画个炫酷的雷达图

所以我们需使用一个二维数组来保存所有刻度的坐标,从最外层(即轴线的末端)的刻度开始记录,方便我们绘制时读取:

// 创建一个二维数组
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();

于是现在我们就有了这么个玩意儿:

如何在CocosCreator里画个炫酷的雷达图

绘制内网格线

当刻度大于 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();
}

就这样我们雷达图的底子就画好啦:

如何在CocosCreator里画个炫酷的雷达图

画数据 

编写画线逻辑之前,先确定一下我们需要的数据结构:

  • 数值数组(必须,小数形式的比例,至少包含 3 个值)
  • 线的宽度(可选,不指定则使用默认值)
  • 线的颜色(可选,不指定则使用默认值)
  • 填充的颜色(可选,不指定则使用默认值)
  • 节点的颜色(可选,不指定则使用默认值)

具体的数据结构如下(导出类型方便外部使用):

/**
 * 雷达图数据
 */
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();
        }

    }
}

到这里我们已经成功制作了一个可用的雷达图:

如何在CocosCreator里画个炫酷的雷达图

但是!我们的征途是星辰大海!必须加点料!

完全静态的雷达图实在是太无趣太普通,得想想办法让它动起来!

我们的雷达图数据的数值是数组形式,想到怎么样才能让这些数值动起来了吗?

得益于 Cocos Creator 为我们提供的 Tween 缓动系统,让复杂的数据动起来变得异常简单!

我们只需要这样,这样,然后那样,是不是很简单?

cc.tween 支持缓动任意对象的任意属性

缓动系统:http://docs.cocos.com/creator/manual/zh/scripting/tween.html

另外我在《一个全能的挖孔 Shader》中也是使用了缓动系统来让挖孔动起来~

在线预览:https://ifaswind.gitee.io/eazax-cases/?case=newGuide

我的思路是:

  1. 将当前的数据保存到当前实例的 this.curDatas
  2. 接收到新的数据时,使用 cc.tweenthis.curData 的属性进行缓动
  3. 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);
}

数值和样式都动起来了:

如何在CocosCreator里画个炫酷的雷达图

雷达图组件:https://gitee.com/ifaswind/eazax-ccc/blob/master/components/RadarChart.ts

以上就是如何在CocosCreator里画个炫酷的雷达图的详细内容,更多关于CocosCreator画个雷达图的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
JavaScript对象的property属性详解
Apr 01 Javascript
js+html5实现canvas绘制镂空字体文本的方法
Jun 05 Javascript
JS实现网页标题栏显示当前时间和日期的完整代码
Nov 02 Javascript
Vue.js实现网格列表布局转换方法
Aug 25 Javascript
JS动态修改网页body的背景色实例代码
Oct 07 Javascript
seaJs使用心得之exports与module.exports的区别实例分析
Oct 13 Javascript
JS通过位运算实现权限加解密
Aug 14 Javascript
vue 项目接口管理的实现
Jan 17 Javascript
jQuery实现左右两个列表框的内容相互移动功能示例
Jan 27 jQuery
深入浅出了解Node.js Streams
May 27 Javascript
解决vue页面渲染但dom没渲染的操作
Jul 27 Javascript
vue3使用vue-count-to组件的实现
Dec 25 Vue.js
jquery插件实现图片悬浮
详解CocosCreator消息分发机制
Apr 16 #Javascript
CocosCreator入门教程之网络通信
Apr 16 #Javascript
JavaScript嵌入百度地图API的最详细方法
用javascript制作qq注册动态页面
利用javaScript处理常用事件详解
Apr 14 #Javascript
带你彻底理解JavaScript中的原型对象
Apr 14 #Javascript
You might like
详解PHP中strlen和mb_strlen函数的区别
2014/03/07 PHP
PHP实现HTTP断点续传的方法
2015/06/17 PHP
PHP并发多进程处理利器Gearman使用介绍
2016/05/16 PHP
浅谈PHP中new self()和new static()的区别
2017/08/11 PHP
Javascript 更新 JavaScript 数组的 uniq 方法
2008/01/23 Javascript
获取当前网页document.url location.href区别总结
2008/05/10 Javascript
JavaScript中使用正则匹配多条,且获取每条中的分组数据
2010/11/30 Javascript
JQuery获取浏览器窗口内容部分高度的代码
2012/02/24 Javascript
Js 去掉字符串中的空格(实现代码)
2013/11/19 Javascript
JS字符串截取函数实例
2013/12/27 Javascript
Jquery仿IGoogle实现可拖动窗口示例代码
2014/08/22 Javascript
深入理解JavaScript系列(36):设计模式之中介者模式详解
2015/03/04 Javascript
如何根据百度地图计算出两地之间的驾驶距离(两种语言js和C#)
2015/10/29 Javascript
JS实现选项卡插件的两种写法(jQuery和class)
2020/12/30 jQuery
Python获取某一天是星期几的方法示例
2017/01/17 Python
Python使用正则表达式抓取网页图片的方法示例
2017/04/21 Python
Python2和Python3中print的用法示例总结
2017/10/25 Python
使用python和Django完成博客数据库的迁移方法
2018/01/05 Python
Python实现抢购IPhone手机
2018/02/07 Python
基于python的ini配置文件操作工具类
2019/04/24 Python
在django中实现页面倒数几秒后自动跳转的例子
2019/08/16 Python
python第三方库学习笔记
2020/02/07 Python
java关于string最常出现的面试题整理
2021/01/18 Python
HTML5 拖拽批量上传文件的示例代码
2018/03/28 HTML / CSS
HTML5新增的Css选择器、伪类介绍
2013/08/07 HTML / CSS
Under Armour安德玛德国官网:美国高端运动科技品牌
2019/03/09 全球购物
size?爱尔兰官方网站:英国伦敦的球鞋精品店
2019/03/31 全球购物
KOHLER科勒美国官网:国际著名卫浴橱柜领先品牌
2020/06/27 全球购物
C语言基础笔试题
2013/04/27 面试题
财务会计人员求职的自我评价
2014/01/13 职场文书
一岗双责责任书
2014/04/15 职场文书
水利专业大学生职业生涯规划书范文
2014/09/17 职场文书
学生旷课检讨书500字
2014/10/28 职场文书
办公室年度工作总结2015
2015/05/21 职场文书
网络舆情信息简报
2015/07/21 职场文书
Python字符串的转义字符
2022/04/07 Python