fix(render): keep a cursor parked on an axis bound inside and visible

This commit is contained in:
Martino Ferrari committed 2026-09-28 17:49:18 +02:00
1 parent eb7371d7a5
commit 93ad774281
3 files changed
+84 -18

No files matched your search

+27 -11
View File
@@ -941,28 +941,44 @@ void Renderer::draw_cursors(const Plot& plot, const PlotLayout& layout) {
// text path -- is this one loop's, which is the point of one type rather than two.
const bool horizontal = cursor.orientation == CursorOrientation::Horizontal;
const f32 at = plot.cursor_screen_coordinate(i);
// A cursor at the *far* edge of its own range is off the plot rather than on it: its
// stroke straddles that edge by construction, so drawing it would put half a marker
// over the label strip beside it. The rule reads the same on both axes -- a marker
// scrolled past the right of its x range, a ruler dragged to the bottom of its y range
// -- and the axes clip would hold the ink in anyway, so this is about not drawing
// something that is a half-stroke and a half-nothing.
if (horizontal ? (at < layout.axes.y || at >= layout.axes.bottom())
: (at < layout.axes.x || at >= layout.axes.right())) {
// A cursor has no position on the plot when it is strictly outside its range's mapped
// span -- a marker scrolled past the right edge, a ruler past the bottom -- and the
// tolerance is the stroke's own half-width: a coordinate *at* a bound is on the edge and
// belongs to the plot, and `to_screen_y()` of a range maximum can undershoot `axes.y` by
// a floating-point ulp, which a bare `<` would mistake for "outside". Only the ones
// beyond that margin are skipped.
const f32 half = 0.5f * style_.device(on ? style_.handle_width_px : style_.line_width_px);
if (horizontal ? (at < layout.axes.y - half || at > layout.axes.bottom() + half)
: (at < layout.axes.x - half || at > layout.axes.right() + half)) {
continue;
}
// The stroke is centred on the coordinate, so a cursor on a bound would paint only the
// half of itself inside `axes` -- and where the edge is fractional the clip's inward
// rounding can leave that half a sub-pixel sliver or nothing at all, because the
// rasteriser keeps a fragment only when its centre lies within the stroke. Clamp the
// *drawn* centre by half the stroke so a marker parked on an edge stays visible at it;
// the cursor's coordinate, the hit test and its label's value are untouched.
const f32 lo_v = layout.axes.x + half;
const f32 hi_v = layout.axes.right() - half;
const f32 lo_h = layout.axes.y + half;
const f32 hi_h = layout.axes.bottom() - half;
const f32 drawn =
horizontal ? std::clamp(at, std::min(lo_h, hi_h), std::max(lo_h, hi_h))
: std::clamp(at, std::min(lo_v, hi_v), std::max(lo_v, hi_v));
if (horizontal) {
stage_segment(x_range.min, cursor.y, x_range.max, cursor.y, log_x,
const f64 y_drawn = plot.y_transform(cursor.axis_y).to_data_y(static_cast<f64>(drawn));
stage_segment(x_range.min, y_drawn, x_range.max, y_drawn, log_x,
plot.y_axis(cursor.axis_y).log);
} else {
stage_segment(cursor.x, y_range.min, cursor.x, y_range.max, plot.x_axis(cursor.axis_x).log,
const f64 x_drawn = plot.x_transform(cursor.axis_x).to_data_x(static_cast<f64>(drawn));
stage_segment(x_drawn, y_range.min, x_drawn, y_range.max, plot.x_axis(cursor.axis_x).log,
log_y);
}
flush_lines(horizontal ? mapping(plot, 0, cursor.axis_y) : mapping(plot, cursor.axis_x, 0),
on ? style_.cursor : cursor.color,
style_.device(on ? style_.handle_width_px : style_.line_width_px));
draw_cursor_label(plot, layout, cursor, at);
draw_cursor_label(plot, layout, cursor, drawn);
}
}