fix(input): pan and box zoom through layout().axes, not the viewport

Every pixel-to-data conversion in the controller now maps through the
same rectangle Plot::x_transform/y_transform are built from:

- pan() derives the scale from layout().axes.w/.h, so a drag moves the
  grabbed data point by exactly the drag distance (the viewport model
  stranded it behind the pointer by the label-strip fraction).
- apply_box_zoom() clamps the selection to layout().axes and sets each
  range from the transforms (x_transform().to_data_x(l/r), y_transform()
  .to_data_y(bottom/top)); the selection's bottom edge is the y minimum.
- end_drag() takes the release event and refreshes the selection from its
  coordinates before applying, so a host that reports only press and
  release still frames the box instead of degrading it to a click.
- axis_zoom() uses a per-pixel sensitivity (200 px == one decade) rather
  than reusing the wheel's per-notch constant, so a pixel drag is no
  longer treated as a wheel notch.

Tests now assert the grab-and-drag invariant through the transform (the
old pixel-fraction assertions certified the viewport defect), add a
press+release-only box zoom, and pin the axis-zoom magnitude.
This commit is contained in:
Martino Ferrari committed 2026-09-27 22:41:42 +02:00
1 parent 5ab854104a
commit e0cdd95a3b
4 files changed
+106 -55

No files matched your search

+6 -4
View File
@@ -42,9 +42,11 @@ enum class DragMode {
/// | `+` / `-` | -- | zoom about the plot centre |
/// | `f` | -- | auto-fit every axis |
///
/// Both axis-zoom drags read the vertical `dy` with the wheel's sign convention
/// (dragging down zooms in), anchored at the pointer's data coordinate on the
/// axis they zoom.
/// Both axis-zoom drags read the vertical `dy` in pointer pixels with the
/// wheel's sign convention (dragging down zooms in), anchored at the pointer's
/// data coordinate on the axis they zoom. The drag has its own per-pixel
/// sensitivity -- a pixel is not a wheel notch -- of one decade per 200 px, so
/// a 200 px drag scales the span by 10.
///
/// A drag that changes no range is harmless: a box-zoom selection under 4 px on
/// either side is a click, and leaves the ranges alone.
@@ -70,7 +72,7 @@ class Controller {
private:
void begin_drag(const PointerEvent& e);
void update_drag(const PointerEvent& e);
void end_drag();
void end_drag(const PointerEvent& e);
void pan(const PointerEvent& e);
void box_zoom_update(const PointerEvent& e);