Editor: nudge crowded threshold inputs off their lines with leader lines
When real wastewater values cluster near the basin floor (minLevel, dryRunLevel, outflowLevel are often within a few cm of each other), the threshold inputs were stacking on top of each other. Now: - Threshold LINE stays at its proportional y on the tank (visual truth: that's where the level actually is). - Input BOX / label / unit are positioned in a nudged right-column stack with a minimum 26-px gap so they never overlap. - A dashed grey leader line connects each line to its input when they had to be pulled apart, so the association stays visible. - Items are sorted by ideal y top-down and nudged downward once; basinHeight is pinned at the rim and acts as the anchor. Also: viewBox extended 430 → 480 so the bottom-of-stack items have room below the tank when the bottom cluster is tight. Warning ribbon moved to y=460 accordingly. No schema change; purely UI layout. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -188,52 +188,113 @@
|
|||||||
const y = DIAG.botY - (val / basinH) * (DIAG.botY - DIAG.topY);
|
const y = DIAG.botY - (val / basinH) * (DIAG.botY - DIAG.topY);
|
||||||
return Math.max(DIAG.topY - 8, Math.min(DIAG.botY + 8, y));
|
return Math.max(DIAG.topY - 8, Math.min(DIAG.botY + 8, y));
|
||||||
};
|
};
|
||||||
const placeRow = (id, y) => {
|
// Place a right-column item. yLine is the threshold's true
|
||||||
if (y == null) return;
|
// proportional position on the tank; yInput is where the label
|
||||||
const line = document.getElementById(`ps-line-${id}`);
|
// and input box land (may be nudged away from yLine to avoid
|
||||||
|
// overlap with neighbouring items). A dashed leader line is
|
||||||
|
// shown only when the two differ by more than a pixel or two.
|
||||||
|
const placeItem = (id, yLine, yInput) => {
|
||||||
|
const line = document.getElementById(`ps-line-${id}`);
|
||||||
const label = document.getElementById(`ps-label-${id}`);
|
const label = document.getElementById(`ps-label-${id}`);
|
||||||
const unit = document.getElementById(`ps-unit-${id}`);
|
const unit = document.getElementById(`ps-unit-${id}`);
|
||||||
const fo = document.getElementById(`ps-fo-${id}`);
|
const fo = document.getElementById(`ps-fo-${id}`);
|
||||||
const sub = document.getElementById(`ps-sub-${id}`);
|
const sub = document.getElementById(`ps-sub-${id}`);
|
||||||
if (line) { line.setAttribute('y1', y); line.setAttribute('y2', y); }
|
const lead = document.getElementById(`ps-leader-${id}`);
|
||||||
if (label) label.setAttribute('y', y + 4);
|
if (line) { line.setAttribute('y1', yLine); line.setAttribute('y2', yLine); }
|
||||||
if (unit) unit.setAttribute('y', y + 4);
|
if (label) label.setAttribute('y', yInput + 4);
|
||||||
if (fo) fo.setAttribute('y', y - 11);
|
if (unit) unit.setAttribute('y', yInput + 4);
|
||||||
if (sub) sub.setAttribute('y', y + 15);
|
if (fo) fo.setAttribute('y', yInput - 11);
|
||||||
|
if (sub) sub.setAttribute('y', yInput + 15);
|
||||||
|
if (lead) {
|
||||||
|
if (Math.abs(yLine - yInput) > 2) {
|
||||||
|
lead.setAttribute('x1', 325); lead.setAttribute('y1', yLine);
|
||||||
|
lead.setAttribute('x2', 420); lead.setAttribute('y2', yInput);
|
||||||
|
lead.setAttribute('visibility', 'visible');
|
||||||
|
} else {
|
||||||
|
lead.setAttribute('visibility', 'hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const redraw = () => {
|
const redraw = () => {
|
||||||
const basinH = fNum('basinHeight') || 5;
|
const basinH = fNum('basinHeight') || 5;
|
||||||
placeRow('overflowLevel', yForLevel(fNum('overflowLevel'), basinH));
|
|
||||||
placeRow('maxLevel', yForLevel(fNum('maxLevel'), basinH));
|
// Derived safety levels (participate in the right-column stack)
|
||||||
placeRow('startLevel', yForLevel(fNum('startLevel'), basinH));
|
const basedOn = document.getElementById('node-input-minHeightBasedOn')?.value || 'outlet';
|
||||||
placeRow('minLevel', yForLevel(fNum('minLevel'), basinH));
|
const refLow = basedOn === 'inlet' ? fNum('inflowLevel') : fNum('outflowLevel');
|
||||||
placeRow('inflowLevel', yForLevel(fNum('inflowLevel'), basinH));
|
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;
|
||||||
|
|
||||||
|
// Build the right-column items. basinHeight is pinned at the
|
||||||
|
// rim (DIAG.topY); others float on the proportional axis and
|
||||||
|
// get nudged apart so the input boxes don't overlap.
|
||||||
|
const items = [
|
||||||
|
{ id: 'basinHeight', yLine: DIAG.topY, pinned: true },
|
||||||
|
{ id: 'overflowLevel', yLine: yForLevel(fNum('overflowLevel'), basinH) },
|
||||||
|
{ id: 'maxLevel', yLine: yForLevel(fNum('maxLevel'), basinH) },
|
||||||
|
{ id: 'startLevel', yLine: yForLevel(fNum('startLevel'), basinH) },
|
||||||
|
{ id: 'minLevel', yLine: yForLevel(fNum('minLevel'), basinH) },
|
||||||
|
{ id: 'dryRunLevel', yLine: yForLevel(dryLvl, basinH) },
|
||||||
|
{ id: 'outflowLevel', yLine: yForLevel(fNum('outflowLevel'), basinH) },
|
||||||
|
].filter(it => it.yLine != null);
|
||||||
|
|
||||||
|
const GAP = 26;
|
||||||
|
items.sort((a, b) => a.yLine - b.yLine);
|
||||||
|
let prev = -Infinity;
|
||||||
|
for (const it of items) {
|
||||||
|
if (it.pinned) { it.yInput = it.yLine; prev = it.yInput; continue; }
|
||||||
|
it.yInput = Math.max(it.yLine, prev + GAP);
|
||||||
|
prev = it.yInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide leader lines for items whose input is cleared
|
||||||
|
const active = new Set(items.map(it => it.id));
|
||||||
|
['overflowLevel','maxLevel','startLevel','minLevel','dryRunLevel','outflowLevel'].forEach(id => {
|
||||||
|
if (!active.has(id)) {
|
||||||
|
const lead = document.getElementById(`ps-leader-${id}`);
|
||||||
|
if (lead) lead.setAttribute('visibility', 'hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (const it of items) placeItem(it.id, it.yLine, it.yInput);
|
||||||
|
|
||||||
|
// Inlet arrow — sole item on the left, no stacking concerns
|
||||||
|
const inflowY = yForLevel(fNum('inflowLevel'), basinH);
|
||||||
|
if (inflowY != null) {
|
||||||
|
const line = document.getElementById('ps-line-inflowLevel');
|
||||||
|
const lbl = document.getElementById('ps-label-inflowLevel');
|
||||||
|
const sub = document.getElementById('ps-sub-inflowLevel');
|
||||||
|
const fo = document.getElementById('ps-fo-inflowLevel');
|
||||||
|
const unit = document.getElementById('ps-unit-inflowLevel');
|
||||||
|
if (line) { line.setAttribute('y1', inflowY); line.setAttribute('y2', inflowY); }
|
||||||
|
if (lbl) lbl.setAttribute('y', inflowY - 4);
|
||||||
|
if (sub) sub.setAttribute('y', inflowY + 8);
|
||||||
|
if (fo) fo.setAttribute('y', inflowY - 11);
|
||||||
|
if (unit) unit.setAttribute('y', inflowY + 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dead-volume band: from outflowLevel down to the floor
|
||||||
const outflowY = yForLevel(fNum('outflowLevel'), basinH);
|
const outflowY = yForLevel(fNum('outflowLevel'), basinH);
|
||||||
placeRow('outflowLevel', outflowY);
|
const deadvol = document.getElementById('ps-deadvol');
|
||||||
// Dead-volume band fills from outflowLevel down to the floor
|
|
||||||
const deadvol = document.getElementById('ps-deadvol');
|
|
||||||
if (deadvol && outflowY != null) {
|
if (deadvol && outflowY != null) {
|
||||||
deadvol.setAttribute('y', outflowY);
|
deadvol.setAttribute('y', outflowY);
|
||||||
deadvol.setAttribute('height', Math.max(0, DIAG.botY - outflowY));
|
deadvol.setAttribute('height', Math.max(0, DIAG.botY - outflowY));
|
||||||
}
|
}
|
||||||
// Derived dryRunLevel (safety, from %)
|
|
||||||
const basedOn = document.getElementById('node-input-minHeightBasedOn')?.value || 'outlet';
|
// dryRunLevel label text (derived, read-only)
|
||||||
const refLow = basedOn === 'inlet' ? fNum('inflowLevel') : fNum('outflowLevel');
|
|
||||||
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');
|
const dryLbl = document.getElementById('ps-label-dryRunLevel');
|
||||||
if (dryLbl) dryLbl.textContent = dryLvl != null
|
if (dryLbl) dryLbl.textContent = dryLvl != null
|
||||||
? `dryRunLevel ≈ ${dryLvl.toFixed(2)} m (safety — from %)`
|
? `dryRunLevel ≈ ${dryLvl.toFixed(2)} m (safety — from %)`
|
||||||
: 'dryRunLevel ≈ — m (safety — from %)';
|
: 'dryRunLevel ≈ — m (safety — from %)';
|
||||||
// Safety-section readouts (same values, second view)
|
|
||||||
|
// Safety-section readouts (second view, beneath the diagram)
|
||||||
const d1 = document.getElementById('derived-dryRunLevel');
|
const d1 = document.getElementById('derived-dryRunLevel');
|
||||||
if (d1) d1.textContent = dryLvl != null ? `→ dryRunLevel ≈ ${dryLvl.toFixed(2)} m` : '→ dryRunLevel ≈ — m';
|
if (d1) d1.textContent = dryLvl != null ? `→ dryRunLevel ≈ ${dryLvl.toFixed(2)} m` : '→ dryRunLevel ≈ — m';
|
||||||
const d2 = document.getElementById('derived-overfillLevel');
|
const d2 = document.getElementById('derived-overfillLevel');
|
||||||
if (d2) d2.textContent = ovfLvl != null ? `→ overfillLevel ≈ ${ovfLvl.toFixed(2)} m` : '→ overfillLevel ≈ — m';
|
if (d2) d2.textContent = ovfLvl != null ? `→ overfillLevel ≈ ${ovfLvl.toFixed(2)} m` : '→ overfillLevel ≈ — m';
|
||||||
|
|
||||||
// Ordering warning ribbon
|
// Ordering warning ribbon
|
||||||
const warn = document.getElementById('ps-warning');
|
const warn = document.getElementById('ps-warning');
|
||||||
const issues = [];
|
const issues = [];
|
||||||
@@ -324,7 +385,7 @@
|
|||||||
#ps-basin-diagram input[type=number]:focus { outline: 1px solid #0c99d9; border-color: #0c99d9; }
|
#ps-basin-diagram input[type=number]:focus { outline: 1px solid #0c99d9; border-color: #0c99d9; }
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<svg id="ps-basin-diagram" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 520 430"
|
<svg id="ps-basin-diagram" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 520 480"
|
||||||
style="display:block;width:100%;max-width:540px;margin:0 0 12px 0;background:#fff;border:1px solid #e5e5e5;border-radius:4px;"
|
style="display:block;width:100%;max-width:540px;margin:0 0 12px 0;background:#fff;border:1px solid #e5e5e5;border-radius:4px;"
|
||||||
font-family="Arial,sans-serif" font-size="11">
|
font-family="Arial,sans-serif" font-size="11">
|
||||||
<defs>
|
<defs>
|
||||||
@@ -404,8 +465,17 @@
|
|||||||
<line x1="195" y1="380" x2="325" y2="380" stroke="#000" stroke-width="2" />
|
<line x1="195" y1="380" x2="325" y2="380" stroke="#000" stroke-width="2" />
|
||||||
<text x="330" y="384" fill="#000">0 m (datum)</text>
|
<text x="330" y="384" fill="#000">0 m (datum)</text>
|
||||||
|
|
||||||
|
<!-- Leader lines: shown when the input row had to be nudged off its threshold's ideal y -->
|
||||||
|
<line id="ps-leader-basinHeight" x1="0" y1="0" x2="0" y2="0" stroke="#bbb" stroke-width="0.6" stroke-dasharray="2 2" visibility="hidden" />
|
||||||
|
<line id="ps-leader-overflowLevel" x1="0" y1="0" x2="0" y2="0" stroke="#bbb" stroke-width="0.6" stroke-dasharray="2 2" visibility="hidden" />
|
||||||
|
<line id="ps-leader-maxLevel" x1="0" y1="0" x2="0" y2="0" stroke="#bbb" stroke-width="0.6" stroke-dasharray="2 2" visibility="hidden" />
|
||||||
|
<line id="ps-leader-startLevel" x1="0" y1="0" x2="0" y2="0" stroke="#bbb" stroke-width="0.6" stroke-dasharray="2 2" visibility="hidden" />
|
||||||
|
<line id="ps-leader-minLevel" x1="0" y1="0" x2="0" y2="0" stroke="#bbb" stroke-width="0.6" stroke-dasharray="2 2" visibility="hidden" />
|
||||||
|
<line id="ps-leader-dryRunLevel" x1="0" y1="0" x2="0" y2="0" stroke="#bbb" stroke-width="0.6" stroke-dasharray="2 2" visibility="hidden" />
|
||||||
|
<line id="ps-leader-outflowLevel" x1="0" y1="0" x2="0" y2="0" stroke="#bbb" stroke-width="0.6" stroke-dasharray="2 2" visibility="hidden" />
|
||||||
|
|
||||||
<!-- Ordering-warning ribbon -->
|
<!-- Ordering-warning ribbon -->
|
||||||
<text id="ps-warning" x="260" y="410" text-anchor="middle" fill="#C0392B" font-size="10" font-style="italic" visibility="hidden"></text>
|
<text id="ps-warning" x="260" y="460" text-anchor="middle" fill="#C0392B" font-size="10" font-style="italic" visibility="hidden"></text>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
|
|||||||
Reference in New Issue
Block a user