淺談canvas繪畫王者榮耀--雷達圖

来源:http://www.cnblogs.com/chedabang/archive/2017/12/09/8011321.html
-Advertisement-
Play Games

背景 : 一日晚上下班的我靜靜的靠在角落上聽著歌,這時"滴!滴!"手機上傳來一陣qq消息。原來我人在問王者榮耀的雷達圖在頁面上如何做出來的,有人回答用canvas繪畫。那麼問題來了,已經好久沒有使用canvas繪畫了東西。 SO ,就想自己畫一個canvas雷達圖,順便重新回顧一下canvas的知識 ...


背景
一日晚上下班的我靜靜的靠在角落上聽著歌,這時"滴!滴!"手機上傳來一陣qq消息。原來我人在問王者榮耀的雷達圖在頁面上如何做出來的,有人回答用canvas繪畫。那麼問題來了,已經好久沒有使用canvas繪畫了東西。
SO,就想自己畫一個canvas雷達圖,順便重新回顧一下canvas的知識點。

王者榮耀雷達圖的基本構成。

聊天記錄當中的雷達圖不是特別清楚,所以我這邊截圖了自己的一個戰績雷達圖。
王者榮耀截圖
是不是有被我的戰績嚇到了,害不害怕!
好了扯遠了,讓我們回到正題上來。
通過截圖上面的雷達圖基本主體是一個正六邊形,每個頂點則配有相應的文字說明。
然後就是中間紅色區域部分則由對角線上的點,連成一圈填充構成。因此這裡我們稱它為數據填充區
所以這個雷達圖我們分為三步來完成。
①正六邊形
②數據填充區
③繪製文本

正六變形的坐標點解析

在繪畫這個正六邊形的時候,先讓我們對於這個正六邊形進行簡單的數學分析。
這裡先用畫板畫一個正六變形,然後進行切割並切角。
六邊形.png

是吧,借用以前高中還是初中的數學,正六邊形的內角和720°,那麼每一個對角就是120°。在已知對角線的長度。那麼通過sin60°cos60°一類的,那個可以求出各個三角形的邊長。

可是問題來了,這裡我們要計算的是各個坐標點。而canvas的坐標軸是從左上角算(0,0)原點的單象限坐標軸。假設六邊形的中心點是(250,250)、對角線的長度是100*2,那麼按照三角函數推斷:
bottom-center坐標:(250, 250 + 100)
bottom-left坐標:(250 - 100*sin(60°), 250+100*cos(60°))
top-left坐標:(250 - 100*sin(60°), 250-100*cos(60°))
top-center坐標:(250, 250 - 100)
top-right坐標:(250 + 100*sin(60°), 250-100*cos(60°))
bottom-right的坐標:(250 + 100*sin(60°), 250+100*cos(60°))

坐標是出來了,但是一個點一個點去繪畫是不是有點太low了!
腫麽辦?
啦啦啦啦!
那麼就到了我們找規律的時間來了!

但是在找規律的同時,為毛中心點的X軸和別人不一樣,為毛一會加一會減。

所以當思考各坐標點參數的規律的時候,讓先回顧以前的函數角度圖表
函數角度圖表.png

看完這個函數參照圖之後,讓我再次修改一下6個點的書寫方式。
bottom-center坐標:(250 + 100*sin(0°), 250 + 100*cos(0°))
bottom-left坐標:(250 + 100*sin(300°), 250+100*cos(300°))
top-left坐標:(250 + 100*sin(240°), 250-100*cos(240°))
top-center坐標:(250 +100*sin(180°), 250 + 100*cos(180°))
top-right坐標:(250 + 100*sin(120°), 250-100*cos(120°))
bottom-right的坐標:(250 + 100*sin60°), 250+100*cos(60°))

這個時候再看組坐標數據點,是不是感覺有點意思!

那麼這個時候我們便可以通過一個for迴圈,用一個數組把這6個坐標點給記錄下來。

var pointArr = [];
for (var i = 0; i < 6; i++) {
        pointArr[i] = {};
       pointArr[i].x = 250 + 100 * Math.sin(60 * i);
        pointArr[i].y = 250 + 100* Math.cos(60 * i);
    }

1.1 繪畫正六邊形

前面既然,將正六邊形的坐標點通過一個for迴圈解析出來。那麼就是代碼繪畫正六邊形了:

<style>
        canvas {
            display: block;
            width: 500px;
            height: 500px;
        }
</style>
<body>
    <canvas class="radar"></canvas>
