diff --git a/pumpingStation.html b/pumpingStation.html index 36e6c05..216f660 100644 --- a/pumpingStation.html +++ b/pumpingStation.html @@ -220,31 +220,65 @@ const dryLvl = (refLow != null && dryPct != null) ? refLow * (1 + dryPct / 100) : null; const ovfLvl = (ovf != null && ovfPct != null) ? ovf * (ovfPct / 100) : null; - // Build the right-column items. basinHeight is pinned at the - // tank rim; others are sorted by their proportional y and then - // pushed apart so every dashed line gets a minimum vertical - // gap for readability. The diagram is a schematic ordered - // list, not a strictly to-scale rendering. + // Right-column stack. TWO anchors: basinHeight pinned at the + // tank rim (top) and outflowLevel pinned at its proportional y + // (bottom). Everything between is nudged to maintain a minimum + // vertical gap via two passes — top-down from the rim, then + // bottom-up from the outlet — so the dashed lines keep their + // value-order and outlet stays near the floor where it belongs. const items = [ - { id: 'basinHeight', yIdeal: DIAG.topY, pinned: true }, + { id: 'basinHeight', yIdeal: DIAG.topY, pinned: true }, { id: 'overflowLevel', yIdeal: yForLevel(fNum('overflowLevel'), basinH) }, { id: 'maxLevel', yIdeal: yForLevel(fNum('maxLevel'), basinH) }, { id: 'startLevel', yIdeal: yForLevel(fNum('startLevel'), basinH) }, { id: 'minLevel', yIdeal: yForLevel(fNum('minLevel'), basinH) }, { id: 'dryRunLevel', yIdeal: yForLevel(dryLvl, basinH) }, - { id: 'outflowLevel', yIdeal: yForLevel(fNum('outflowLevel'), basinH) }, + { id: 'outflowLevel', yIdeal: yForLevel(fNum('outflowLevel'), basinH), pinned: true }, ].filter(it => it.yIdeal != null); const GAP = 36; items.sort((a, b) => a.yIdeal - b.yIdeal); - let prev = -Infinity; - for (const it of items) { - if (it.pinned) { it.y = it.yIdeal; prev = it.y; continue; } - it.y = Math.max(it.yIdeal, prev + GAP); - prev = it.y; + for (const it of items) it.y = it.yIdeal; + // Pass 1: top-down — push DOWN to maintain GAP; pinned items don't move + for (let i = 1; i < items.length; i++) { + if (items[i].pinned) continue; + items[i].y = Math.max(items[i].y, items[i - 1].y + GAP); + } + // Pass 2: bottom-up — push UP so outflow's pin propagates up the stack + for (let i = items.length - 2; i >= 0; i--) { + if (items[i].pinned) continue; + items[i].y = Math.min(items[i].y, items[i + 1].y - GAP); } for (const it of items) placeItem(it.id, it.y); + // Zone labels between adjacent thresholds (italic, centered). + // Hidden if either bracketing threshold is missing, or the gap + // is too small to read (< 14 px). + const placeZone = (zoneId, topId, botId) => { + const el = document.getElementById(`ps-zone-${zoneId}`); + if (!el) return; + const top = items.find(it => it.id === topId); + const bot = items.find(it => it.id === botId); + if (!top || !bot || (bot.y - top.y) < 14) { + el.setAttribute('visibility', 'hidden'); return; + } + el.setAttribute('y', (top.y + bot.y) / 2 + 3); + el.setAttribute('visibility', 'visible'); + }; + placeZone('spare', 'overflowLevel', 'maxLevel'); + placeZone('sewage', 'maxLevel', 'startLevel'); + placeZone('buffer1', 'startLevel', 'minLevel'); + placeZone('buffer2', 'minLevel', 'dryRunLevel'); + // "Dead volume" sits inside the blue band between outflowLevel and the floor + const outflowPinned = items.find(it => it.id === 'outflowLevel'); + const deadLbl = document.getElementById('ps-zone-dead'); + if (deadLbl && outflowPinned && (DIAG.botY - outflowPinned.y) > 14) { + deadLbl.setAttribute('y', (outflowPinned.y + DIAG.botY) / 2 + 3); + deadLbl.setAttribute('visibility', 'visible'); + } else if (deadLbl) { + deadLbl.setAttribute('visibility', 'hidden'); + } + // Inlet arrow — sole item on the left, no stacking concerns const inflowY = yForLevel(fNum('inflowLevel'), basinH); if (inflowY != null) { @@ -385,6 +419,20 @@ + + basin volume + + + + + + + Spare volume before spilling + Sewage + tank buffer + Tank buffer + Tank buffer + Dead volume + @@ -465,11 +513,6 @@ -
- - -
-

Control Strategy