微信小程式canvas繪製雷達圖
阿新 • • 發佈:2018-12-11
效果圖展示:
程式碼實現(具體實現):
const DEFAULT_COLOR = '#FC9A00'; class Radar { constructor(options) { const { canvasId, width, height, categories, data, initrad = Math.PI, colors = [] } = options; this.colors = colors; this.categories = categories; // 直徑 const diameter = Math.min(width, height); this.ctx = wx.createCanvasContext(canvasId); // 圓心 this.center = { x: width / 2, y: height / 2 }; // 半徑 this.radius = diameter / 2; if (categories.length) { // 減去文字的高度 先定為20 this.radius -= 30; } // 幾邊形 this.step = data.length; const maxData = Math.max(...data); if (maxData == 0) { this.rateArray = data; } else { this.rateArray = data.map(d => d / maxData); } // this.rateArray = data; // 值 // 開始的角度 this.initrad = initrad; // 清空上次畫的內容 this.ctx.draw(); this.drawBackground(); // 背景 this.drawRate(); // 畫比例 this.drawBackLine(); // 畫白色的線 this.drawRateLine(); // 畫中間的線 this.drawCenter(); // 畫圓心 this.drawRatePoint(); // 畫點 this.drawText(); // 畫文字 } // 畫背景 drawBackground() { // 測試畫背景線 const rad = 2 * Math.PI / this.step; let initrad = this.initrad; // 畫蜘蛛網 var isBlue = false; for (var s=8; s>0; s--) { this.ctx.beginPath(); this.ctx.setLineWidth(1); this.ctx.setStrokeStyle('#f8f8f8'); let lineWidth = this.radius / 6; for (var i=0;i<this.step;i++) { initrad += rad; this.ctx.setLineWidth(lineWidth); const x = this.center.x + Math.sin(initrad) * (this.radius - lineWidth / 2)*(s/8); const y = this.center.y + Math.cos(initrad) * (this.radius - lineWidth / 2)*(s/8); this.ctx.lineTo(x, y); } if (s>6) { isBlue = !isBlue; } this.ctx.closePath(); if (isBlue) { this.ctx.setLineJoin('round'); this.ctx.stroke(); } } // 畫內圈線 isBlue = false; for (s=8; s>0; s--) { this.ctx.beginPath(); this.ctx.setLineWidth(0.5); this.ctx.setStrokeStyle('#eeeeee'); for (i=0;i<this.step;i++) { initrad += rad; const x = this.center.x + Math.sin(initrad) * this.radius*(s/8) * 0.66; const y = this.center.y + Math.cos(initrad) * this.radius*(s/8) * 0.66; this.ctx.lineTo(x, y); } if (s>6) { isBlue = !isBlue; } this.ctx.closePath(); if (isBlue) { this.ctx.setLineJoin('round'); this.ctx.stroke(); } } this.ctx.draw(true); } // 畫比例 drawRate() { const rad = 2 * Math.PI / this.step; let initrad = this.initrad; const initPoint = { x: this.center.x + Math.sin(initrad) * this.radius * this.rateArray[0], y: this.center.y + Math.cos(initrad) * this.radius * this.rateArray[0] }; this.ctx.moveTo(initPoint.x, initPoint.y); this.ctx.beginPath(); this.ctx.setStrokeStyle('#feeeb8'); this.ctx.setLineWidth(0.5); let isShowIf = false; let arr = []; // 點數 let arr1 = []; let arr2 = []; for (let i=0;i<this.rateArray.length;i++) { if (this.rateArray[i]) { arr.push(1); } let index = i; let index_ = i + 1; let index2 = i + 2; let index4 = i + 4; let index5 = i + 5; if (index2 > (this.step-1)) index2 = index2 - this.step; if (index4 > (this.step-1)) index4 = index4 - this.step; if (index5 > (this.step-1)) index5 = index5 - this.step; if (this.rateArray[index] && this.rateArray[index_]) { arr1.push(1); } if (this.rateArray[index] && this.rateArray[index_] && this.rateArray[index2]) { arr2.push(1); } if (this.rateArray[index] && this.rateArray[index4] && this.rateArray[index5]) { arr2.push(1); } if (this.rateArray[index] && this.rateArray[index4]) { arr1.push(1); } if (this.rateArray[index] && this.rateArray[index5]) { arr1.push(1); } } if (arr.length == 2 && arr1.length) isShowIf = true; if (arr.length == 2 && !arr1.length) isShowIf = false; if (arr.length == 3 && arr2.length) isShowIf = true; if (arr.length == 3 && !arr2.length) isShowIf = false; if (arr.length == 4) isShowIf = false; for (let i = 0; i < this.step; i++) { const x = this.center.x + Math.sin(initrad) * this.radius * this.rateArray[i]; const y = this.center.y + Math.cos(initrad) * this.radius * this.rateArray[i]; if (isShowIf) { this.ctx.lineTo(x, y); this.ctx.stroke(); } else { if (!(x === this.center.x && y === this.center.y)) { // 不連線圓心 this.ctx.lineTo(x, y); this.ctx.stroke(); } } initrad += rad; var grd = this.ctx.createCircularGradient(this.center.x, this.center.y, this.radius); grd.addColorStop(0, '#feeeb8'); grd.addColorStop(1, '#feeeb8'); } this.ctx.lineTo(initPoint.x, initPoint.y); this.ctx.setFillStyle(grd); this.ctx.fill(); this.ctx.stroke(); this.ctx.closePath(); this.ctx.draw(true); } // 畫中間的線 drawRateLine() { const rad = 2 * Math.PI / this.step; let initrad = this.initrad; const initPoint = { x: this.center.x + Math.sin(initrad) * this.radius * this.rateArray[0], y: this.center.y + Math.cos(initrad) * this.radius * this.rateArray[0] }; this.ctx.setStrokeStyle('#F2E9B7'); this.ctx.setLineWidth(0.5); this.ctx.moveTo(this.center.x, this.center.y); this.ctx.lineTo(initPoint.x, initPoint.y); this.ctx.stroke(); for (let i = 0; i < this.step; i++) { const x = this.center.x + Math.sin(initrad) * this.radius * this.rateArray[i]; const y = this.center.y + Math.cos(initrad) * this.radius * this.rateArray[i]; this.ctx.moveTo(this.center.x, this.center.y); this.ctx.lineTo(x, y); this.ctx.stroke(); initrad += rad; } this.ctx.draw(true); } // 畫點 drawRatePoint() { const rad = 2 * Math.PI / this.step; let initrad = this.initrad; const initPoint = { x: this.center.x + Math.sin(initrad) * this.radius * this.rateArray[0], y: this.center.y + Math.cos(initrad) * this.radius * this.rateArray[0] }; this.ctx.beginPath(); this.ctx.arc(initPoint.x, initPoint.y, 2, 0, 2 * Math.PI); this.ctx.setFillStyle('#ffffff'); this.ctx.fill(); this.ctx.closePath(); let color = this.colors[0] || DEFAULT_COLOR; this.ctx.beginPath(); this.ctx.arc(initPoint.x, initPoint.y, 1, 0, 2 * Math.PI); this.ctx.setFillStyle(color); this.ctx.fill(); this.ctx.closePath(); for (let i = 0; i < this.step; i++) { const x = this.center.x + Math.sin(initrad) * this.radius * this.rateArray[i]; const y = this.center.y + Math.cos(initrad) * this.radius * this.rateArray[i]; let color = this.colors[i] || DEFAULT_COLOR; this.ctx.beginPath(); this.ctx.arc(x, y, 2, 0, 2 * Math.PI); this.ctx.setFillStyle('#ffffff'); this.ctx.fill(); this.ctx.closePath(); this.ctx.beginPath(); this.ctx.arc(x, y, 2, 0, 2 * Math.PI); this.ctx.setFillStyle(color); this.ctx.fill(); this.ctx.closePath(); initrad += rad; } this.ctx.draw(true); } // 畫白色的線 drawBackLine() { const rad = 2 * Math.PI / this.step; let initrad = this.initrad; this.ctx.setStrokeStyle('#eeeeee'); this.ctx.setLineWidth(0.5); for (let i = 0; i < this.step; i++) { this.ctx.moveTo(this.center.x, this.center.y); const x = this.center.x + Math.sin(initrad) * this.radius; const y = this.center.y + Math.cos(initrad) * this.radius; this.ctx.lineTo(x, y); this.ctx.stroke(); initrad += rad; } this.ctx.draw(true); } // 畫圓心 drawCenter() { this.ctx.beginPath(); this.ctx.arc(this.center.x, this.center.y, 1, 0, 2 * Math.PI); // this.ctx.setFillStyle('rgb(205, 241, 250)'); this.ctx.setFillStyle('#cccccc'); this.ctx.fill(); this.ctx.draw(true); this.ctx.closePath(); } // 繪製文字 drawText() { const rad = 2 * Math.PI / this.step; let initrad = this.initrad; this.ctx.setFontSize(13); this.ctx.setFillStyle('#333333'); this.ctx.setTextBaseline('middle'); this.ctx.setTextAlign('center'); for (let i = 0; i < this.step; i++) { const x = this.center.x + Math.sin(initrad) * this.radius; const y = this.center.y + Math.cos(initrad) * this.radius; const text = this.categories[i]; // 偏移值 let incX = 0, incY = 0; let offsetX = x - this.center.x; let offsetY = y - this.center.y; // 水平方向上左右兩邊 if (offsetX > 40) { incX = 40; } else if (offsetX < -40) { incX = -40; } // 垂直方向上距離雷達圖 if (offsetY > 40) { incY = 5; } else if (offsetY < -40) { incY = -5; } // 上下兩個相鄰元素的間距 if (this.step == 6) { if (i === 4) incX = -40; if (i === 3) incX = 40; if (i === 1) incX = 40; if (i === 0) incX = -40; } let color = this.colors[i] || '#333333'; // 當文字長度大於4時,分成兩列 if (text.length > 4) { let firstLine = text.slice(0, 4); let secondLine = text.slice(4); if (text.slice(0, 5) == 'STEAM') { firstLine = text.slice(0, 7); secondLine = text.slice(7); } if (incY < 0) incY = -20; if (incY > 0) incY = -5; if (incY == 0) incY = -10; this.ctx.setFillStyle(color); this.ctx.setFontSize(18); this.ctx.fillText(secondLine, x + incX, y + incY); this.ctx.setFontSize(12); this.ctx.setFillStyle('#666'); this.ctx.fillText(firstLine, x + incX, y + incY + 18); this.ctx.setTextAlign('center'); } else { this.ctx.fillText(text, x + incX, y + incY); } initrad += rad; } this.ctx.draw(true); } } module.exports = Radar;
呼叫:
// 畫雷達圖 drawRadar(titleArr, countArr) { let initrad = undefined; if (countArr.length == 4) { initrad = Math.PI * 2; } else if (countArr.length == 5) { initrad = -Math.PI * 7 / 36; } else { initrad = Math.PI * 11 / 6; } const colors = [ '#5bc5be', '#fcb046', '#f26747', '#f5457e', '#786eff', '#03c3fb' ]; new Radar({ canvasId: 'radarCanvas', colors, width: 260, height: 180, categories: titleArr.reverse(), // ['故事回本', '英語教育', ...],分類陣列 data: countArr.reverse(), // [3, 5, ...], 分類對應的值 initrad: initrad, }); },