diff --git a/static/js/nlos.js b/static/js/nlos.js index ed8cee5..919bc7c 100644 --- a/static/js/nlos.js +++ b/static/js/nlos.js @@ -200,6 +200,33 @@ ctx.restore(); } + // Label with a dark rounded-rect background so it stays readable over busy backgrounds. + function pillLabel(ctx, text, x, y, color = COLORS.fg, align = 'left', baseline = 'middle') { + ctx.save(); + ctx.font = '12px system-ui, sans-serif'; + ctx.textAlign = align; + ctx.textBaseline = baseline; + const tw = ctx.measureText(text).width; + const padX = 5, padY = 3; + const h = 14 + padY * 2; + let bx; + if (align === 'right') bx = x - tw - padX; + else if (align === 'center') bx = x - tw / 2 - padX; + else bx = x - padX; + let by; + if (baseline === 'middle') by = y - h / 2; + else if (baseline === 'top') by = y - padY; + else by = y - 14 - padY; + ctx.fillStyle = 'rgba(26, 29, 36, 0.82)'; + ctx.beginPath(); + if (ctx.roundRect) ctx.roundRect(bx, by, tw + padX * 2, h, 3); + else ctx.rect(bx, by, tw + padX * 2, h); + ctx.fill(); + ctx.fillStyle = color; + ctx.fillText(text, x, y); + ctx.restore(); + } + // ---------- Scene 1: Straight-line LOS ---------- function sceneLOS() { const s = scene('scene-los', { @@ -1777,7 +1804,7 @@ ctx.setLineDash([6, 4]); ctx.beginPath(); ctx.moveTo(xOf(0), yA); ctx.lineTo(xOf(100), yB); ctx.stroke(); ctx.setLineDash([]); - labelText(ctx, 'straight LOS: blocked by terrain', xOf(50), (yA + yB) / 2 - 6, COLORS.red, 'center'); + pillLabel(ctx, 'straight LOS: blocked by terrain', xOf(50), (yA + yB) / 2 - 8, COLORS.red, 'center', 'middle'); // Ducted path: climb into the duct, bounce inside, descend to RX const ductMid = (ductBottom + ductTop) / 2; @@ -1804,8 +1831,8 @@ const hPxB = hB / altMax * pH; drawAntenna(ctx, xOf(0), yOf(terrain[0]), hPxA, COLORS.orange); drawAntenna(ctx, xOf(100), yOf(terrain[100]), hPxB, COLORS.orange); - labelText(ctx, 'My QTH', xOf(0) + 4, yOf(elevA) - 8, COLORS.orange); - labelText(ctx, 'NTMS beacon', xOf(100) - 4, yOf(elevB) - 8, COLORS.orange, 'right'); + pillLabel(ctx, 'My QTH', xOf(0) + 4, yOf(elevA) - 12, COLORS.orange, 'left', 'middle'); + pillLabel(ctx, 'NTMS beacon', xOf(100) - 4, yOf(elevB) - 12, COLORS.orange, 'right', 'middle'); requestAnimationFrame(draw); } @@ -2115,11 +2142,14 @@ labelText(ctx, label, xOf(labelAt), yOf(fn(labelAt)) - 8, color); } } - drawCurve(dist10, COLORS.cyan, '10 GHz', 25); - drawCurve(dist24, COLORS.orange, '24 GHz', 45); + // Only show fixed curve end-labels when cursor is far from them + const cursorX = xOf(pwat); + const showEnd10 = Math.abs(cursorX - xOf(25)) > 60; + const showEnd24 = Math.abs(cursorX - xOf(45)) > 60; + drawCurve(dist10, COLORS.cyan, showEnd10 ? '10 GHz' : '', 25); + drawCurve(dist24, COLORS.orange, showEnd24 ? '24 GHz' : '', 45); // Current-point cursor - const cursorX = xOf(pwat); ctx.strokeStyle = COLORS.yellow; ctx.setLineDash([3, 4]); ctx.beginPath(); ctx.moveTo(cursorX, margin.t); ctx.lineTo(cursorX, margin.t + pH); ctx.stroke(); @@ -2131,9 +2161,13 @@ ctx.fillStyle = COLORS.orange; ctx.beginPath(); ctx.arc(cursorX, y24, 5, 0, Math.PI * 2); ctx.fill(); - labelText(ctx, `PWAT ${pwat.toFixed(0)} mm`, cursorX + 8, margin.t + 14, COLORS.yellow); - labelText(ctx, `10 GHz: ${Math.round(dist10(pwat))} km`, cursorX + 8, y10 + 14, COLORS.cyan); - labelText(ctx, `24 GHz: ${Math.round(dist24(pwat))} km`, cursorX + 8, y24 - 8, COLORS.orange); + // Flip readout labels to left side when cursor is past midpoint + const flipLeft = pwat > (pMin + pMax) / 2; + const align = flipLeft ? 'right' : 'left'; + const offset = flipLeft ? -10 : 10; + pillLabel(ctx, `PWAT ${pwat.toFixed(0)} mm`, cursorX + offset, margin.t + 14, COLORS.yellow, align, 'middle'); + pillLabel(ctx, `10 GHz: ${Math.round(dist10(pwat))} km`, cursorX + offset, y10 + 16, COLORS.cyan, align, 'middle'); + pillLabel(ctx, `24 GHz: ${Math.round(dist24(pwat))} km`, cursorX + offset, y24 - 14, COLORS.orange, align, 'middle'); requestAnimationFrame(draw); }