六邊形能力圖以下,由 6 個 六邊形組成,每個頂點表明其在某一方面的能力。這篇文章咱們就來看看如何基於 canvas 去繪製這麼一個六邊形能力圖。固然,你也能夠基於其餘開源的 js 方案來實現,如 EChars.js 等。canvas
六邊形能力圖有 6 個 六邊形組成,那咱們只要繪製出一個,另外 5 個則依次減少六邊形的邊長便可。那咱們首先來分析一下,如何繪製出一個六邊形。bash
如上圖,繪製一個六邊形有如下幾個關鍵點:ui
1.紫色矩形區域咱們能夠當作是 canvas 的畫布。其大小能夠認爲是 (width,height)。center(centerX,centerY) 是其中心點,即 (width / 2, height / 2)。spa
2.繪製六邊形的關鍵是計算出它的 6 個頂點的座標。而如上圖所示,這裏面最關鍵的又是計算出六邊形所在矩形區域的左上角座標(left,top)。對照上圖,(left,top) 的計算公式以下。code
要計算出 (left,top) 須要先計算出 x,y 。而 x,y 的值與六邊形的邊長有關。cdn
3.如上的 x,y 的計算公式爲blog
4.所以,X1(x1,y1),X2(x2,y2),X3(x3,y3),X4(x4,y4),X5(x5,y5),X6(x6,y6) 的座標計算爲圖片
所以,獲得繪製六邊形的代碼爲:get
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;
}
複製代碼
基於 canvas 繪製,首先就是須要獲取 context。string
_context = canvas.getContext('2d');
複製代碼
而繪製的話,已經知道 6 個頂點了,那隻須要將這 6 個點用連線的方式鏈接起來就能夠了。主要用到 moveTo(x,y) 和 lineTo(x,y) 兩個方法。這裏總共須要繪製 6 個六邊形,那隻要按等比例減少 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 個相關的 API。beginPath() 和 closePath() 主要就是繪製獲得一個封閉的路徑。stroke() 主要是獲得一個鏤空的形狀。固然,相應的就有 fill() 獲得填充的形狀。
繪製那 3 條直線也是比較簡單的,只要將 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();
}
複製代碼
6 個頂點表明了六種能力,好比這裏的各科成績,把六種能力封閉成一個閉合路徑並填充則稱爲覆蓋圖。要繪製出覆蓋圖,這裏須要計算出六個頂點。6 個頂點能夠經過最外圍的六邊形的 6 個頂點和中心點來計算。簡單來講就是經過能力得分,在頂點到中心距離的佔比來計算。計算公式以下。
代碼以下
/**
* 畫覆蓋物
*/
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();
}
複製代碼
/**
* 描點
* @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();
}
}
複製代碼
繪製文本也是用的最外圍的 6 個頂點的座標。而用的 API 是 fillText(text,x,y),其中 x,y 代碼文字繪製起點,但注意,不是文字所在矩形框的左上角,應該在左下角的大概位置。準確來講是文字的基線位置,這個在其餘的GUI系統中也是同樣,固然這裏不追求那麼細節了,就認爲是左下角位置吧。
所以,對於不一樣側的文字,其起點座標也是不同。如左側的文字至少應該是左側的頂點 x 減去文字的寬度。再好比,上下兩側的文字與頂點中相對居中對齊的,所以計算方法是 x 減去文字寬度的一半。代碼的實現分爲了上下左右來進行不一樣的繪製。
代碼以下,看着有點長,但實際上是很簡單的。
/**
* 繪製上側的文字
* @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);
}
複製代碼
/**
* 繪製下側的文字
* @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);
}
複製代碼
/**
* 繪製左側的文字
* @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);
}
複製代碼
/**
* 繪製右側的文字
* @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);
}
}
}
複製代碼
文章主要是基於 canvas 自定義一個六邊形能力圖,而這個能力的圖的關鍵部分是對於六邊形的繪製,而六邊形的繪製又在於計算出 6 個頂點。有了 6 個頂點,再繪製其餘的邊,文本,覆蓋區域等都基於這個 6 個頂點進行相應的繪製便可。
最後,感謝你能讀到此文章。若是個人分享對你有所幫助,還請幫忙點個贊,給個鼓勵吧,謝謝。