From a16f52696494189b877b5f142c65af4b1aca7616 Mon Sep 17 00:00:00 2001 From: znetsixe Date: Thu, 28 May 2026 17:52:19 +0200 Subject: [PATCH] chore(dashboardAPI): enforce min visual gap between threshold lines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User-visible problem: with the basin config dryRunThresholdPercent=2 (so dryRunLevel ≈ outflowLevel) and highVolumeSafetyThresholdPercent=98 (so highSafetyLevel ≈ overflowLevel), two pairs of threshold lines sat right on top of each other in the tank visual, leaving no room between them for their labels. The 'BELOW' fallback in the label algorithm couldn't fit either, so labels ended up crossing lines. Fix: enforce a minimum 28 px visual gap between adjacent threshold lines inside the tank (≈3.7 % of the 760-tall reference frame, > LABEL_H + 2). Lines closer than that get spread apart while preserving order. If the stack would push the lowest line past the tank floor, the whole stack shifts up to fit. Slight geometric distortion is accepted — the tank visual conveys ordering and zone structure, not exact-scale level measurement; numeric values are still rendered next to each line. Result: at any basin geometry, labels sit cleanly above their line with no overlap, no label-on-line collision, and no fallback to a 'stacked' position that crosses its own line. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/specificClass.js | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/specificClass.js b/src/specificClass.js index 6c58a7b..0185c60 100644 --- a/src/specificClass.js +++ b/src/specificClass.js @@ -205,11 +205,36 @@ class DashboardApi { const yFor = (v) => +(TANK_BOT - (v / heightBasin) * TANK_H).toFixed(2); const tyFor = (yLine) => +(yLine - 8).toFixed(2); // centre 16px text on the line - const y_overflow = yFor(overflowLevel); - const y_highSafety = yFor(highSafetyLevel); - const y_inflow = yFor(inflowLevel); - const y_dryRun = yFor(dryRunLevel); - const y_outflow = yFor(outflowLevel); + let y_overflow = yFor(overflowLevel); + let y_highSafety = yFor(highSafetyLevel); + let y_inflow = yFor(inflowLevel); + let y_dryRun = yFor(dryRunLevel); + let y_outflow = yFor(outflowLevel); + + // Enforce a minimum visual gap between adjacent threshold lines so labels + // can always sit cleanly between them — independent of how close the + // underlying physical thresholds are. Slight geometric distortion is + // acceptable: the tank visual conveys ORDERING and ZONE STRUCTURE, not + // exact-scale level measurement. Dashed/value labels carry the true + // numeric values. + const MIN_LINE_GAP = 28; // px (≈3.7% of 760-tall frame, > LABEL_H + 2) + const sorted = [ + { id: 'overflow', get: () => y_overflow, set: (v) => (y_overflow = v) }, + { id: 'highSafety', get: () => y_highSafety, set: (v) => (y_highSafety = v) }, + { id: 'inflow', get: () => y_inflow, set: (v) => (y_inflow = v) }, + { id: 'dryRun', get: () => y_dryRun, set: (v) => (y_dryRun = v) }, + { id: 'outflow', get: () => y_outflow, set: (v) => (y_outflow = v) }, + ].sort((a, b) => a.get() - b.get()); + // Push down to enforce min gap (anchor: topmost line) + for (let i = 1; i < sorted.length; i++) { + const minY = sorted[i - 1].get() + MIN_LINE_GAP; + if (sorted[i].get() < minY) sorted[i].set(minY); + } + // If the last (lowest) line went past the floor, shift the whole stack up. + const overshoot = sorted[sorted.length - 1].get() - TANK_BOT; + if (overshoot > 0) { + for (const item of sorted) item.set(item.get() - overshoot); + } // Label y-positions: labels sit either ABOVE or BELOW their threshold // line, never on it. Each label is offset by ABOVE_OFFSET=22 px above