diff --git a/CLAUDE.md b/CLAUDE.md index 67de04b..a5926ea 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -756,12 +756,27 @@ far the largest file. average* over `time_stride` samples, so a number between two stored samples is a second estimate laid over the first that nothing on screen or in an export holds; and past the sample spacing the polyline is a line the renderer drew, so a point on it reports the drawing. The - change is in `deps/plotter` and unconditional — no setting, because the library had no business - offering a readout the data does not contain. `CrosshairRow::marker` is placed from the same y, - so the dot lands on the quoted sample and **sits beside the polyline at a deep zoom, by - design**: one y feeding both is what keeps the mark and the number from disagreeing. The guards - are the library's `crosshair_value_is_the_nearest_sample` and `[cursors-probe]`'s `first mark at - x=.. y=..` agreeing with the pane's first value. + change is in `deps/plotter`, which keeps the blend as `CrosshairValue::Interpolated` for a host + whose x is a quantity it is willing to interpolate — the nearest sample is the **default** and + **udpscope never states the other**, so a `set_crosshair_value_mode()` call appearing here is a + regression, not a feature. + **Both the mark and the number come from one call, `Plot::crosshair_point()`**, which returns + the quoted sample's own **x** as well as its y — so every mark this app draws for a readout is + *snapped to the sample it quotes*. The library's `CrosshairRow::marker` and the cursors pane's + `mix` crossing annotations (`place_crossings()`, which must use `crosshair_point()` and not + `crosshair_value()`) are both placed from it. Two consequences that look like defects and are + not: the mark **sits beside the polyline at a deep zoom** (one y feeding both the number and the + dot is what keeps them from disagreeing), and a crossing dot **sits a little off its own + vertical cursor line** — up to half a sample period, invisible at a whole-window view and the + width of the panel at a few samples across, which is the only zoom at which anyone is asking + which point it is. The line says where the *marker* is; the dot says which *datum* is quoted. + The hover mark is also deliberately *larger* than a trace's own sample dots + (`Style::crosshair_marker_px` 9 against `marker_px` 3), because a snapped mark lands among them + and at their size reads as one more sample. The guards are the library's + `crosshair_value_is_the_nearest_sample`, `the_crosshair_marker_snaps_to_the_sample_it_reports`, + `crosshair_interpolated_mode_blends_the_two_samples` and + `the_crosshair_mark_is_bigger_than_a_sample_dot`, plus `[cursors-probe]`'s `first mark at x=.. + y=..` agreeing with the pane's first value. - **The cursors are read back from the plots only while a gesture is on that row.** `Cursor::x` is the *free* pointer position while a drag runs and the snapped value otherwise -- which is what the app itself wrote a frame earlier, or the snap of it. A tick that reads a row on diff --git a/README.md b/README.md index eb2a9a2..83cea19 100644 --- a/README.md +++ b/README.md @@ -1153,10 +1153,25 @@ second, undeclared estimate laid over the first, and nothing on screen or in an holds it. And at a zoom past the sample spacing it is visibly not the datum the pointer is over: the polyline between two samples is a straight line the renderer drew, and quoting a point on it reports the drawing rather than the data. A tie reads the earlier -sample. The fix is upstream and unconditional (`deps/plotter`, `crosshair_span()`'s `t` -is now a selector rather than a blend factor), so the library's own hover crosshair -changed with the pane — which is the point, since both are the same lookup and a user -comparing them must not see two numbers. +sample. The fix is upstream (`deps/plotter`, `crosshair_span()`'s `t` is a selector rather +than a blend factor), so the library's own hover crosshair changed with the pane — which is +the point, since both are the same lookup and a user comparing them must not see two +numbers. Upstream keeps the blend as `CrosshairValue::Interpolated` for hosts whose x is a +quantity they are willing to interpolate; the nearest sample is the default and udpscope +never states the other. + +**The crossing dot is on the sample, not on the marker's column.** The dot is placed +through `Plot::crosshair_point()` — the same lookup as the value, returning the quoted +sample's own x as well as its y — so the mark the eye follows and the number in the pane +are the same datum. The two x's differ by up to half a sample period: nothing at a +whole-window view, and the whole width of the panel once a few samples span the axes, which +is exactly the state in which a reader is asking "which point is that?". It therefore leaves +the dot a little off its own vertical line by design — the line says where the *marker* is +and the dot says which *datum* is being quoted, and when the marker is between two samples +those are two places. The library's hover crosshair snaps the same way and draws its mark +`Style::crosshair_marker_px` across (9 logical px against a trace's own 3 px sample dots), +since a snapped mark lands among those dots and at their size would read as one more sample +instead of as the answer to the question the hover asks. The readouts are a **table**: a header naming the columns, then one row per instrument — `| who | A | B | delta |`. The first row is the two times and their difference, and the diff --git a/deps/plotter b/deps/plotter index c028f48..c960dec 160000 --- a/deps/plotter +++ b/deps/plotter @@ -1 +1 @@ -Subproject commit c028f48fa0e2499e3e0774b00297297b028378a5 +Subproject commit c960decaea6ec1ba00abd1d4d10c2a29c1d0a511 diff --git a/src/help/09-cursors.txt b/src/help/09-cursors.txt index e7960ef..ea706e1 100644 --- a/src/help/09-cursors.txt +++ b/src/help/09-cursors.txt @@ -25,7 +25,10 @@ owns a Reset button. Drag a marker to move it. The mode dropdown offers f • A marker past the newest sample reads --. With only one marker over data the row reads partial: a difference between a number and nothing is not a number. • In mix each marker also places a crossing annotation per trace, which is what lets - you see on the plot which sample a number came from. + you see on the plot which sample a number came from. It is on that sample, so when the + marker falls between two it sits a little off its own line: the line is where you put the + marker, the dot is the datum being quoted. The hover crosshair's mark snaps the same way, and + is drawn larger than a trace's sample dots so it does not read as one more sample. • The time row exists only in the modes that show the vertical pair. horizontal has no time row, by design. diff --git a/src/plot_host.cpp b/src/plot_host.cpp index fc878aa..b5040bf 100644 --- a/src/plot_host.cpp +++ b/src/plot_host.cpp @@ -396,18 +396,30 @@ void RowSet::place_crossings() { if (ida >= r.plot->annotation_count() || idb >= r.plot->annotation_count()) continue; plot::Annotation a = r.plot->annotation(ida); plot::Annotation b = r.plot->annotation(idb); + double xa = 0.0; double ya = 0.0; + double xb = 0.0; double yb = 0.0; - const bool has_a = mix && r.plot->crosshair_value(static_cast(s), cursor_a_x_, ya); - const bool has_b = mix && r.plot->crosshair_value(static_cast(s), cursor_b_x_, yb); + // `crosshair_point()`, not `crosshair_value()`: the number beside this dot is a *stored + // sample's*, so the dot belongs on that sample and not on the marker's own column. The + // two differ by up to half a sample period, which is nothing at a whole-window view and + // the whole width of the panel once the view is zoomed to a handful of samples -- the + // state in which a reader is asking "which point is that?" in the first place. It leaves + // the dot a little off its vertical line by design: the line says *where the marker is* + // and the dot says *which datum is being quoted*, and when the marker is between two + // samples those are not the same place. + const bool has_a = + mix && r.plot->crosshair_point(static_cast(s), cursor_a_x_, xa, ya); + const bool has_b = + mix && r.plot->crosshair_point(static_cast(s), cursor_b_x_, xb, yb); a.visible = has_a; b.visible = has_b; if (has_a) { - a.x = cursor_a_x_; + a.x = xa; a.y = ya; } if (has_b) { - b.x = cursor_b_x_; + b.x = xb; b.y = yb; } r.plot->set_annotation(ida, a);