| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- /**
- * wuxing-sandbox-helpers/render.js
- * 五角星 Canvas 渲染(纯函数,无状态)
- */
- /**
- * 计算五角星顶点坐标
- * 外顶点:5 个,72° 等分,起始角度 -90°(正上方)
- * 内顶点:5 个,偏移 36°,半径 = 外半径 × 0.382
- * @param {number} cx - 中心 x
- * @param {number} cy - 中心 y
- * @param {number} outerR - 外半径
- * @returns {{outer:Array<{x:number,y:number}>,inner:Array<{x:number,y:number}>}}
- */
- export function calcStarVertices(cx, cy, outerR) {
- var innerR = outerR * 0.382
- var outer = []
- var inner = []
- for (var i = 0; i < 5; i++) {
- var angle = -Math.PI / 2 + i * 2 * Math.PI / 5
- outer.push({
- x: cx + outerR * Math.cos(angle),
- y: cy + outerR * Math.sin(angle)
- })
- var innerAngle = angle + Math.PI / 5
- inner.push({
- x: cx + innerR * Math.cos(innerAngle),
- y: cy + innerR * Math.sin(innerAngle)
- })
- }
- return { outer: outer, inner: inner }
- }
- /** 五色配置(与 CSS 一致) */
- var COLORS = [
- { r: 76, g: 175, b: 80 }, // 木·身
- { r: 255, g: 107, b: 53 }, // 火·心
- { r: 141, g: 110, b: 99 }, // 土·智
- { r: 255, g: 215, b: 0 }, // 金·行
- { r: 66, g: 165, b: 245 } // 水·富
- ]
- /**
- * 绘制完整五角星图(含填充、描边、太极)
- * @param {CanvasRenderingContext2D} ctx
- * @param {number} w - canvas 宽度
- * @param {number} h - canvas 高度
- * @param {Array<number>} ts - 5 维归一化值 (0~1),顺序 [body,mind,wisdom,action,wealth]
- * @param {Object} [options]
- * @param {boolean} [options.showTaiji=true] - 是否绘制太极
- */
- export function renderStar(ctx, w, h, ts, options) {
- if (!ctx || !w || !h) return
- var opts = options || {}
- var showTaiji = opts.showTaiji !== false
- // 清空
- ctx.clearRect(0, 0, w, h)
- // 星型几何
- var cx = w / 2
- var cy = h / 2
- var outerR = Math.min(w, h) * 0.42
- var vertices = calcStarVertices(cx, cy, outerR)
- var outer = vertices.outer
- var inner = vertices.inner
- // 绘制每个星臂
- for (var i = 0; i < 5; i++) {
- var outerIdx = (i + 4) % 5
- var iBefore = (i + 3) % 5
- var iAfter = (i + 4) % 5
- var t = ts[i]
- var c = COLORS[i]
- // 星臂 polygon: center, inner[iBefore], outer[outerIdx], inner[iAfter]
- var polygon = [
- { x: cx, y: cy },
- inner[iBefore],
- outer[outerIdx],
- inner[iAfter]
- ]
- // --- 背景填充(低透明度) ---
- ctx.beginPath()
- ctx.moveTo(polygon[0].x, polygon[0].y)
- for (var p = 1; p < polygon.length; p++) {
- ctx.lineTo(polygon[p].x, polygon[p].y)
- }
- ctx.closePath()
- ctx.fillStyle = 'rgba(' + c.r + ', ' + c.g + ', ' + c.b + ', 0.08)'
- ctx.fill()
- // --- 渐变填充(t 控制色彩饱和度,覆盖整个星臂) ---
- if (t > 0.01) {
- ctx.save()
- ctx.beginPath()
- ctx.moveTo(polygon[0].x, polygon[0].y)
- for (var fp = 1; fp < polygon.length; fp++) {
- ctx.lineTo(polygon[fp].x, polygon[fp].y)
- }
- ctx.closePath()
- ctx.clip()
- var grad = ctx.createRadialGradient(cx, cy, 0, cx, cy, Math.max(outerR, 1))
- grad.addColorStop(0, 'rgba(' + c.r + ', ' + c.g + ', ' + c.b + ', ' + (0.85 * t) + ')')
- grad.addColorStop(0.5, 'rgba(' + c.r + ', ' + c.g + ', ' + c.b + ', ' + (0.35 * t) + ')')
- grad.addColorStop(1, 'rgba(' + c.r + ', ' + c.g + ', ' + c.b + ', ' + (0.02 * t) + ')')
- ctx.fillStyle = grad
- ctx.fillRect(0, 0, w, h)
- ctx.restore()
- }
- // --- 星臂描边 ---
- ctx.beginPath()
- ctx.moveTo(polygon[0].x, polygon[0].y)
- for (var sp = 1; sp < polygon.length; sp++) {
- ctx.lineTo(polygon[sp].x, polygon[sp].y)
- }
- ctx.closePath()
- ctx.strokeStyle = 'rgba(' + c.r + ', ' + c.g + ', ' + c.b + ', 0.4)'
- ctx.lineWidth = 1.5
- ctx.stroke()
- }
- // --- 五角星外轮廓(10 个顶点依次连接) ---
- ctx.beginPath()
- ctx.moveTo(outer[0].x, outer[0].y)
- ctx.lineTo(inner[0].x, inner[0].y)
- ctx.lineTo(outer[1].x, outer[1].y)
- ctx.lineTo(inner[1].x, inner[1].y)
- ctx.lineTo(outer[2].x, outer[2].y)
- ctx.lineTo(inner[2].x, inner[2].y)
- ctx.lineTo(outer[3].x, outer[3].y)
- ctx.lineTo(inner[3].x, inner[3].y)
- ctx.lineTo(outer[4].x, outer[4].y)
- ctx.lineTo(inner[4].x, inner[4].y)
- ctx.closePath()
- ctx.strokeStyle = 'rgba(143, 197, 232, 0.50)'
- ctx.lineWidth = 2
- ctx.stroke()
- // --- 内部五边形(5 个内顶点) ---
- ctx.beginPath()
- ctx.moveTo(inner[0].x, inner[0].y)
- ctx.lineTo(inner[1].x, inner[1].y)
- ctx.lineTo(inner[2].x, inner[2].y)
- ctx.lineTo(inner[3].x, inner[3].y)
- ctx.lineTo(inner[4].x, inner[4].y)
- ctx.closePath()
- ctx.strokeStyle = 'rgba(143, 197, 232, 0.35)'
- ctx.lineWidth = 1.5
- ctx.stroke()
- // --- 中心太极阴阳鱼(在 Canvas 最上层绘制) ---
- if (showTaiji) {
- var taijiR = outerR * 0.22
- drawTaiji(ctx, cx, cy, taijiR)
- }
- }
- /**
- * 绘制太极阴阳鱼
- * @param {CanvasRenderingContext2D} ctx
- * @param {number} cx - 中心 x
- * @param {number} cy - 中心 y
- * @param {number} r - 半径
- */
- export function drawTaiji(ctx, cx, cy, r) {
- var yangColor = 'rgba(91, 155, 213, 0.35)'
- var yinColor = 'rgba(26, 42, 74, 0.75)'
- var strokeColor = 'rgba(143, 197, 232, 0.2)'
- var yangEyeColor = 'rgba(26, 42, 74, 0.85)'
- var yinEyeColor = 'rgba(143, 197, 232, 0.75)'
- ctx.save()
- // 1. 外圆背景及边框
- ctx.beginPath()
- ctx.arc(cx, cy, r, 0, Math.PI * 2)
- ctx.closePath()
- ctx.fillStyle = yangColor
- ctx.fill()
- ctx.strokeStyle = strokeColor
- ctx.lineWidth = 1
- ctx.stroke()
- // 2. 左半边:阴(深色)
- ctx.beginPath()
- ctx.moveTo(cx, cy)
- ctx.arc(cx, cy, r, Math.PI / 2, -Math.PI / 2)
- ctx.closePath()
- ctx.fillStyle = yinColor
- ctx.fill()
- // 3. 上半小圆左半弧:阳色
- ctx.beginPath()
- ctx.moveTo(cx, cy - r)
- ctx.arc(cx, cy - r / 2, r / 2, Math.PI / 2, -Math.PI / 2)
- ctx.closePath()
- ctx.fillStyle = yangColor
- ctx.fill()
- // 4. 下半小圆右半弧:阴色
- ctx.beginPath()
- ctx.moveTo(cx, cy + r)
- ctx.arc(cx, cy + r / 2, r / 2, -Math.PI / 2, Math.PI / 2)
- ctx.closePath()
- ctx.fillStyle = yinColor
- ctx.fill()
- // 5. 阳眼(深色点,上方)
- ctx.beginPath()
- ctx.arc(cx, cy - r / 2, r * 0.12, 0, Math.PI * 2)
- ctx.closePath()
- ctx.fillStyle = yangEyeColor
- ctx.fill()
- // 6. 阴眼(浅色点,下方)
- ctx.beginPath()
- ctx.arc(cx, cy + r / 2, r * 0.12, 0, Math.PI * 2)
- ctx.closePath()
- ctx.fillStyle = yinEyeColor
- ctx.fill()
- ctx.restore()
- // 7. 文字标签
- ctx.save()
- var fontSize = Math.round(r * 0.3)
- ctx.font = 'bold ' + fontSize + 'px sans-serif'
- ctx.textAlign = 'center'
- ctx.textBaseline = 'middle'
- ctx.fillStyle = 'rgba(143, 197, 232, 0.6)'
- ctx.fillText('家', cx - r * 0.3, cy + r * 0.15)
- ctx.fillStyle = 'rgba(91, 155, 213, 0.8)'
- ctx.fillText('个', cx + r * 0.3, cy - r * 0.15)
- ctx.restore()
- }
|