diff --git a/pumpingStation.html b/pumpingStation.html index a74c25e..417e720 100644 --- a/pumpingStation.html +++ b/pumpingStation.html @@ -173,31 +173,93 @@ setNumberField('node-input-flowSetpoint', this.flowSetpoint); setNumberField('node-input-flowDeadband', this.flowDeadband); - // Live-compute derived safety levels so the operator can see - // what the % will actually trip at. Mirrors the code formula - // in specificClass._validateThresholdOrdering. - const fNum = (id) => parseFloat(document.getElementById(`node-input-${id}`)?.value); - const updateDerivedLevels = () => { + // Interactive diagram: place every threshold line/input at its + // proportional y on the tank, plus compute derived safety levels + // (dryRunLevel, overfillLevel) that are shown both in the diagram + // and next to the safety-% fields. Same formulas as + // specificClass._validateThresholdOrdering. + const DIAG = { topY: 40, botY: 380 }; + const fNum = (id) => { + const v = parseFloat(document.getElementById(`node-input-${id}`)?.value); + return Number.isFinite(v) ? v : null; + }; + const yForLevel = (val, basinH) => { + if (val == null || !basinH) return null; + const y = DIAG.botY - (val / basinH) * (DIAG.botY - DIAG.topY); + return Math.max(DIAG.topY - 8, Math.min(DIAG.botY + 8, y)); + }; + const placeRow = (id, y) => { + if (y == null) return; + const line = document.getElementById(`ps-line-${id}`); + const label = document.getElementById(`ps-label-${id}`); + const unit = document.getElementById(`ps-unit-${id}`); + const fo = document.getElementById(`ps-fo-${id}`); + const sub = document.getElementById(`ps-sub-${id}`); + if (line) { line.setAttribute('y1', y); line.setAttribute('y2', y); } + if (label) label.setAttribute('y', y + 4); + if (unit) unit.setAttribute('y', y + 4); + if (fo) fo.setAttribute('y', y - 11); + if (sub) sub.setAttribute('y', y + 15); + }; + const redraw = () => { + const basinH = fNum('basinHeight') || 5; + placeRow('overflowLevel', yForLevel(fNum('overflowLevel'), basinH)); + placeRow('maxLevel', yForLevel(fNum('maxLevel'), basinH)); + placeRow('startLevel', yForLevel(fNum('startLevel'), basinH)); + placeRow('minLevel', yForLevel(fNum('minLevel'), basinH)); + placeRow('inflowLevel', yForLevel(fNum('inflowLevel'), basinH)); + const outflowY = yForLevel(fNum('outflowLevel'), basinH); + placeRow('outflowLevel', outflowY); + // Dead-volume band fills from outflowLevel down to the floor + const deadvol = document.getElementById('ps-deadvol'); + if (deadvol && outflowY != null) { + deadvol.setAttribute('y', outflowY); + deadvol.setAttribute('height', Math.max(0, DIAG.botY - outflowY)); + } + // Derived dryRunLevel (safety, from %) const basedOn = document.getElementById('node-input-minHeightBasedOn')?.value || 'outlet'; const refLow = basedOn === 'inlet' ? fNum('inflowLevel') : fNum('outflowLevel'); - const dryRunPct = fNum('dryRunThresholdPercent'); - const overfillPct = fNum('overfillThresholdPercent'); - const overflow = fNum('overflowLevel'); - const dryRunLvl = Number.isFinite(refLow) && Number.isFinite(dryRunPct) - ? refLow * (1 + dryRunPct / 100) : null; - const overfillLvl = Number.isFinite(overflow) && Number.isFinite(overfillPct) - ? overflow * (overfillPct / 100) : null; - const dryEl = document.getElementById('derived-dryRunLevel'); - const ovfEl = document.getElementById('derived-overfillLevel'); - if (dryEl) dryEl.textContent = dryRunLvl != null ? `→ dryRunLevel ≈ ${dryRunLvl.toFixed(2)} m` : '→ dryRunLevel ≈ — m'; - if (ovfEl) ovfEl.textContent = overfillLvl != null ? `→ overfillLevel ≈ ${overfillLvl.toFixed(2)} m` : '→ overfillLevel ≈ — m'; + const dryPct = fNum('dryRunThresholdPercent'); + const ovfPct = fNum('overfillThresholdPercent'); + const ovf = fNum('overflowLevel'); + const dryLvl = (refLow != null && dryPct != null) ? refLow * (1 + dryPct / 100) : null; + const ovfLvl = (ovf != null && ovfPct != null) ? ovf * (ovfPct / 100) : null; + placeRow('dryRunLevel', yForLevel(dryLvl, basinH)); + const dryLbl = document.getElementById('ps-label-dryRunLevel'); + if (dryLbl) dryLbl.textContent = dryLvl != null + ? `dryRunLevel ≈ ${dryLvl.toFixed(2)} m (safety — from %)` + : 'dryRunLevel ≈ — m (safety — from %)'; + // Safety-section readouts (same values, second view) + const d1 = document.getElementById('derived-dryRunLevel'); + if (d1) d1.textContent = dryLvl != null ? `→ dryRunLevel ≈ ${dryLvl.toFixed(2)} m` : '→ dryRunLevel ≈ — m'; + const d2 = document.getElementById('derived-overfillLevel'); + if (d2) d2.textContent = ovfLvl != null ? `→ overfillLevel ≈ ${ovfLvl.toFixed(2)} m` : '→ overfillLevel ≈ — m'; + // Ordering warning ribbon + const warn = document.getElementById('ps-warning'); + const issues = []; + const pairs = [ + ['outflowLevel', 'inflowLevel', '<'], + ['inflowLevel', 'overflowLevel', '<'], + ['minLevel', 'startLevel', '<='], + ['startLevel', 'maxLevel', '<'], + ['maxLevel', 'overflowLevel', '<='], + ]; + for (const [a, b, op] of pairs) { + const av = fNum(a), bv = fNum(b); + if (av == null || bv == null) continue; + if (op === '<' ? !(av < bv) : !(av <= bv)) issues.push(`${a} ${op} ${b}`); + } + if (warn) { + if (issues.length) { warn.setAttribute('visibility', 'visible'); warn.textContent = `⚠ Check ordering: ${issues.join(', ')}`; } + else { warn.setAttribute('visibility', 'hidden'); } + } }; - ['inflowLevel','outflowLevel','overflowLevel','minHeightBasedOn','dryRunThresholdPercent','overfillThresholdPercent'] - .forEach((id) => { - const el = document.getElementById(`node-input-${id}`); - if (el) { el.addEventListener('input', updateDerivedLevels); el.addEventListener('change', updateDerivedLevels); } - }); - setTimeout(updateDerivedLevels, 50); + ['basinHeight','overflowLevel','maxLevel','startLevel','minLevel','inflowLevel','outflowLevel', + 'dryRunThresholdPercent','overfillThresholdPercent','minHeightBasedOn'].forEach((id) => { + const el = document.getElementById(`node-input-${id}`); + if (el) { el.addEventListener('input', redraw); el.addEventListener('change', redraw); } + }); + setTimeout(redraw, 60); //------------------- END OF CUSTOM config UI ELEMENTS ------------------- // }, @@ -250,76 +312,106 @@
-

Basin Geometry

-

All heights measured from the basin floor (0 m).

+

Basin parameters

+

Heights are measured from the basin floor (0 m). Enter values next to each line — the diagram scales to whatever you enter.

-
- 📐 Parameters diagram - - - - - - - - - - - - - basinHeight - - - overflowLevel - - - maxLevel - - - startLevel - - - Inlet - bottom of pipe - - - minLevel - - - dryRunLevel - safety — from % - - - Outlet - top of pipe - - - 0 m (datum) - -
+ + + + + + + + + + + + + + + + + basinHeight + + + + m + + + + overflowLevel + + + + m + + + + maxLevel + + + + m + + + + startLevel + + + + m + + + + Inlet + bottom of pipe + + + + m + + + + minLevel + + + + m + + + + dryRunLevel ≈ — m (safety — from %) + + + + Outlet + top of pipe + + + + m + + + + 0 m (datum) + + + +
-
- - -
- - -
- - -
-
- - -
-
- - -

@@ -334,18 +426,7 @@
-
- - -
-
- - -
-
- - -
+

Level-based uses minLevel / startLevel / maxLevel from the diagram above.