</body>
<script>
    var canvas = document.getElementsByClassName('radar')[0];
    canvas.width = 500;
    canvas.height = 500;
    var ctx = canvas.getContext('2d');
    ctx.save();
    ctx.strokeStyle = '#888888';  // 設置線條顏色
    var lineArr = [];
    var rAngle = Math.PI * 2 / 6;  // 算出每一個內角和
    console.log(rAngle);
    var rCenter = 250;  // 確定中心點
    var curR = 100;   // 確定半徑長度
    ctx.beginPath();
    for (var i = 0; i < 6; i++) {
        lineArr[i] = {};
        lineArr[i].y = rCenter + curR * Math.cos(rAngle * i);
        lineArr[i].x = rCenter + curR * Math.sin(rAngle * i);
        ctx.lineTo(lineArr[i].x, lineArr[i].y);
    }
    ctx.closePath();
    ctx.stroke();
    ctx.restore();

繪畫的六邊形.png
啦啦啦!!!一個正六邊形就這麼的畫出來。
備註:這裡rAngle這裡是很靈活的,如果說畫18正邊形,就除以18,然後for迴圈18次就ok了.

正18邊形.png

哈哈!!感覺發現了新大陸了!繪製正多邊形的貌似可以按照這個規律來!!

1.2 繪畫對角線

既然前面有一個數組存儲各個坐標點,所以讓每個對角線對角點直線想連就ok了!

ctx.strokeStyle = '#e8ddc7';  // PS吸管那麼一吸
    ctx.save();
    ctx.beginPath();
    // for (var j = 0; j < 3; j++) {
    //     ctx.lineTo(lineArr[j].x, lineArr[j].y);
    //     ctx.lineTo(lineArr[j+3].x, lineArr[j+3].y);
    //     ctx.stroke();
    // }
    for (var j = 0; j < 3; j++) {
        ctx.moveTo(lineArr[j].x, lineArr[j].y);
        ctx.lineTo(lineArr[j + 3].x, lineArr[j + 3].y);
        ctx.stroke();
    }
    ctx.closePath();
    ctx.restore();

2.1數據填充區

關於數據填充區,也就是雷達圖當中,不規則的紅色半透明的六邊形。其實就是就可以看做中心點,到各個邊角點之間線段為一區間這。之後就是將這個區間分成若幹份,你占這個這個區間多少份,滿份就是邊角點,零份就是原點。

觀察前面的雷達圖當中,B等級大概占據某個等級的50%左右。而B前面還有等級A、S。
所以當S等級時候,可以看作區間 / 1。
B等級看作區間 / 2, 那麼A就是 區間 / 1.5.
以此類推就可以得出剩下 C 就是區間 / 2.5、D:區間/ 3

這裡我就不用for迴圈書寫了,直接偷懶手寫一個對象。

// 繪製數據區域
var letterData = {
        'S': 1,
        'A': 1.5,
        'B': 2,
        'C': 2.5,
        'D': 3
    }
ctx.save();
ctx.beginPath();
for (var i = 0; i < 6; i++) {
        lineArr[i].yEnd = rCenter + curR * Math.cos(rAngle * i) / (letterData[rData[i][1]]);
        lineArr[i].xEnd = rCenter + curR * Math.sin(rAngle * i) / (letterData[rData[i][1]]);
        ctx.lineTo(lineArr[i].xEnd, lineArr[i].yEnd); 
        console.log(lineArr);
 }
ctx.closePath();
ctx.stroke();
ctx.fillStyle = 'rgba(255, 0, 0, 0.5)'; 
ctx.fill();

雷達圖能力區域

2.2 對數據填充區域繪畫小圓點和邊長

當我們回歸到前面的截圖發現,需要單獨把數據填充區域的的各個點位置給加強,並把邊角用更深的線條的描繪出來。

ctx.lineWidth = 2;  //設置數據填充區域的線條顏色
ctx.strokeStyle = '#dd3f26';  //設置填充區域的顏色
var point = 3; //設置數據填充區域的小圓點大小
for (var i = 0; i < 6; i++) {
        ctx.beginPath();
        ctx.arc(lineArr[i].xEnd, lineArr[i].yEnd, point, 0, Math.PI * 2); 
        ctx.fillStyle = 'rgba(255, 0, 0, 0.8)';
        ctx.fill();
        console.log(lineArr);
    }
    ctx.restore();

修飾完成數據區域

3.1 繪製文本

王者榮耀雷達文本是需要繪製兩點,
①用黑色16px字體繪製各點描述點
②用紅色30px字體繪製各點能力級別

但是估計看到繪製文本,估計有的小伙伴就會說。不是有數組的存儲各個邊角的坐標,直接一個for迴圈依次根據各個點繪畫出來不就OK了。

 // 繪製文本
    var rData = [
        ['生存', 'S'],
        ['經濟', 'S'],
        ['輸出', 'S'],
        ['KDA', 'B'],
        ['打野', 'B'],
        ['推進', 'S']
    ]
    ctx.save();
    ctx.font = '16px Microsoft Yahei';  //設置字體
    ctx.fillStyle = '#000';  // 顏色
    for (var i = 0; i < 6; i++) {
        var y = rCenter + curR * Math.cos(rAngle * i);
        var x = rCenter + curR * Math.sin(rAngle * i);
        ctx.fillText(rData[i][0], x, y);
    }
    ctx.restore();

