josie / alder-tools

Graphs: pad trace 1 sub-row clear of box borders

- Usable sub-row band is now [1, sub_h-2] (1 sub-row padding top and
  bottom) so the trace stays visually clear of the box borders instead
  of sitting flush against them
- marker_row (vCore limit) rescaled into the same usable band so the
  limit line stays in the same band as the trace
- 15 tests, clippy clean; pty-verified 50x100, 30x80

52f00c5b233cce4e721ffdb2b88888b037f3d704
josie <administrator@josie-c.com> · 2026-09-01T23:11 · browse files at this commit

parents: be5b81e

diff --git a/src/plot.rs b/src/plot.rs
index 78004f7..314c81c 100644
--- a/src/plot.rs
+++ b/src/plot.rs
@@ -158,7 +158,12 @@ pub fn render(
     }
     let rows = area.height as usize;
     let cols = area.width as usize;
+    // Reserve 1 sub-row at the top and bottom so the trace stays visually
+    // clear of the box borders (a dot at sub-row 0 or sub_h-1 would sit
+    // flush against the border and read as touching/overlapping it).
+    // Usable sub-rows: 1..(sub_h-1), mapped over [min,max].
     let sub_h = rows * 4;
+    let usable_h = sub_h.saturating_sub(2).max(1);
     let sub_w = cols * 2;
     let span = max - min;
 
@@ -169,12 +174,15 @@ pub fn render(
     }
     let col_vals = map_to_subcols(&samples, sub_w);
 
-    // Sub-row (0=top=max) for a value. No hysteresis — the 4-level sub-row
-    // quantization is the noise floor (sub-row jitter maps to the same
-    // sub-row and doesn't move the dot).
+    // Sub-row (0=top=max) for a value, mapped into the usable band
+    // [1, sub_h-1) so the trace stays 1 sub-row clear of both borders.
+    // No hysteresis — the 4-level sub-row quantization is the noise floor
+    // (sub-row jitter maps to the same sub-row and doesn't move the dot).
     let to_sub_row = |v: f64| -> usize {
-        let cont = (1.0 - (v - min) / span).clamp(0.0, 1.0) * (sub_h - 1) as f64;
-        cont.round() as usize
+        // Map [min,max] → [1, sub_h-2] (usable band, 1 sub-row clear of
+        // each border).
+        let cont = (1.0 - (v - min) / span).clamp(0.0, 1.0) * (usable_h - 1) as f64;
+        1 + cont.round() as usize
     };
 
     // Accumulate braille bits per cell. cells[row][col] = u8; 0 = blank.
@@ -219,7 +227,17 @@ pub fn render(
     }
 
     // Marker row: red '┄' on blank cells (first-blank-wins, as before).
-    let marker_cell_row = marker_row.map(|m| (m as usize).min(rows - 1));
+    // Marker row: red '┄' on blank cells. Map the caller's [0, rows-1]
+    // cell-row into the usable band [0, usable_h-1] sub-rows, then convert
+    // to a cell row so the marker stays in the same band as the trace.
+    let marker_cell_row = marker_row.map(|m| {
+        // m is in [0, rows-1] (caller's cell-row index). Rescale into the
+        // usable sub-row band and convert back to a cell row.
+        let sub = (m as f64 / (rows - 1).max(1) as f64) * (usable_h - 1) as f64;
+        let sub = sub.round() as usize;
+        let padded = 1 + sub; // back into full sub_h coordinates
+        (padded / 4).min(rows - 1)
+    });
 
     let st = Style::default().fg(color);
     let marker_st = Style::default().fg(Color::Red);