如何在CocosCreator里畫個炫酷的雷達(dá)圖
前言
雷達(dá)圖(Radar Chart) 也稱為網(wǎng)絡(luò)圖、星圖或蜘蛛網(wǎng)圖。
是以從同一點開始的軸上表示的三個或更多個定量變量的二維圖表的形式顯示多元數(shù)據(jù)的圖形方法。
適用于顯示三個或更多的維度的變量。
雷達(dá)圖常用于📚數(shù)據(jù)統(tǒng)計或?qū)Ρ?,對于查看哪些變量具有相似的值、變量之間是否有異常值都很有用。
同時在不少游戲中都有雷達(dá)圖的身影,可以很直觀地展示并對比一些數(shù)據(jù)。
例如王者榮耀中的對戰(zhàn)資料中就用到了:
那么在本篇文章中,皮皮就來分享下在 Cocos Creator 中如何利用 Graphics 組件來繪制炫酷的雷達(dá)圖~
文中會對原始代碼進(jìn)行一定的削減以保證閱讀體驗。
雷達(dá)圖組件:https://gitee.com/ifaswind/eazax-ccc/blob/master/components/RadarChart.ts
預(yù)覽
先來看看效果吧~
在線預(yù)覽:https://ifaswind.gitee.io/eazax-cases/?case=radarChart
兩條數(shù)據(jù)
緩動數(shù)據(jù)
花里胡哨
藝術(shù)就是爆炸
逐漸偏離主題
正文
Graphics 組件
在我們正式開始制作雷達(dá)圖之前,讓我們先來大概了解一下 Cocos Creator 引擎中的 Graphics 組件。
Graphics 組件繼承于 cc.RenderComponent
,利用該組件我們可以實現(xiàn)畫板和表格之類的功能。
屬性(Properties)
下面是我們本次將會用到的屬性:
lineCap
:設(shè)置或返回線條兩端的樣式(無、圓形線帽或方形線帽)lineJoin
:設(shè)置或返回兩條線相交時的拐角樣式(斜角、圓角或尖角)lineWidth
:設(shè)置或返回當(dāng)前畫筆的粗細(xì)(線條的寬度)strokeColor
:設(shè)置或返回當(dāng)前畫筆的顏色fillColor
:設(shè)置或返回填充用的顏色(油漆桶)
函數(shù)(Functions)
下面是我們本次將會用到的函數(shù):
moveTo(x, y)
:抬起畫筆并移動到指定位置(不創(chuàng)建線條)lineTo(x, y)
:放下畫筆并創(chuàng)建一條直線至指定位置circle(cx, cy, r)
:在指定位置(圓心)畫一個圓close()
:閉合已創(chuàng)建的線條(相當(dāng)于lineTo(起點)
)stroke()
:繪制已創(chuàng)建(但未被繪制)的線條(將線條想象成默認(rèn)透明的,此行為則是賦予線條顏色)fill()
:填充當(dāng)前線條包圍的區(qū)域(如果線條沒有閉合則會嘗試”模擬閉合“起點和終點)clear()
:擦掉當(dāng)前畫板上的所有東西
Graphics 組件文檔:http://docs.cocos.com/creator/manual/zh/components/graphics.html?h=graphics
畫網(wǎng)格
先來看看一個標(biāo)準(zhǔn)的雷達(dá)圖有啥特點:
發(fā)現(xiàn)了嗎?雷達(dá)圖的基本特點如下:
- 有 3 條或以上的軸線
- 軸與軸之間的夾角相同
- 每條軸上除中心點外應(yīng)至少有 1 個刻度
- 每條軸上都有相同的刻度
- 刻度與刻度之間的距離也相同
- 軸之間的刻度相連形成網(wǎng)格線
計算軸線角度
先算出軸之間的夾角度數(shù) [ 360 ÷ 軸數(shù)
],再計算所有軸的角度:
this.angles = []; // 軸間夾角 const iAngle = 360 / this.axes; for (let i = 0; i < this.axes; i++) { // 計算 const angle = iAngle * i; this.angles.push(angle); }
計算刻度坐標(biāo)
雷達(dá)圖至少擁有 3 條軸,且每條軸上都應(yīng)有 1 個或以上的刻度(不包含中心點):
所以我們需使用一個二維數(shù)組來保存所有刻度的坐標(biāo),從最外層(即軸線的末端)的刻度開始記錄,方便我們繪制時讀?。?/p>
// 創(chuàng)建一個二維數(shù)組 let scalesSet: cc.Vec2[][] = []; for (let i = 0; i < 軸上刻度個數(shù); i++) { // 用來保存當(dāng)前層上的刻度坐標(biāo) let scales = []; // 計算刻度在軸上的位置 const length = 軸線長度 - (軸線長度 / 軸上刻度個數(shù) * i); for (let j = 0; j < this.angles.length; j++) { // 將角度轉(zhuǎn)為弧度 const radian = (Math.PI / 180) * this.angles[j]; // 根據(jù)三角公式計算刻度相對于中心點(0, 0)的坐標(biāo) const pos = cc.v2(length * Math.cos(radian), length * Math.sin(radian)); // 推進(jìn)數(shù)組 scales.push(pos); } // 推進(jìn)二維數(shù)組 scalesSet.push(scales); }
繪制軸線和外網(wǎng)格線
軸線
連接中心點 (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); }
外網(wǎng)格線
連接所有軸上最外層 scalesSet[0]
的刻度即形成外網(wǎng)格線:
// 畫筆移動至第一個點 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); } // 閉合當(dāng)前線條(外網(wǎng)格線) this.graphics.close();
填充并繪制
這里需要注意先填充顏色再繪制線條,要不然軸線和網(wǎng)格線就被擋住了:
// 填充線條包圍的空白區(qū)域 this.graphics.fill(); // 繪制已創(chuàng)建的線條(軸線和外網(wǎng)格線) this.graphics.stroke();
于是現(xiàn)在我們就有了這么個玩意兒:
繪制內(nèi)網(wǎng)格線
當(dāng)刻度大于 1 個時就需要繪制內(nèi)網(wǎng)格線,從刻度坐標(biāo)集的下標(biāo) 1 開始繪制:
// 刻度大于 1 個時才繪制內(nèi)網(wǎng)格線 if (scalesSet.length > 1) { // 從下邊 1 開始(下標(biāo) 0 是外網(wǎng)格線) 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); } // 閉合當(dāng)前線條(內(nèi)網(wǎng)格線) this.graphics.close(); } // 繪制已創(chuàng)建的線條(內(nèi)網(wǎng)格線) this.graphics.stroke(); }
就這樣我們雷達(dá)圖的底子就畫好啦:
畫數(shù)據(jù)
編寫畫線邏輯之前,先確定一下我們需要的數(shù)據(jù)結(jié)構(gòu):
- 數(shù)值數(shù)組(必須,小數(shù)形式的比例,至少包含 3 個值)
- 線的寬度(可選,不指定則使用默認(rèn)值)
- 線的顏色(可選,不指定則使用默認(rèn)值)
- 填充的顏色(可選,不指定則使用默認(rèn)值)
- 節(jié)點的顏色(可選,不指定則使用默認(rèn)值)
具體的數(shù)據(jù)結(jié)構(gòu)如下(導(dǎo)出類型方便外部使用):
/** * 雷達(dá)圖數(shù)據(jù) */ export interface RadarChartData { /** 數(shù)值 */ values: number[]; /** 線的寬度 */ lineWidth?: number; /** 線的顏色 */ lineColor?: cc.Color; /** 填充的顏色 */ fillColor?: cc.Color; /** 節(jié)點的顏色 */ joinColor?: cc.Color; }
繪制數(shù)據(jù)
繪制數(shù)據(jù)比較簡單,我們只需要算出數(shù)據(jù)點在圖表中的位置,并將數(shù)據(jù)連起來就好了。
在 draw
函數(shù)中我們接收一份或以上的雷達(dá)圖數(shù)據(jù),并按照順序遍歷繪制出來(⚠️長代碼警告):
/** * 繪制數(shù)據(jù) * @param data 數(shù)據(jù) */ public draw(data: RadarChartData | RadarChartData[]) { // 處理數(shù)據(jù) const datas = Array.isArray(data) ? data : [data]; // 開始繪制數(shù)據(jù) 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é)點坐標(biāo) 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(); // 繪制數(shù)據(jù)節(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(); } } }
到這里我們已經(jīng)成功制作了一個可用的雷達(dá)圖:
但是!我們的征途是星辰大海!必須加點料!
完全靜態(tài)的雷達(dá)圖實在是太無趣太普通,得想想辦法讓它動起來!
我們的雷達(dá)圖數(shù)據(jù)的數(shù)值是數(shù)組形式,想到怎么樣才能讓這些數(shù)值動起來了嗎?
得益于 Cocos Creator 為我們提供的 Tween 緩動系統(tǒng),讓復(fù)雜的數(shù)據(jù)動起來變得異常簡單!
我們只需要這樣,這樣,然后那樣,是不是很簡單?
cc.tween
支持緩動任意對象的任意屬性緩動系統(tǒng):http://docs.cocos.com/creator/manual/zh/scripting/tween.html
另外我在《一個全能的挖孔 Shader》中也是使用了緩動系統(tǒng)來讓挖孔動起來~
在線預(yù)覽:https://ifaswind.gitee.io/eazax-cases/?case=newGuide
我的思路是:
- 將當(dāng)前的數(shù)據(jù)保存到當(dāng)前實例的
this.curDatas
中 - 接收到新的數(shù)據(jù)時,使用
cc.tween
對this.curData
的屬性進(jìn)行緩動 - 在
update
中調(diào)用draw
函數(shù),每幀都重新繪制this.curDatas
中的數(shù)據(jù)
每幀更新
// 當(dāng)前雷達(dá)圖數(shù)據(jù) private curDatas: RadarChartData[] = []; protected update() { if (!this.keepUpdating) return; // 繪制當(dāng)前數(shù)據(jù) this.draw(this.curDatas); }
緩動數(shù)據(jù)
/** * 緩動繪制 * @param data 目標(biāo)數(shù)據(jù) * @param duration 動畫時長 */ public to(data: RadarChartData | RadarChartData[], duration: number) { // 處理重復(fù)調(diào)用 this.unscheduleAllCallbacks(); // 包裝單條數(shù)據(jù) const datas = Array.isArray(data) ? data : [data]; // 打開每幀更新 this.keepUpdating = true; // 動起來! for (let i = 0; i < datas.length; i++) { // 數(shù)值動起來! // 遍歷數(shù)據(jù)中的全部數(shù)值,逐個讓他們動起來! 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(() => { // 關(guān)閉每幀更新 this.keepUpdating = false; }, duration); }
數(shù)值和樣式都動起來了:
雷達(dá)圖組件:https://gitee.com/ifaswind/eazax-ccc/blob/master/components/RadarChart.ts
以上就是如何在CocosCreator里畫個炫酷的雷達(dá)圖的詳細(xì)內(nèi)容,更多關(guān)于CocosCreator畫個雷達(dá)圖的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Javascript & DHTML DOM基礎(chǔ)和基本API
DOM是文檔對象模型(Document Object Model,是基于瀏覽器編程(在本教程中,可以說就是DHTML編程)的一套API接口,W3C出臺的推薦標(biāo)準(zhǔn),每個瀏覽器都有一些細(xì)微的差別,其中以Mozilla的瀏覽器最與標(biāo)準(zhǔn)接近。2008-07-07