瀏覽器最終顯示的視覺效果:

驚不驚喜

是不是覺得很驚喜,這裡輸出經濟位置勉強還行,但是剩下的文字位置就偏差了許多了。所以在繪製文字的時候,還得針對文字的坐標位置進行相應的調整。

3.2 繪製文本--描述

既然直接調用坐標的位置會出問題,那麼讓根據上文中的圖片文字的規則簡單分析。
①如果X軸 == 中心點,那麼就判斷Y軸。比中心點大文字下移一點,反之文字上移一點。
②如果X軸 < 中心點,那麼文字X軸位置就左移動一點,反正右移動距離。

 // 繪製文本
    ctx.save();
    var fontSize = 16;
    ctx.font =  fontSize + 'px Microsoft Yahei';
    ctx.textBaseline="middle"; //設置基線參考點
    ctx.textAlign="center";  // 文本居中
    ctx.fillStyle = '#000';
    for (var i = 0; i < 6; i++) {
        var y = rCenter + curR * Math.cos(rAngle * i);
        var x = rCenter + curR * Math.sin(rAngle * i);
        console.log(Math.sin(rAngle * i))
        var s_width = ctx.measureText(rData[i][0]).width; //獲取當前繪畫的字體寬度
        if ( x == rCenter) {
            if (y > rCenter ) {
                ctx.fillText(rData[i][0], x - s_width/2, y + fontSize);
            } else {
                ctx.fillText(rData[i][0], x - s_width/2, y - fontSize);
            }
        } else if ( x > rCenter) {
            console.log(rData[i][0]);
            ctx.fillText(rData[i][0], x + s_width*1.5, y);
        } else {
             ctx.fillText(rData[i][0], x - s_width*1.5, y);
        }

調整好的樣式

這裡多了好幾個不常用的屬性,下麵就是介紹這些屬性的特點:
ctx.textBaseline: 設置或返回在繪製文本時使用的當前文本基線
說到基線,各位童鞋想一想咱們以前英文練習本,上面有著一條條線條
英語練習本

瞬間回憶到當年被罰抄英語單詞的歲月,一把辛酸淚呀。

網頁設計字體也有一個基線的存在,因此canvas的基線點就是直接從坐標點划出一條橫線基線。
這裡從網路上截圖一張,通過設置基線參考位置,看看文本所在位置的改變。

圖片來自網路

ctx.textAlign: 這個文本水平居中,不過和CSS當中的居中不一樣的是,他是從坐標點划出一條豎線分割文本的。

圖片來自網路

ctx.measureText : 返回包含指定文本寬度的對象。

通俗一點的就是說,就是獲取你繪製文本的寬度。假設一排文字內容為'Hello World', size為16px大小文本。在這裡高度都是16px穩定不變,這樣canvas畫其他元素對這個位置只需要Y軸移動這個文本的'size'大小就可以避免覆蓋到上面。

但是如果要X軸去移動位置,你根本不知道'Hello World'這串文本的長度。那麼這個時候就需要ctx.measureText這個方法,獲取當前你繪製文本的寬度。

3.2 繪製文本--能力級別

既然前面已經介紹了描述的繪畫方法,那麼依葫蘆畫瓢。讓我們一併開始繪製能力級別的文本。

// 繪製文本
    ctx.save();
    var fontSize = 16;
    var maxfontSize = 30;
    ctx.font =  fontSize + 'px Microsoft Yahei';
    ctx.textBaseline="middle";
    ctx.textAlign="center";
    for (var i = 0; i < 6; i++) {
        var y = rCenter + curR * Math.cos(rAngle * i);
        var x = rCenter + curR * Math.sin(rAngle * i);
        console.log(Math.sin(rAngle * i))
        var s_width = ctx.measureText(rData[i][0]).width;
        if ( x == rCenter) {
            if (y > rCenter ) {
                ctx.fillText(rData[i][0], x - s_width/2, y + fontSize);
            } else {
                ctx.fillText(rData[i][0], x - s_width/2, y - fontSize);
            }
        } else if ( x > rCenter) {
            console.log(rData[i][0]);
            ctx.fillText(rData[i][0], x + s_width*1.5, y);
        } else {
             ctx.fillText(rData[i][0], x - s_width*1.5, y);
        }
    }
    ctx.restore();
    ctx.save(); 
// 繪製等級
    ctx.font = '30px Microsoft Yahei bold';
    ctx.fillStyle = '#d7431f';
    ctx.textBaseline="middle";
    ctx.textAlign="center";
    for (var i = 0; i < 6; i++) {
        var y = rCenter + curR * Math.cos(rAngle * i);
        var x = rCenter + curR * Math.sin(rAngle * i);
        var M_width = ctx.measureText(rData[i][1]).width;
        if ( x == rCenter) {
            if (y > rCenter ) {
                ctx.fillText(rData[i][1], x + M_width/2, y + fontSize);
            } else {
                ctx.fillText(rData[i][1], x + M_width/2, y - fontSize);
            }
        } else if ( x > rCenter) {
            console.log(rData[i][0]);
            ctx.fillText(rData[i][1], x + M_width, y);
        } else {
             ctx.fillText(rData[i][1], x - M_width, y);
        }
    }
    ctx.restore();
    ctx.save();

頁面最終效果:
最終的效果

結尾

好了!以上就是鄙人對於canvas繪畫一點簡單理解與複習了,其中也回顧了一些canvas基本屬性點。後續如何用canvas玩出各種花樣就看各位看官自己了!

小貼士:
在使用ctx.measureText這個方法的時候需要註意一下。這個方法在寬度參考對象也跟當前繪畫環境的font-size有關聯的。

打個比方說,在繪製描述的文本的時候。font-size設置是16px,那麼ctx.measureText('輸出').width 是32。
那麼在繪製能力等級的時候,font-size設置是32,那麼ctx.measureText('輸出').width 就不再是32了而是64或者。

貼士2:
這裡順便幫做設計朋友推廣他的一個微信H5視頻案例,全程水墨畫武俠風,畫工炒雞棒棒。

另外前面loading動畫寶劍出鞘css3部分,利用極少transform3d代碼完成。感興趣的童鞋可以微信掃一掃,看一下運動軌跡就心中估計就能猜出運行的的css3代碼了。

微信體驗地址

原創文章,文筆有限,才疏學淺,文中若有不正之處,再次再次再次歡迎各位啪啪的打臉賜教。(有句話說的好,重要的詞得說三遍。)

我是車大棒!我為我自己……emmmmmmm,今天就不自己帶眼了,為朋友插眼吧!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本章目標 一、瞭解Android菜單類型 二、掌握選項菜單使用 三、掌握上下文菜單使用 四、掌握消息通知Toast組件 五、掌握狀態欄通知Notification組件 一、Android菜單 Android菜單可以分為三種: 1、選項菜單 2、上下文菜單 3、子菜單 選項菜單: 1、Android設 ...
  • 1、安裝 nvm curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | bash 安裝成功預設將會在用戶文件夾中生成一個隱藏的 .nvm 文件 顯示隱藏文件:defaults write com. ...
  • 對於沒參加過互聯網企業招聘,或是沒有參加過大型互聯網企業招聘的人來說,能以這些公司的面試題做為鍛煉,無疑是一種非常好的學習和進步的途徑。下麵是一道騰訊的前端面試題(JS解答),題目本身在現實中意義不大,主要是考察應試者對js及演算法的理解程度,本文給出了三種答案,期待有更大的俠來一試身手,做出更好的解 ...
  • 首先,先介紹一下關於javascript中dataset屬性。。html5中可以使用data-首碼設置我們需要的自定義屬性,來進行一些數據的存放。下麵是元素應用data屬性的一個例子:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ...
  • 1. 實例 2.HTML 代碼 3.CSS 代碼 4. 練習 區別隻是 heigth屬性所設置的高度大小 ...
  • 學過Angular的同學都知道,輸入框通過 實現雙向數據綁定,那麼自定義組件能不能實現雙向數據綁定呢?答案是肯定的。 Angular中,我們常常需要通過方括弧 和圓括弧 實現組件間的交互: 那麼在 和`()`的基礎上,如何實現組件的雙向數據綁定? 例子如下。 子組件: 註意這裡的寫法,這是關鍵所在, ...
  • DOM(文檔對象模型)是針對HTML和XML文檔的一個API,描繪了一個層次化的節點樹,允許開發人員添加、刪除和修改頁面的某一部分。 HTML DOM 樹形結構如下: 1.Node方面 1.1 節點類型 確定節點類型,相容的方法是將nodeType屬性與數字值進行比較,如下所示: if(someNo ...
  • 前言 網站的佈局是一個網站設計的根本,CSS的Grid佈局已經成為了未來網站佈局的基本方式。 今天這篇文章我們通過圖文,一起看看如何自己實現Grid佈局方式。 CSS 第一個Grid佈局 首先我們看看最基本的Grid佈局是什麼樣的,HTML頁面的代碼如下所示。 HTML代碼 然後設置其CSS屬性,這 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...