如何在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 相关文章推荐
js获取class的所有元素
Mar 28 Javascript
鼠标拖动实现DIV排序示例代码
Oct 14 Javascript
使用jquery修改表单的提交地址基本思路
Jun 04 Javascript
有关Promises异步问题详解
Nov 13 Javascript
js中获取jsp表单中radio类型的值简单实例
Aug 15 Javascript
微信小程序 图片宽度自适应的实现
Apr 06 Javascript
原生javascript实现分页效果
Apr 21 Javascript
利用C/C++编写node.js原生模块的方法教程
Jul 07 Javascript
纯js实现页面返回顶部的动画(超简单)
Aug 10 Javascript
vue实现模态框的通用写法推荐
Feb 26 Javascript
vue路由教程之静态路由
Sep 03 Javascript
基于 Vue 的 Electron 项目搭建过程图文详解
Jul 22 Javascript
jquery插件实现图片悬浮
详解CocosCreator消息分发机制
Apr 16 #Javascript
CocosCreator入门教程之网络通信
Apr 16 #Javascript
JavaScript嵌入百度地图API的最详细方法
用javascript制作qq注册动态页面
利用javaScript处理常用事件详解
Apr 14 #Javascript
带你彻底理解JavaScript中的原型对象
Apr 14 #Javascript
You might like
Flash空降上海 化身大魔王接受挑战
2020/03/02 星际争霸
ThinkPHP验证码使用简明教程
2014/03/05 PHP
Yii2学习笔记之汉化yii设置表单的描述(属性标签attributeLabels)
2017/02/07 PHP
ASP Json Parser修正版
2009/12/06 Javascript
js function定义函数使用心得
2010/04/15 Javascript
JavaScript高级程序设计 DOM学习笔记
2011/09/10 Javascript
JavaScript程序员应该知道的45个实用技巧
2014/03/04 Javascript
自己动手写的jquery分页控件(非常简单实用)
2015/10/28 Javascript
获取阴历(农历)和当前日期的js代码
2016/02/15 Javascript
AngularJS递归指令实现Tree View效果示例
2016/11/07 Javascript
读Javascript高性能编程重点笔记
2016/12/21 Javascript
非常实用的vue导航钩子
2017/03/20 Javascript
详解用node-images 打造简易图片服务器
2017/05/08 Javascript
Node.js操作redis实现添加查询功能
2017/05/25 Javascript
使用原生js+canvas实现模拟心电图的实例
2017/09/20 Javascript
vue中实现左右联动的效果
2018/06/22 Javascript
JS回调函数 callback的理解与使用案例分析
2019/09/09 Javascript
layui的layedit富文本赋值方法
2019/09/18 Javascript
详解Typescript里的This的使用方法
2021/01/08 Javascript
[44:22]完美世界DOTA2联赛循环赛 FTD vs PXG BO2第一场 11.01
2020/11/02 DOTA
[49:30]DOTA2-DPC中国联赛正赛 Dragon vs Dynasty BO3 第二场 3月4日
2021/03/11 DOTA
Python深入学习之装饰器
2014/08/31 Python
Python爬取读者并制作成PDF
2015/03/10 Python
python实现查找excel里某一列重复数据并且剔除后打印的方法
2015/05/26 Python
使用PyInstaller将python转成可执行文件exe笔记
2018/05/26 Python
Python3如何对urllib和urllib2进行重构
2019/11/25 Python
Python3标准库之functools管理函数的工具详解
2020/02/27 Python
Windows下Pycharm远程连接虚拟机中Centos下的Python环境(图文教程详解)
2020/03/19 Python
全球最大的网上自行车商店:Chain Reaction Cycles
2016/12/02 全球购物
捐助倡议书范文
2014/04/15 职场文书
业务内勤岗位职责
2014/04/30 职场文书
公司周年庆典策划方案
2014/05/17 职场文书
欢迎标语大全
2014/06/21 职场文书
创优争先心得体会
2014/09/11 职场文书
2016年小学感恩节活动总结
2016/04/01 职场文书
JavaScript 中for/of,for/in 的详细介绍
2021/11/17 Javascript