| 5 | // (and colour intensity) scale with the magnitude. |
| 6 | |
| 7 | function paint(ctx, w, h) { |
| 8 | // Cream paper background + vignette. |
| 9 | ctx.fillStyle = theme.widget_base; |
| 10 | ctx.fillRect(0, 0, w, h); |
| 11 | |
| 12 | const pairs = Math.floor(datasets.length / 2); |
| 13 | |
| 14 | // Header. |
| 15 | ctx.fillStyle = theme.widget_text; |
| 16 | ctx.font = "bold 11px sans-serif"; |
| 17 | ctx.textAlign = "start"; |
| 18 | ctx.fillText("VECTOR FIELD", 14, 18); |
| 19 | ctx.fillStyle = theme.placeholder_text; |
| 20 | ctx.font = "10px sans-serif"; |
| 21 | ctx.textAlign = "end"; |
| 22 | ctx.fillText(pairs + (pairs === 1 ? " vector" : " vectors"), w - 14, 18); |
| 23 | ctx.fillStyle = theme.widget_border; |
| 24 | ctx.fillRect(14, 22, w - 28, 1); |
| 25 | |
| 26 | if (pairs === 0) { |
| 27 | ctx.fillStyle = theme.placeholder_text; |
| 28 | ctx.font = "12px sans-serif"; |
| 29 | ctx.textAlign = "center"; |
| 30 | ctx.fillText("Add datasets in (Vx, Vy) pairs", w / 2, h / 2); |
| 31 | return; |
| 32 | } |
| 33 | |
| 34 | // Layout. |
| 35 | const cols = Math.ceil(Math.sqrt(pairs * w / h)); |
| 36 | const rows = Math.ceil(pairs / cols); |
| 37 | const padX = 14; |
| 38 | const padTop = 30; |
| 39 | const padBot = 14; |
| 40 | const gap = 8; |
| 41 | const cw = (w - padX * 2 - gap * (cols - 1)) / cols; |
| 42 | const ch = (h - padTop - padBot - gap * (rows - 1)) / rows; |
| 43 | |
| 44 | for (let i = 0; i < pairs; ++i) { |
| 45 | const r = Math.floor(i / cols); |
| 46 | const c = i % cols; |
| 47 | const x0 = padX + c * (cw + gap); |
| 48 | const y0 = padTop + r * (ch + gap); |
| 49 | const cx = x0 + cw * 0.5; |
| 50 | const cy = y0 + ch * 0.5 + 4; |
| 51 | const arrowMax = Math.max(8, Math.min(cw, ch - 24) * 0.40); |
| 52 | |
| 53 | // Card. |
| 54 | ctx.fillStyle = theme.widget_border; |
| 55 | ctx.fillRect(x0 + 1, y0 + 2, cw, ch); |
| 56 | ctx.fillStyle = theme.alternate_base; |
| 57 | ctx.fillRect(x0, y0, cw, ch); |
| 58 | ctx.strokeStyle = theme.widget_border; |
| 59 | ctx.lineWidth = 1; |
| 60 | ctx.strokeRect(x0 + 0.5, y0 + 0.5, cw - 1, ch - 1); |
| 61 | |
| 62 | // Mini header. |
| 63 | const dsX = datasets[i * 2]; |
| 64 | const dsY = datasets[i * 2 + 1]; |