diff --git a/lib/microwaveprop_web/live/skewt_svg.ex b/lib/microwaveprop_web/live/skewt_svg.ex
index 72d44dca..8aca5362 100644
--- a/lib/microwaveprop_web/live/skewt_svg.ex
+++ b/lib/microwaveprop_web/live/skewt_svg.ex
@@ -12,8 +12,10 @@ defmodule MicrowavepropWeb.SkewtSvg do
"""
alias Microwaveprop.Weather.SkewtParams
- # Plot canvas (SVG user units).
- @width 720
+ # Plot canvas (SVG user units). The right margin (right edge of the
+ # plot box → SVG edge) is sized to fit the longest critical-level
+ # label rail entry, e.g. `LCL 925 mb`, without clipping.
+ @width 820
@height 640
@left 60
@right 660
@@ -142,26 +144,46 @@ defmodule MicrowavepropWeb.SkewtSvg do
# in-plot tick. Mirrors SPC's `143 mb`, `158 mb (mix)`, etc. rail.
defp critical_level_markers([]), do: ""
+ # Walk the levels in y order and bump any text whose baseline would
+ # land within @critical_label_min_dy of the previous one. The tick
+ # always sits at the level's true y; only the label gets nudged with
+ # a thin leader so close levels (e.g. LCL/LFC, 0 °C/WBZ) read cleanly.
defp critical_level_markers(levels) do
- Enum.flat_map(levels, fn
+ levels
+ |> Enum.flat_map(fn
%{label: label, pressure_mb: p} when is_number(p) ->
y = pressure_y(p)
-
- if y >= @top and y <= @bottom do
- [render_critical_marker(label, p, y)]
- else
- []
- end
+ if y >= @top and y <= @bottom, do: [{label, p, y}], else: []
_ ->
[]
end)
+ |> Enum.sort_by(fn {_lbl, _p, y} -> y end)
+ |> Enum.map_reduce(@top, fn {label, p, y}, last_text_y ->
+ text_y = max(y, last_text_y + @critical_label_min_dy)
+ iodata = render_critical_marker(label, p, y, text_y)
+ {iodata, text_y}
+ end)
+ |> elem(0)
end
- defp render_critical_marker(label, pressure_mb, y) do
+ # Minimum vertical gap between two stacked label baselines (px in
+ # SVG user units). 14 leaves the 10 px text plus a hair of breathing
+ # room — anything tighter starts overlapping descenders.
+ @critical_label_min_dy 14
+
+ defp render_critical_marker(label, pressure_mb, y, text_y) do
+ leader =
+ if abs(text_y - y) > 0.5 do
+ ~s||
+ else
+ ""
+ end
+
[
~s||,
- ~s|#{label} #{round(pressure_mb)} mb|
+ leader,
+ ~s|#{label} #{round(pressure_mb)} mb|
]
end
@@ -245,6 +267,7 @@ defmodule MicrowavepropWeb.SkewtSvg do
.virtual-temp-line { fill: none; stroke: #fb7185; stroke-width: 1; stroke-dasharray: 1 3; stroke-linejoin: round; stroke-linecap: round; }
.wet-bulb-line { fill: none; stroke: #2563eb; stroke-width: 1.4; stroke-linejoin: round; stroke-linecap: round; opacity: 0.85; }
.critical-tick { stroke: currentColor; stroke-width: 1.2; }
+ .critical-leader { stroke: currentColor; stroke-opacity: 0.45; stroke-width: 0.8; }
.critical-label { font: 10px ui-sans-serif, system-ui, sans-serif; fill: currentColor; fill-opacity: 0.85; }
.skewt-cursor-line { stroke: currentColor; stroke-opacity: 0.5; stroke-width: 0.8; stroke-dasharray: 3 3; pointer-events: none; }
.skewt-cursor-dot-t { fill: #ff0000; pointer-events: none; }