feat(plot): set_annotation(), so a readout annotation can be moved

A host whose annotation is a readout -- the point where a cursor crosses a
trace -- has to move it every frame, and `add_annotation()` only appends:
the id would change every frame and the annotation count grow without bound.
The setter keeps the id, ignores a stale one exactly as the cursor setters
do, and pairs with parking (`visible = false`) since there is no
`remove_annotation`.

Tested by `an_annotation_can_be_moved_by_id` in tests/test_plot.cpp.
This commit is contained in:
Martino Ferrari committed 2026-10-02 15:00:50 +02:00
1 parent e337c6a60c
commit 133cd680ce
3 files changed
+46

No files matched your search

+7
View File
@@ -229,6 +229,13 @@ class Plot {
/// Measures the interval between two cursors over the series of `trace`.
u32 add_measure(u32 cursor_a, u32 cursor_b, u32 trace = 0);
u32 add_annotation(const Annotation& annotation);
/// Moves an annotation that is already in the plot, keeping its id -- what a host whose
/// annotation is a *readout* needs (the point where a cursor crosses a trace moves with
/// the cursor). `Plot` has no `remove_annotation`, so the same parking discipline as the
/// cursors applies: set `visible = false` rather than dropping it, and re-place where it
/// belongs. An id that names no annotation is ignored, not an error, exactly as every
/// other setter here treats a stale id.
void set_annotation(u32 id, const Annotation& annotation);
/// Adds a region. Its bounds are ordered and, when the region asks to snap, pulled
/// onto the sample grid as it is added -- exactly as `add_cursor()` places a
/// snapping cursor -- so a region handed to a plot that already holds samples is on
+5
View File
@@ -306,6 +306,11 @@ u32 Plot::add_annotation(const Annotation& annotation) {
return static_cast<u32>(annotations_.size() - 1);
}
void Plot::set_annotation(u32 id, const Annotation& annotation) {
if (id >= annotations_.size()) return;
annotations_[id] = annotation;
}
u32 Plot::add_region(const Region& region) {
// A region whose bounds are not numbers is not an interval this plot can hold, and a
// `Free` one stored that way could never be dragged back (every motion re-derives both
+34
View File
@@ -2584,3 +2584,37 @@ PLOT_TEST(xy_series_measurement_uses_the_true_nearest_samples) {
PLOT_CHECK_NEAR(stats.min, 10.0, 1e-9);
PLOT_CHECK_NEAR(stats.max, 10.0, 1e-9);
}
// A host whose annotation is a readout -- the point where a cursor crosses a trace --
// moves it every frame. The id has to survive that, and a stale id must be ignored
// rather than read or written out of bounds, exactly as the cursors' setters behave.
PLOT_TEST(an_annotation_can_be_moved_by_id) {
plot::Plot p;
const plot::Annotation pin{1.0, 2.0, "first", plot::Color::hex(0xE8E8E8), 0u, 0u, true};
const u32 id = p.add_annotation(pin);
PLOT_CHECK(p.annotation(id).x == 1.0);
PLOT_CHECK(p.annotation(id).text == std::string("first"));
plot::Annotation moved = pin;
moved.x = 3.5;
moved.y = -1.25;
moved.text = "moved";
p.set_annotation(id, moved);
PLOT_CHECK_NEAR(p.annotation(id).x, 3.5, 1e-12);
PLOT_CHECK_NEAR(p.annotation(id).y, -1.25, 1e-12);
PLOT_CHECK(p.annotation(id).text == std::string("moved"));
PLOT_CHECK(p.annotation_count() == 1u); // moved, not added
// Parked rather than removed, like a cursor: the id stays valid and reads back hidden.
plot::Annotation parked = moved;
parked.visible = false;
p.set_annotation(id, parked);
PLOT_CHECK(!p.annotation(id).visible);
PLOT_CHECK(p.annotation_count() == 1u);
// A stale id changes nothing (and reads a default, as the accessors promise).
plot::Annotation other = pin;
other.x = 99.0;
p.set_annotation(42u, other);
PLOT_CHECK_NEAR(p.annotation(id).x, 3.5, 1e-12);
}