fix(render): map the visible range through the series' own x

Renderer::draw_traces picked the sample window with (x - x0()) / dx(), and dx() is
documented as a *nominal* spacing -- for a series whose x is a streamed timestamp it
is only the mean of the retained window. The window it computed therefore landed
inside the visible range, and the two end segments, the ones that enter and leave the
frame, were never emitted: the left and right margins of a zoomed-in view came out
empty. A wide enough drift dropped the trace entirely (last <= first).

It asks Series::neighbours() now -- a binary search over the real x, the same call the
crosshair reads through, costing the same. A uniform series answers the same question
through the same call, so it is one path. dx is still read for the samples-per-pixel
density estimate, where a nominal spacing is exactly right.

Test: a_sparse_window_between_samples_still_inks_both_margins -- a series whose dx()
lies, with the window strictly between two samples, so nothing inside the window is
sampled and only the crossing segments can ink it. With the grid walk reverted it
stages nothing at all (visible=0, gathered=0, covered=0/24); with the fix it stages the
three bracketing samples and covers 24/24 columns.
This commit is contained in:
Martino Ferrari committed 2026-10-03 14:20:11 +02:00
1 parent 2dcd1f1864
commit f3fb500068
3 files changed
+143 -17

No files matched your search

+5
View File
@@ -14,6 +14,11 @@ built on top of it.
| Qt6 | `plot::qt6` | `adapters/qt6/` |
| GTK4 | `plot::gtk4` | `adapters/gtk4/` |
The visible x range is mapped onto sample indices through the series' own x
(`Series::neighbours()`, a binary search over the real stamps), never through the
`(x - x0()) / dx()` grid: `dx()` is a *nominal* spacing, and a series whose x is a streamed
timestamp would otherwise have its draw window land inside the visible range and lose the two
segments that enter and leave the frame — the empty left and right margins of a zoomed-in view.
Per-frame rendering cost is O(visible pixels + samples produced since the last frame),
not O(samples): a trace denser than one sample per pixel is drawn from a min/max LOD
pyramid rather than from raw samples. The `plot_multiplot_demo` run stages **20,901 GPU