如何在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 相关文章推荐
JQuery UI DatePicker中z-index默认为1的解决办法
Sep 28 Javascript
jQuery Clone Bug解决代码
Dec 22 Javascript
JavaScript数组去重的两种方法推荐
Apr 05 Javascript
KnockoutJs快速入门教程
May 16 Javascript
AngularJs学习第五篇从Controller控制器谈谈$scope作用域
Jun 08 Javascript
Node.js使用NodeMailer发送邮件实例代码
Mar 06 Javascript
Vue如何从1.0迁移到2.0
Oct 19 Javascript
JS中移除非数字最多保留一位小数
May 09 Javascript
react MPA 多页配置详解
Oct 18 Javascript
Vue实现多标签选择器
Nov 28 Javascript
基于vue和bootstrap实现简单留言板功能
May 30 Javascript
vue实现锚点定位功能
Jun 29 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
使用网络地址转换实现多服务器负载均衡
2006/10/09 PHP
基于php导出到Excel或CSV的详解(附utf8、gbk 编码转换)
2013/06/25 PHP
使用PHP Socket 编程模拟Http post和get请求
2014/11/25 PHP
JS中==与===操作符的比较
2009/03/21 Javascript
js 页面传参数时 参数值含特殊字符的问题
2009/12/13 Javascript
jQuery 获取对象 定位子对象
2010/05/31 Javascript
Javascript图像处理—平滑处理实现原理
2012/12/28 Javascript
jquery ajax同步异步的执行最终解决方案
2013/04/26 Javascript
利用jquery包将字符串生成二维码图片
2013/09/12 Javascript
JS短信验证码倒计时功能的实现(没有验证码,只有倒计时)
2016/10/27 Javascript
Bootstrap源码解读表单(2)
2016/12/22 Javascript
js实现带缓动动画的导航栏效果
2017/01/16 Javascript
ie下js不执行的几种可能
2017/02/28 Javascript
vue+element的表格实现批量删除功能示例代码
2018/08/17 Javascript
详解vue 兼容IE报错解决方案
2018/12/29 Javascript
小程序二次贝塞尔曲线实现购物车商品曲线飞入效果
2019/01/07 Javascript
微信小程序在text文本实现多种字体样式
2019/11/08 Javascript
JS代码检查工具ESLint介绍与使用方法
2020/02/04 Javascript
vue.js+element 默认提示中英文操作
2020/11/11 Javascript
原生JS实现pc端轮播图效果
2020/12/21 Javascript
python爬虫框架scrapy实现模拟登录操作示例
2018/08/02 Python
pandas DataFrame 删除重复的行的实现方法
2019/01/29 Python
利用Django模版生成树状结构实例代码
2019/05/19 Python
解决os.path.isdir() 判断文件夹却返回false的问题
2019/11/29 Python
python3.7添加dlib模块的方法
2020/07/01 Python
基于PyTorch中view的用法说明
2021/03/03 Python
简单聊聊H5的pushState与replaceState的用法
2018/04/03 HTML / CSS
处理HTML5新标签的浏览器兼容版问题
2017/03/13 HTML / CSS
德国领先的大尺码和超大尺码男装在线零售商:Bigtex
2019/06/22 全球购物
法国最大的在线眼镜店:EasyLunettes
2019/08/26 全球购物
研修第一天随笔感言
2014/02/15 职场文书
红色故事演讲稿
2014/05/22 职场文书
临床专业自荐信
2014/06/22 职场文书
十佳标兵事迹材料
2014/08/18 职场文书
施工安全协议书
2016/03/22 职场文书
利用Python实现Picgo图床工具
2021/11/23 Python