背景:
一日晚上下班的我靜靜的靠在角落上聽着歌,這時"滴!滴!"手機上傳來一陣qq消息。原來我人在問王者榮耀的雷達圖在頁面上如何作出來的,有人回答用canvas繪畫。那麼問題來了,已經很久沒有使用canvas繪畫了東西。
SO,就想本身畫一個canvas雷達圖,順便從新回顧一下canvas的知識點。git
聊天記錄當中的雷達圖不是特別清楚,因此我這邊截圖了本身的一個戰績雷達圖。
是否是有被個人戰績嚇到了,害不懼怕!
好了扯遠了,讓咱們回到正題上來。
經過截圖上面的雷達圖基本主體是一個正六邊形,每一個頂點則配有相應的文字說明。
而後就是中間紅色區域部分則由對角線上的點,連成一圈填充構成。所以這裏咱們稱它爲數據填充區
因此這個雷達圖咱們分爲三步來完成。
①正六邊形
②數據填充區
③繪製文本github
在繪畫這個正六邊形的時候,先讓咱們對於這個正六邊形進行簡單的數學分析。
這裏先用畫板畫一個正六變形,而後進行切割並切角。
canvas
是吧,借用之前高中仍是初中的數學,正六邊形的內角和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
軸和別人不同,爲毛一會加一會減。函數
因此當思考各座標點參數的規律的時候,讓先回顧之前的函數角度圖表
字體
看完這個函數參照圖以後,讓我再次修改一下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°))
設計
這個時候再看組座標數據點,是否是感受有點意思!3d
那麼這個時候咱們即可以經過一個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); }
前面既然,將正六邊形的座標點經過一個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();
啦啦啦!!!一個正六邊形就這麼的畫出來。
備註:這裏rAngle這裏是很靈活的,若是說畫18正邊形,就除以18,而後for循環18次就ok了.
哈哈!!感受發現了新大陸了!繪製正多邊形的貌似能夠按照這個規律來!!
既然前面有一個數組存儲各個座標點,因此讓每一個對角線對角點直線想連就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();
關於數據填充區,也就是雷達圖當中,不規則的紅色半透明的六邊形。其實就是就能夠看作中心點,到各個邊角點之間線段爲一區間這。以後就是將這個區間分紅若干份,你佔這個這個區間多少份,滿份就是邊角點,零份就是原點。
觀察前面的雷達圖當中,B等級大概佔據某個等級的50%左右。而B前面還有等級A、S。
因此當S等級時候,能夠看做區間 / 1。
B等級看做區間 / 2, 那麼A就是 區間 / 1.5.
以此類推就能夠得出剩下 C 就是區間 / 2.五、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();
當咱們迴歸到前面的截圖發現,須要單獨把數據填充區域的的各個點位置給增強,並把邊角用更深的線條的描繪出來。
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();
王者榮耀雷達文本是須要繪製兩點,
①用黑色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();
瀏覽器最終顯示的視覺效果:
是否是以爲很驚喜,這裏輸出
、經濟
位置勉強還行,可是剩下的文字位置就誤差了許多了。因此在繪製文字的時候,還得針對文字的座標位置進行相應的調整。
既然直接調用座標的位置會出問題,那麼讓根據上文中的圖片文字的規則簡單分析。
①若是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這個方法,獲取當前你繪製文本的寬度。
既然前面已經介紹了描述的繪畫方法,那麼依葫蘆畫瓢。讓咱們一併開始繪製能力級別的文本。
// 繪製文本 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或者。
原創文章,文筆有限,才疏學淺,文中如有不正之處,再次再次再次歡迎各位啪啪的打臉賜教。(有句話說的好,重要的詞得說三遍。)
PS:對於裏面完整blogDemo代碼感興趣的,能夠從本人github上閱覽。
源碼demo傳送門地址
我是車大棒!我爲我本身帶眼了!