From 41a20d4679bdff91a58884bb8efef21a8608db60 Mon Sep 17 00:00:00 2001 From: znetsixe Date: Thu, 28 May 2026 11:32:45 +0200 Subject: [PATCH] chore(dashboardAPI): center basin labels, position above/below lines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Threshold labels were sitting right on top of their lines (label center at line_y - 8) and were right-aligned at the tank's right edge. They now: - Sit clearly above the line (label bottom 6 px above) by default, or below the line (label top 6 px below) when an adjacent threshold is closer than 24 px (would crowd both labels above their lines). For the current basin config this puts overflowLevel + inflowLevel + dryRunLevel ABOVE their lines, and highSafety + outflowLevel BELOW. - Are centered horizontally in the tank (name at left:115 width:95 right-aligned, value at left:215 width:80 left-aligned) so the combined phrase "overflowLevel 3.22 m" reads as one centered string. Value width 60 → 80 so 'mm'-formatted small-meter values don't wrap to two lines. Footer floor moved to y:728 to keep clear of the BELOW labels near the tank floor. Co-Authored-By: Claude Opus 4.7 (1M context) --- config/pumpingStation.json | 22 +++++++++---------- src/specificClass.js | 43 +++++++++++++++++++++----------------- 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/config/pumpingStation.json b/config/pumpingStation.json index 5f655d8..5228354 100644 --- a/config/pumpingStation.json +++ b/config/pumpingStation.json @@ -309,7 +309,7 @@ { "name": "Label Overflow Name", "type": "text", - "placement": { "top": {{ty_overflow}}, "left": 180, "width": 140, "height": 16 }, + "placement": { "top": {{ty_overflow}}, "left": 115, "width": 95, "height": 16 }, "background": { "color": { "fixed": "transparent" } }, "border": { "color": { "fixed": "transparent" }, "width": 0 }, "config": { "text": { "mode": "fixed", "fixed": "overflowLevel" }, "color": { "fixed": "#c92020" }, "size": 11, "align": "right", "valign": "middle" } @@ -317,7 +317,7 @@ { "name": "Label HighSafety Name", "type": "text", - "placement": { "top": {{ty_highSafety}}, "left": 180, "width": 140, "height": 16 }, + "placement": { "top": {{ty_highSafety}}, "left": 115, "width": 95, "height": 16 }, "background": { "color": { "fixed": "transparent" } }, "border": { "color": { "fixed": "transparent" }, "width": 0 }, "config": { "text": { "mode": "fixed", "fixed": "highSafety" }, "color": { "fixed": "#cf7e20" }, "size": 11, "align": "right", "valign": "middle" } @@ -325,7 +325,7 @@ { "name": "Label Inflow Name", "type": "text", - "placement": { "top": {{ty_inflow}}, "left": 180, "width": 140, "height": 16 }, + "placement": { "top": {{ty_inflow}}, "left": 115, "width": 95, "height": 16 }, "background": { "color": { "fixed": "transparent" } }, "border": { "color": { "fixed": "transparent" }, "width": 0 }, "config": { "text": { "mode": "fixed", "fixed": "inflowLevel" }, "color": { "fixed": "#3d8a5a" }, "size": 11, "align": "right", "valign": "middle" } @@ -333,7 +333,7 @@ { "name": "Label DryRun Name", "type": "text", - "placement": { "top": {{ty_dryRun}}, "left": 180, "width": 140, "height": 16 }, + "placement": { "top": {{ty_dryRun}}, "left": 115, "width": 95, "height": 16 }, "background": { "color": { "fixed": "transparent" } }, "border": { "color": { "fixed": "transparent" }, "width": 0 }, "config": { "text": { "mode": "fixed", "fixed": "dryRunLevel" }, "color": { "fixed": "#3a76a8" }, "size": 11, "align": "right", "valign": "middle" } @@ -341,7 +341,7 @@ { "name": "Label Outflow Name", "type": "text", - "placement": { "top": {{ty_outflow}}, "left": 180, "width": 140, "height": 16 }, + "placement": { "top": {{ty_outflow}}, "left": 115, "width": 95, "height": 16 }, "background": { "color": { "fixed": "transparent" } }, "border": { "color": { "fixed": "transparent" }, "width": 0 }, "config": { "text": { "mode": "fixed", "fixed": "outflowLevel" }, "color": { "fixed": "#6a6a6a" }, "size": 11, "align": "right", "valign": "middle" } @@ -349,7 +349,7 @@ { "name": "Value Overflow", "type": "metric-value", - "placement": { "top": {{ty_overflow}}, "left": 323, "width": 65, "height": 16 }, + "placement": { "top": {{ty_overflow}}, "left": 215, "width": 80, "height": 16 }, "background": { "color": { "fixed": "transparent" } }, "border": { "color": { "fixed": "transparent" }, "width": 0 }, "config": { "text": { "mode": "field", "fixed": "", "field": "overflowLevel" }, "color": { "fixed": "#c92020" }, "size": 11, "align": "left", "valign": "middle" } @@ -357,7 +357,7 @@ { "name": "Value HighSafety", "type": "metric-value", - "placement": { "top": {{ty_highSafety}}, "left": 323, "width": 65, "height": 16 }, + "placement": { "top": {{ty_highSafety}}, "left": 215, "width": 80, "height": 16 }, "background": { "color": { "fixed": "transparent" } }, "border": { "color": { "fixed": "transparent" }, "width": 0 }, "config": { "text": { "mode": "field", "fixed": "", "field": "highVolumeSafetyLevel" }, "color": { "fixed": "#cf7e20" }, "size": 11, "align": "left", "valign": "middle" } @@ -365,7 +365,7 @@ { "name": "Value Inflow", "type": "metric-value", - "placement": { "top": {{ty_inflow}}, "left": 323, "width": 65, "height": 16 }, + "placement": { "top": {{ty_inflow}}, "left": 215, "width": 80, "height": 16 }, "background": { "color": { "fixed": "transparent" } }, "border": { "color": { "fixed": "transparent" }, "width": 0 }, "config": { "text": { "mode": "field", "fixed": "", "field": "inflowLevel" }, "color": { "fixed": "#3d8a5a" }, "size": 11, "align": "left", "valign": "middle" } @@ -373,7 +373,7 @@ { "name": "Value DryRun", "type": "metric-value", - "placement": { "top": {{ty_dryRun}}, "left": 323, "width": 65, "height": 16 }, + "placement": { "top": {{ty_dryRun}}, "left": 215, "width": 80, "height": 16 }, "background": { "color": { "fixed": "transparent" } }, "border": { "color": { "fixed": "transparent" }, "width": 0 }, "config": { "text": { "mode": "field", "fixed": "", "field": "dryRunLevel" }, "color": { "fixed": "#3a76a8" }, "size": 11, "align": "left", "valign": "middle" } @@ -381,7 +381,7 @@ { "name": "Value Outflow", "type": "metric-value", - "placement": { "top": {{ty_outflow}}, "left": 323, "width": 65, "height": 16 }, + "placement": { "top": {{ty_outflow}}, "left": 215, "width": 80, "height": 16 }, "background": { "color": { "fixed": "transparent" } }, "border": { "color": { "fixed": "transparent" }, "width": 0 }, "config": { "text": { "mode": "field", "fixed": "", "field": "outflowLevel" }, "color": { "fixed": "#6a6a6a" }, "size": 11, "align": "left", "valign": "middle" } @@ -397,7 +397,7 @@ { "name": "Footer Floor", "type": "text", - "placement": { "top": 702, "left": 10, "width": 380, "height": 16 }, + "placement": { "top": 728, "left": 10, "width": 380, "height": 16 }, "background": { "color": { "fixed": "transparent" } }, "border": { "color": { "fixed": "transparent" }, "width": 0 }, "config": { "text": { "mode": "fixed", "fixed": "floor (0.00 m)" }, "color": { "fixed": "#8a8a8a" }, "size": 10, "align": "center", "valign": "middle" } diff --git a/src/specificClass.js b/src/specificClass.js index 87f7a33..06185fa 100644 --- a/src/specificClass.js +++ b/src/specificClass.js @@ -204,26 +204,31 @@ class DashboardApi { const y_dryRun = yFor(dryRunLevel); const y_outflow = yFor(outflowLevel); - // Label y-positions get min-gap enforcement so labels never overlap even - // when thresholds sit nearly on top of each other (e.g. dryRun=2 % means - // dryRunLevel sits right on outflowLevel; highSafety=98 % puts it under - // overflow). Lines stay at proportional y; only the label text moves. - // Two-pass (down + up) mirrors editor's basin-diagram.js placement logic. - const GAP = 20; - const labels = [ - { id: 'overflow', y: tyFor(y_overflow) }, - { id: 'highSafety', y: tyFor(y_highSafety) }, - { id: 'inflow', y: tyFor(y_inflow) }, - { id: 'dryRun', y: tyFor(y_dryRun) }, - { id: 'outflow', y: tyFor(y_outflow) }, - ].sort((a, b) => a.y - b.y); - for (let i = 1; i < labels.length; i++) { - if (labels[i].y < labels[i - 1].y + GAP) labels[i].y = labels[i - 1].y + GAP; + // 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 + // its line by default (16 px tall label + 6 px clear above the line). + // If two thresholds are too close together for both labels to fit ABOVE + // their lines (label of the lower one would cross the upper line), the + // lower one's label flips BELOW its line instead. With the current + // basin (dryRun=2% means dryRunLevel sits right on outflowLevel; high- + // Safety=98% puts it just under overflowLevel) this naturally puts + // highSafety BELOW and outflow BELOW. + const ABOVE_OFFSET = 22; // label_top = line_y - 22 (label bottom is 6 px clear above the line) + const BELOW_OFFSET = 6; // label_top = line_y + 6 (label is 6 px clear below the line) + const MIN_DIST_FOR_ABOVE = 24; // if distance to previous (upper) line < this, go below + const lines = [ + { id: 'overflow', line: y_overflow }, + { id: 'highSafety', line: y_highSafety }, + { id: 'inflow', line: y_inflow }, + { id: 'dryRun', line: y_dryRun }, + { id: 'outflow', line: y_outflow }, + ].sort((a, b) => a.line - b.line); + for (let i = 0; i < lines.length; i++) { + const prev = i > 0 ? lines[i - 1] : null; + const tooClose = prev && (lines[i].line - prev.line) < MIN_DIST_FOR_ABOVE; + lines[i].y = tooClose ? (lines[i].line + BELOW_OFFSET) : (lines[i].line - ABOVE_OFFSET); } - for (let i = labels.length - 2; i >= 0; i--) { - if (labels[i].y > labels[i + 1].y - GAP) labels[i].y = labels[i + 1].y - GAP; - } - const ty = Object.fromEntries(labels.map((l) => [l.id, +l.y.toFixed(2)])); + const ty = Object.fromEntries(lines.map((l) => [l.id, +l.y.toFixed(2)])); return { heightBasin: +heightBasin.toFixed(2),