(ctx, w, h)
| 18 | } |
| 19 | |
| 20 | function paint(ctx, w, h) { |
| 21 | // Cream paper background + vignette. |
| 22 | ctx.fillStyle = theme.widget_base; |
| 23 | ctx.fillRect(0, 0, w, h); |
| 24 | |
| 25 | if (datasets.length === 0) return; |
| 26 | |
| 27 | // Header. |
| 28 | ctx.fillStyle = theme.widget_text; |
| 29 | ctx.font = "bold 11px sans-serif"; |
| 30 | ctx.textAlign = "start"; |
| 31 | ctx.fillText("LED MATRIX", 14, 18); |
| 32 | ctx.fillStyle = theme.placeholder_text; |
| 33 | ctx.font = "10px sans-serif"; |
| 34 | ctx.textAlign = "end"; |
| 35 | ctx.fillText(LEDS_PER_ROW + " LEDs / row", w - 14, 18); |
| 36 | ctx.fillStyle = theme.widget_border; |
| 37 | ctx.fillRect(14, 22, w - 28, 1); |
| 38 | |
| 39 | // Card body. |
| 40 | const padX = 14; |
| 41 | const padTop = 30; |
| 42 | const padBot = 14; |
| 43 | const cardX = padX; |
| 44 | const cardY = padTop; |
| 45 | const cardW = w - padX * 2; |
| 46 | const cardH = h - padTop - padBot; |
| 47 | |
| 48 | ctx.fillStyle = theme.widget_border; |
| 49 | ctx.fillRect(cardX + 1, cardY + 2, cardW, cardH); |
| 50 | ctx.fillStyle = theme.alternate_base; |
| 51 | ctx.fillRect(cardX, cardY, cardW, cardH); |
| 52 | ctx.strokeStyle = theme.widget_border; |
| 53 | ctx.lineWidth = 1; |
| 54 | ctx.strokeRect(cardX + 0.5, cardY + 0.5, cardW - 1, cardH - 1); |
| 55 | |
| 56 | // Per-row layout. |
| 57 | const labelW = 78; |
| 58 | const valueW = 60; |
| 59 | const innerPad = 12; |
| 60 | const rowH = cardH / datasets.length; |
| 61 | const ledH = Math.max(8, Math.min(rowH * 0.55, 22)); |
| 62 | const ledArea = cardW - innerPad * 2 - labelW - valueW - 16; |
| 63 | const ledGap = 2; |
| 64 | const ledW = (ledArea - ledGap * (LEDS_PER_ROW - 1)) / LEDS_PER_ROW; |
| 65 | |
| 66 | for (let i = 0; i < datasets.length; ++i) { |
| 67 | const ds = datasets[i]; |
| 68 | const span = (ds.max - ds.min) || 1; |
| 69 | const v = Number.isFinite(ds.value) ? ds.value : ds.min; |
| 70 | const norm = Math.max(0, Math.min(1, (v - ds.min) / span)); |
| 71 | const lit = Math.round(norm * LEDS_PER_ROW); |
| 72 | const yMid = cardY + i * rowH + rowH * 0.5; |
| 73 | |
| 74 | // Channel label on the left. |
| 75 | ctx.fillStyle = theme.widget_text; |
| 76 | ctx.font = "bold 11px sans-serif"; |
| 77 | ctx.textAlign = "start"; |
nothing calls this directly
no test coverage detected