這篇文章將為大家詳細(xì)講解有關(guān)Canvas如何實(shí)現(xiàn)一個(gè)六邊形能力圖,小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。
成都創(chuàng)新互聯(lián)公司堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:成都網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿(mǎn)足客戶(hù)于互聯(lián)網(wǎng)時(shí)代的南川網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
一、前言
六邊形能力圖如下,由 6 個(gè) 六邊形組成,每一個(gè)頂點(diǎn)代表其在某一方面的能力。這篇文章我們就來(lái)看看如何基于 canvas 去繪制這么一個(gè)六邊形能力圖。當(dāng)然,你也可以基于其他開(kāi)源的 js 方案來(lái)實(shí)現(xiàn),如 EChars.js 等。
二、六邊形繪制基礎(chǔ)
六邊形能力圖有 6 個(gè) 六邊形組成,那我們只要繪制出一個(gè),另外 5 個(gè)則依次減小六邊形的邊長(zhǎng)即可。那我們首先來(lái)分析一下,如何繪制出一個(gè)六邊形。
如上圖,繪制一個(gè)六邊形有以下幾個(gè)關(guān)鍵點(diǎn):
1.紫色矩形區(qū)域我們可以看成是 canvas 的畫(huà)布。其大小可以認(rèn)為是 (width,height)。center(centerX,centerY) 是其中心點(diǎn),即 (width / 2, height / 2)。
2.繪制六邊形的關(guān)鍵是計(jì)算出它的 6 個(gè)頂點(diǎn)的坐標(biāo)。而如上圖所示,這里面最關(guān)鍵的又是計(jì)算出六邊形所在矩形區(qū)域的左上角坐標(biāo)(left,top)。對(duì)照上圖,(left,top) 的計(jì)算公式如下。
要計(jì)算出 (left,top) 需要先計(jì)算出 x,y 。而 x,y 的值與六邊形的邊長(zhǎng)有關(guān)。
3.如上的 x,y 的計(jì)算公式為
4.因此,X1(x1,y1),X2(x2,y2),X3(x3,y3),X4(x4,y4),X5(x5,y5),X6(x6,y6) 的坐標(biāo)計(jì)算為
因此,得到繪制六邊形的代碼為:
function computeHexagonPoints(width, height, edge) { let centerX = width / 2; let centerY = height / 2; let x = edge * Math.sqrt(3) / 2; let left = centerX - x; let x1,x2,x3,x4,x5,x6; let y1,y2,y3,y4,y5,y6; x5 = x6 = left; x2 = x3 = left + x * 2; x1 = x4 = left + x; let y = edge / 2; let top = centerY - 2 * y; y1 = top; y2 = y6 = top + y; y3 = y5 = top + 3 * y; y4 = top + 4 * y; let points = new Array(); points[0] = [x1, y1]; points[1] = [x2, y2]; points[2] = [x3, y3]; points[3] = [x4, y4]; points[4] = [x5, y5]; points[5] = [x6, y6]; return points; }
三、繪制六維能力圖
3.1 繪制 6 個(gè)六邊形
基于 canvas 繪制,首先就是需要獲取 context。
_context = canvas.getContext('2d');
而繪制的話(huà),已經(jīng)知道 6 個(gè)頂點(diǎn)了,那只需要將這 6 個(gè)點(diǎn)用連線(xiàn)的方式連接起來(lái)就可以了。主要用到 moveTo(x,y) 和 lineTo(x,y) 兩個(gè)方法。這里總共需要繪制 6 個(gè)六邊形,那只要按等比例減小 edge 的值就可以了。因此繪制六邊形能力圖的主要代碼如下。
function drawHexagonInner(edge) { _context.strokeStyle = _color; for (var i = 0; i < 6; i++) { _allPoints[i] = computeHexagonPoints(_width, _height, edge - i * edge / 5); _context.beginPath(); _context.moveTo(_allPoints[i][5][0],_allPoints[i][5][1]); for (var j = 0; j < 6; j++) { _context.lineTo(_allPoints[i][j][0],_allPoints[i][j][1]); } _context.closePath(); _context.stroke(); } }
代碼中還有 3 個(gè)相關(guān)的 API。beginPath() 和 closePath() 主要就是繪制得到一個(gè)封閉的路徑。stroke() 主要是得到一個(gè)鏤空的形狀。當(dāng)然,相應(yīng)的就有 fill() 得到填充的形狀。
3.2 繪制 3 條直線(xiàn)
繪制那 3 條直線(xiàn)也是比較簡(jiǎn)單的,只要將 X1和X4 連接,將X2 和 X5 相連,將 X3 和 X6 相連。代碼如下:
function drawLines() { _context.beginPath(); _context.strokeStyle = _color; for (let i = 0; i < 3; i++) { _context.moveTo(_allPoints[0][i][0],_allPoints[0][i][1]); //1-4 _context.lineTo(_allPoints[0][i+3][0],_allPoints[0][i+3][1]); //1-4 _context.stroke(); } _context.closePath(); }
3.3 繪制覆蓋圖
6 個(gè)頂點(diǎn)代表了六種能力,比如這里的各科成績(jī),把六種能力封閉成一個(gè)閉合路徑并填充則稱(chēng)為覆蓋圖。要繪制出覆蓋圖,這里需要計(jì)算出六個(gè)頂點(diǎn)。6 個(gè)頂點(diǎn)可以通過(guò)最外圍的六邊形的 6 個(gè)頂點(diǎn)和中心點(diǎn)來(lái)計(jì)算。簡(jiǎn)單來(lái)說(shuō)就是通過(guò)能力得分,在頂點(diǎn)到中心距離的占比來(lái)計(jì)算。計(jì)算公式如下。
代碼如下
/** * 畫(huà)覆蓋物 */ function drawCover() { let tmpCoverPoints = _allPoints[0]; _coverPoints = []; console.log("coverPoints ",tmpCoverPoints) let centerX = _width / 2; let centerY = _height / 2; for (let i = 0; i < tmpCoverPoints.length; i++) { _coverPoints.push([ centerX + (tmpCoverPoints[i][0] - centerX) * (_data[i].score / 100.0), centerX + (tmpCoverPoints[i][1] - centerY) * (_data[i].score / 100.0) ]); } console.log("newCoverPoints ",_coverPoints) _context.beginPath(); _context.fillStyle = 'rgba(90,200,250,0.4)'; _context.moveTo(_coverPoints[5][0],_coverPoints[5][1]); //5 for (var j = 0; j < 6; j++) { _context.lineTo(_coverPoints[j][0],_coverPoints[j][1]); } _context.stroke(); _context.closePath(); _context.fill(); }
/** * 描點(diǎn) * @param pointRadius */ function drawPoints(pointRadius) { _context.fillStyle = _color; for (let i = 0; i < _coverPoints.length; i++) { _context.beginPath(); _context.arc(_coverPoints[i][0],_coverPoints[i][1],pointRadius,0,Math.PI*2); _context.closePath(); _context.fill(); } }
3.4 最后來(lái)繪制文本
繪制文本也是用的最外圍的 6 個(gè)頂點(diǎn)的坐標(biāo)。而用的 API 是 fillText(text,x,y),其中 x,y 代碼文字繪制起點(diǎn),但注意,不是文字所在矩形框的左上角,應(yīng)該在左下角的大概位置。準(zhǔn)確來(lái)說(shuō)是文字的基線(xiàn)位置,這個(gè)在其他的GUI系統(tǒng)中也是一樣,當(dāng)然這里不追求那么細(xì)節(jié)了,就認(rèn)為是左下角位置吧。
因此,對(duì)于不同側(cè)的文字,其起點(diǎn)坐標(biāo)也是不一樣。如左側(cè)的文字至少應(yīng)該是左側(cè)的頂點(diǎn) x 減去文字的寬度。再比如,上下兩側(cè)的文字與頂點(diǎn)中相對(duì)居中對(duì)齊的,因此計(jì)算方法是 x 減去文字寬度的一半。代碼的實(shí)現(xiàn)分為了上下左右來(lái)進(jìn)行不同的繪制。
代碼如下,看著有點(diǎn)長(zhǎng),但其實(shí)是很簡(jiǎn)單的。
/** * 繪制上側(cè)的文字 * @param text * @param pos */ function drawUpText(item, pos) { let nameMeasure = _context.measureText(item.name); let scoreMeasure = _context.measureText(item.score); _context.fillStyle = '#8E8E8E'; _context.fillText(item.name, pos[0] - nameMeasure.width / 2,pos[1] - 26); _context.fillStyle = '#212121'; _context.fillText(item.score, pos[0] - scoreMeasure.width / 2,pos[1] - 10); }
/** * 繪制下側(cè)的文字 * @param text * @param pos */ function drawDownText(item, pos) { let nameMeasure = _context.measureText(item.name); let scoreMeasure = _context.measureText(item.score); _context.fillStyle = '#8E8E8E'; _context.fillText(item.name, pos[0] - nameMeasure.width / 2,pos[1] + 16); _context.fillStyle = '#212121'; _context.fillText(item.score, pos[0] - scoreMeasure.width / 2,pos[1] + 32); }
/** * 繪制左側(cè)的文字 * @param text * @param pos */ function drawLeftText(item, pos) { let nameMeasure = _context.measureText(item.name); let scoreMeasure = _context.measureText(item.score); _context.fillStyle = '#8E8E8E'; _context.fillText(item.name, pos[0] - nameMeasure.width - 10,pos[1]); _context.fillStyle = '#212121'; _context.fillText(item.score, pos[0] - 10 - (nameMeasure.width + scoreMeasure.width) / 2,pos[1] + 16); }
/** * 繪制右側(cè)的文字 * @param text * @param pos */ function drawRightText(item, pos) { let nameMeasure = _context.measureText(item.name); let scoreMeasure = _context.measureText(item.score); _context.fillStyle = '#8E8E8E'; _context.fillText(item.name, pos[0] - nameMeasure.width + 26,pos[1]); _context.fillStyle = '#212121'; _context.fillText(item.score, pos[0] + 26 - (nameMeasure.width + scoreMeasure.width) / 2,pos[1] + 16); }
/** * 繪制所有文本 */ function drawText() { _context.fillStyle = '#8E8E8E'; _context.strokeStyle = _color; let textPos = _allPoints[0]; for (let i = 0; i < textPos.length; i++) { let item = _data[i]; let pos = textPos[i]; if(i == 0) { drawUpText(item, pos); } else if(i == 1 || i == 2) { drawRightText(item, pos); } else if(i == 3) { drawDownText(item, pos); } else if(i == 4 || i == 5) { drawLeftText(item, pos); } } }
關(guān)于“Canvas如何實(shí)現(xiàn)一個(gè)六邊形能力圖”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。