Compare commits

..

1 Commits

Author SHA1 Message Date
znetsixe
2dd419dbf4 Editor: nudge dashed lines themselves, revert tank height
Reverts the tank-bigger approach from last commit. Instead of
scaling the tank and keeping strict proportionality, the dashed
threshold lines are now nudged apart directly so each gets a
guaranteed 36-px vertical gap. Inputs and labels align with the
lines (no more leader lines needed).

Trade-off: the diagram is now an ordered schematic, not a strictly
to-scale rendering. Values are still shown next to each line via
the input boxes, and the value ordering is preserved. For an editor
where the goal is entering parameters, readability wins over scale
fidelity.

Sizing reverted:
  viewBox    620 → 430
  tank h     520 → 340
  botY       560 → 380

Behavior:
  GAP        30 → 36 (more visible space between dashed lines)
  placeItem  takes a single y now (line + input + label + unit
             share it); leader-line mechanism kept as hidden
             plumbing in case we switch back to proportional later

Dead-volume band now anchors to the (possibly-nudged) outflow line
instead of the proportional y so it still visually meets the line
cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 12:10:23 +02:00

View File

@@ -178,7 +178,7 @@
// (dryRunLevel, overfillLevel) that are shown both in the diagram // (dryRunLevel, overfillLevel) that are shown both in the diagram
// and next to the safety-% fields. Same formulas as // and next to the safety-% fields. Same formulas as
// specificClass._validateThresholdOrdering. // specificClass._validateThresholdOrdering.
const DIAG = { topY: 40, botY: 560 }; const DIAG = { topY: 40, botY: 380 };
const fNum = (id) => { const fNum = (id) => {
const v = parseFloat(document.getElementById(`node-input-${id}`)?.value); const v = parseFloat(document.getElementById(`node-input-${id}`)?.value);
return Number.isFinite(v) ? v : null; return Number.isFinite(v) ? v : null;
@@ -188,32 +188,24 @@
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));
}; };
// Place a right-column item. yLine is the threshold's true // Place a row — line, label, input, unit all share the same y.
// proportional position on the tank; yInput is where the label // The diagram is a schematic ordered list (value order is
// and input box land (may be nudged away from yLine to avoid // preserved, but the y-positions are distributed with a
// overlap with neighbouring items). A dashed leader line is // guaranteed minimum gap for readability), not a strictly
// shown only when the two differ by more than a pixel or two. // proportional rendering.
const placeItem = (id, yLine, yInput) => { const placeItem = (id, y) => {
const line = document.getElementById(`ps-line-${id}`); 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}`);
const lead = document.getElementById(`ps-leader-${id}`); const lead = document.getElementById(`ps-leader-${id}`);
if (line) { line.setAttribute('y1', yLine); line.setAttribute('y2', yLine); } if (line) { line.setAttribute('y1', y); line.setAttribute('y2', y); }
if (label) label.setAttribute('y', yInput + 4); if (label) label.setAttribute('y', y + 4);
if (unit) unit.setAttribute('y', yInput + 4); if (unit) unit.setAttribute('y', y + 4);
if (fo) fo.setAttribute('y', yInput - 11); if (fo) fo.setAttribute('y', y - 11);
if (sub) sub.setAttribute('y', yInput + 15); if (sub) sub.setAttribute('y', y + 15);
if (lead) { if (lead) lead.setAttribute('visibility', 'hidden');
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 = () => {
@@ -229,36 +221,29 @@
const ovfLvl = (ovf != null && ovfPct != null) ? ovf * (ovfPct / 100) : null; const ovfLvl = (ovf != null && ovfPct != null) ? ovf * (ovfPct / 100) : null;
// Build the right-column items. basinHeight is pinned at the // Build the right-column items. basinHeight is pinned at the
// rim (DIAG.topY); others float on the proportional axis and // tank rim; others are sorted by their proportional y and then
// get nudged apart so the input boxes don't overlap. // 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.
const items = [ const items = [
{ id: 'basinHeight', yLine: DIAG.topY, pinned: true }, { id: 'basinHeight', yIdeal: DIAG.topY, pinned: true },
{ id: 'overflowLevel', yLine: yForLevel(fNum('overflowLevel'), basinH) }, { id: 'overflowLevel', yIdeal: yForLevel(fNum('overflowLevel'), basinH) },
{ id: 'maxLevel', yLine: yForLevel(fNum('maxLevel'), basinH) }, { id: 'maxLevel', yIdeal: yForLevel(fNum('maxLevel'), basinH) },
{ id: 'startLevel', yLine: yForLevel(fNum('startLevel'), basinH) }, { id: 'startLevel', yIdeal: yForLevel(fNum('startLevel'), basinH) },
{ id: 'minLevel', yLine: yForLevel(fNum('minLevel'), basinH) }, { id: 'minLevel', yIdeal: yForLevel(fNum('minLevel'), basinH) },
{ id: 'dryRunLevel', yLine: yForLevel(dryLvl, basinH) }, { id: 'dryRunLevel', yIdeal: yForLevel(dryLvl, basinH) },
{ id: 'outflowLevel', yLine: yForLevel(fNum('outflowLevel'), basinH) }, { id: 'outflowLevel', yIdeal: yForLevel(fNum('outflowLevel'), basinH) },
].filter(it => it.yLine != null); ].filter(it => it.yIdeal != null);
const GAP = 30; const GAP = 36;
items.sort((a, b) => a.yLine - b.yLine); items.sort((a, b) => a.yIdeal - b.yIdeal);
let prev = -Infinity; let prev = -Infinity;
for (const it of items) { for (const it of items) {
if (it.pinned) { it.yInput = it.yLine; prev = it.yInput; continue; } if (it.pinned) { it.y = it.yIdeal; prev = it.y; continue; }
it.yInput = Math.max(it.yLine, prev + GAP); it.y = Math.max(it.yIdeal, prev + GAP);
prev = it.yInput; prev = it.y;
} }
for (const it of items) placeItem(it.id, it.y);
// 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 // Inlet arrow — sole item on the left, no stacking concerns
const inflowY = yForLevel(fNum('inflowLevel'), basinH); const inflowY = yForLevel(fNum('inflowLevel'), basinH);
@@ -275,12 +260,14 @@
if (unit) unit.setAttribute('y', inflowY + 4); if (unit) unit.setAttribute('y', inflowY + 4);
} }
// Dead-volume band: from outflowLevel down to the floor // Dead-volume band: from the (possibly-nudged) outflow line
const outflowY = yForLevel(fNum('outflowLevel'), basinH); // down to the floor. Use the nudged y so the band meets the
const deadvol = document.getElementById('ps-deadvol'); // outflow line exactly.
if (deadvol && outflowY != null) { const outflowItem = items.find(it => it.id === 'outflowLevel');
deadvol.setAttribute('y', outflowY); const deadvol = document.getElementById('ps-deadvol');
deadvol.setAttribute('height', Math.max(0, DIAG.botY - outflowY)); if (deadvol && outflowItem) {
deadvol.setAttribute('y', outflowItem.y);
deadvol.setAttribute('height', Math.max(0, DIAG.botY - outflowItem.y));
} }
// dryRunLevel label text (derived, read-only) // dryRunLevel label text (derived, read-only)
@@ -385,7 +372,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 620" <svg id="ps-basin-diagram" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 520 430"
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>
@@ -395,7 +382,7 @@
</defs> </defs>
<!-- Tank body --> <!-- Tank body -->
<rect x="200" y="40" width="120" height="520" fill="#F0F8FF" stroke="#333" stroke-width="1.5" /> <rect x="200" y="40" width="120" height="340" fill="#F0F8FF" stroke="#333" stroke-width="1.5" />
<!-- Dead-volume band (y + height updated dynamically below outflowLevel) --> <!-- Dead-volume band (y + height updated dynamically below outflowLevel) -->
<rect id="ps-deadvol" x="201" width="118" fill="#AACCE0" /> <rect id="ps-deadvol" x="201" width="118" fill="#AACCE0" />
@@ -462,8 +449,8 @@
<text id="ps-unit-outflowLevel" x="500" fill="#555">m</text> <text id="ps-unit-outflowLevel" x="500" fill="#555">m</text>
<!-- Floor / datum --> <!-- Floor / datum -->
<line x1="195" y1="560" x2="325" y2="560" stroke="#000" stroke-width="2" /> <line x1="195" y1="380" x2="325" y2="380" stroke="#000" stroke-width="2" />
<text x="330" y="564" 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 --> <!-- 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-basinHeight" x1="0" y1="0" x2="0" y2="0" stroke="#bbb" stroke-width="0.6" stroke-dasharray="2 2" visibility="hidden" />
@@ -475,7 +462,7 @@
<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" /> <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="600" text-anchor="middle" fill="#C0392B" font-size="10" font-style="italic" visibility="hidden"></text> <text id="ps-warning" x="260" y="410" text-anchor="middle" fill="#C0392B" font-size="10" font-style="italic" visibility="hidden"></text>
</svg> </svg>
<div class="form-row"> <div class="form-row">