前言
???雷達圖(Radar Chart) 也稱為網絡圖府蛇、星圖或蜘蛛網圖矿辽。
是以從同一點開始的軸上表示的三個或更多個定量變量的二維圖表的形式顯示多元數據的圖形方法芭毙。
適用于顯示三個或更多的維度的變量嘱丢。
???雷達圖常用于??數據統(tǒng)計或對比,對于查看哪些變量具有相似的值伟桅、變量之間是否有異常值都很有用敞掘。
??同時在不少游戲中都有雷達圖的身影,可以很直觀地展示并對比一些數據贿讹。
例如王者榮耀中的對戰(zhàn)資料中就用到了:
??那么在本篇文章中渐逃,皮皮就來分享下在 Cocos Creator 中如何利用 Graphics 組件來繪制炫酷的雷達圖~
文中會對原始代碼進行一定的削減以保證閱讀體驗够掠。
雷達圖組件:https://gitee.com/ifaswind/eazax-ccc/blob/master/components/RadarChart.ts
預覽
??先來看看效果吧~
??兩條數據
??緩動數據
??花里胡哨
??藝術就是爆炸
??逐漸偏離主題
?? 沒有人
?? 比我
?? 更懂
?? 花里胡哨
(??川老師直呼內行)
正文
??Graphics 組件
在我們正式開始制作雷達圖之前民褂,讓我們先來大概了解一下 Cocos Creator 引擎中的 Graphics 組件。
Graphics 組件繼承于 cc.RenderComponent
疯潭,利用該組件我們可以實現(xiàn)畫板和表格之類的功能赊堪。
屬性(Properties)
下面是我們本次將會用到的屬性:
-
lineCap
:設置或返回線條兩端的樣式(無、圓形線帽或方形線帽) -
lineJoin
:設置或返回兩條線相交時的拐角樣式(斜角竖哩、圓角或尖角) -
lineWidth
:設置或返回當前畫筆的粗細(線條的寬度) -
strokeColor
:設置或返回當前畫筆的顏色 -
fillColor
:設置或返回填充用的顏色(油漆桶)
函數(Functions)
下面是我們本次將會用到的函數:
-
moveTo(x, y)
:抬起畫筆并移動到指定位置(不創(chuàng)建線條) -
lineTo(x, y)
:放下畫筆并創(chuàng)建一條直線至指定位置 -
circle(cx, cy, r)
:在指定位置(圓心)畫一個圓 -
close()
:閉合已創(chuàng)建的線條(相當于lineTo(起點)
) -
stroke()
:繪制已創(chuàng)建(但未被繪制)的線條(將線條想象成默認透明的哭廉,此行為則是賦予線條顏色) -
fill()
:填充當前線條包圍的區(qū)域(如果線條沒有閉合則會嘗試”模擬閉合“起點和終點) -
clear()
:擦掉當前畫板上的所有東西
Graphics 組件文檔:http://docs.cocos.com/creator/manual/zh/components/graphics.html?h=graphics
??畫網格
捋一捋
先來看看一個標準的雷達圖有啥特點:
??發(fā)現(xiàn)了嗎?雷達圖的基本特點如下:
- 有 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 個或以上的刻度(不包含中心點):
所以我們需使用一個二維數組來保存所有刻度的坐標,從最外層(即軸線的末端)的刻度開始記錄增淹,方便我們繪制時讀却环谩:
// 創(chuàng)建一個二維數組
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);
// 創(chuàng)建線條
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++) {
// 創(chuàng)建線條
this.graphics.lineTo(scalesSet[0][i].x, scalesSet[0][i].y);
}
// 閉合當前線條(外網格線)
this.graphics.close();
填充并繪制
這里需要注意先填充顏色再繪制線條,要不然軸線和網格線就被擋住了:
// 填充線條包圍的空白區(qū)域
this.graphics.fill();
// 繪制已創(chuàng)建的線條(軸線和外網格線)
this.graphics.stroke();
??于是現(xiàn)在我們就有了這么個玩意兒:
繪制內網格線
當刻度大于 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++) {
// 創(chuàng)建線條
this.graphics.lineTo(scalesSet[i][j].x, scalesSet[i][j].y);
}
// 閉合當前線條(內網格線)
this.graphics.close();
}
// 繪制已創(chuàng)建的線條(內網格線)
this.graphics.stroke();
}
??就這樣我們雷達圖的底子就畫好啦:
??畫數據
捋一捋
編寫畫線邏輯之前成玫,先確定一下我們需要的數據結構:
- 數值數組(必須,小數形式的比例拳喻,至少包含 3 個值)
- 線的寬度(可選哭当,不指定則使用默認值)
- 線的顏色(可選,不指定則使用默認值)
- 填充的顏色(可選冗澈,不指定則使用默認值)
- 節(jié)點的顏色(可選钦勘,不指定則使用默認值)
具體的數據結構如下(導出類型方便外部使用):
/**
* 雷達圖數據
*/
export interface RadarChartData {
/** 數值 */
values: number[];
/** 線的寬度 */
lineWidth?: number;
/** 線的顏色 */
lineColor?: cc.Color;
/** 填充的顏色 */
fillColor?: cc.Color;
/** 節(jié)點的顏色 */
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;
// 計算節(jié)點坐標
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);
}
// 創(chuàng)建線條
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(); // 閉合線條
// 填充包圍區(qū)域
this.graphics.fill();
// 繪制線條
this.graphics.stroke();
// 繪制數據節(jié)點
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();
}
}
}
??到這里我們已經成功制作了一個可用的雷達圖:
??但是!我們的征途是星辰大海颊亮!必須加點料柴梆!
??加料不加價
動起來?
??完全靜態(tài)的雷達圖實在是太無趣太普通终惑,得想想辦法讓它動起來绍在!
??我們的雷達圖數據的數值是數組形式,想到怎么樣才能讓這些數值動起來了嗎雹有?
??別 擔 心 偿渡!
??得益于 Cocos Creator 為我們提供的 Tween 緩動系統(tǒng),讓復雜的數據動起來變得異常簡單霸奕!
??我們只需要這樣溜宽,這樣,然后那樣质帅,是不是很簡單适揉?
cc.tween
支持緩動任意對象的任意屬性緩動系統(tǒng):http://docs.cocos.com/creator/manual/zh/scripting/tween.html
另外我在《一個全能的挖孔 Shader》中也是使用了緩動系統(tǒng)來讓挖孔動起來~
動手吧
我的思路是:
- 將當前的數據保存到當前實例的
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
傳送門
更多分享
《Cocos Creator 性能優(yōu)化:DrawCall》
公眾號
菜鳥小棧
我是陳皮皮,這是我的個人公眾號兵怯,專注但不僅限于游戲開發(fā)彩匕、前端和后端技術記錄與分享。
每一篇原創(chuàng)都非常用心摇零,你的關注就是我原創(chuàng)的動力推掸!
Input and output.
[圖片上傳失敗...(image-86261-1597845635091)]