added auto measuraments
This commit is contained in:
1 parent
44fc4cdf36
commit
0eceaf537b
7 files changed
+2182
-55
No files matched your search
+227
-24
@@ -26,6 +26,10 @@ constexpr plot::u32 kUnplottedColor = 0x6E6E6Eu;
|
||||
constexpr plot::u32 kCursorAColor = 0xFFD24Fu;
|
||||
constexpr plot::u32 kCursorBColor = 0x7FE0FFu;
|
||||
constexpr plot::u32 kRulerColor = 0xB0B0B0u;
|
||||
constexpr plot::u32 kRulerBColor = 0x8FA8C8u;
|
||||
/// The measurements' own colour, distinct from both cursor rulers so a readout mark cannot be
|
||||
/// mistaken for something the user placed.
|
||||
constexpr plot::u32 kMeasureColor = 0x9BE8A0u;
|
||||
constexpr plot::u32 kTriggerColor = 0xFF5B5Bu;
|
||||
|
||||
constexpr double kParked = -1.0e12; ///< hides an instrument without removing it
|
||||
@@ -59,10 +63,9 @@ void RowSet::carry_row(Row& dst, const Row& src) {
|
||||
dst.name = src.name;
|
||||
dst.channel = src.channel;
|
||||
dst.color_index = src.color_index;
|
||||
dst.cursors_visible = src.cursors_visible;
|
||||
dst.cursor_a_x = src.cursor_a_x;
|
||||
dst.cursor_b_x = src.cursor_b_x;
|
||||
|
||||
dst.ruler_y = src.ruler_y;
|
||||
dst.ruler_b_y = src.ruler_b_y;
|
||||
// The slot's series stays where it is, but what it is bound to belonged to the row
|
||||
// this state came *from*. `bind()` re-resolves every slot next frame; clearing it here
|
||||
// is what stops the frame in between from drawing the moved row's data under the new
|
||||
@@ -75,9 +78,6 @@ void RowSet::clear_row(Row& r) {
|
||||
r.name = {};
|
||||
r.channel = {-1, -1, -1, -1};
|
||||
r.color_index = {0, 1, 2, 3};
|
||||
r.cursors_visible = false;
|
||||
r.cursor_a_x = 0.0;
|
||||
r.cursor_b_x = 0.0;
|
||||
r.ruler_y = 0.0;
|
||||
for (SlotSeries& s : r.slots) s.bind(nullptr);
|
||||
}
|
||||
@@ -115,6 +115,30 @@ void RowSet::install(Row& r, int /*row_index*/, plot::Plot& p) {
|
||||
r.cursor_a = r.plot->add_cursor(0.0, plot::Color::hex(kCursorAColor));
|
||||
r.cursor_b = r.plot->add_cursor(0.0, plot::Color::hex(kCursorBColor));
|
||||
r.ruler = r.plot->add_h_ruler(0.0, plot::Color::hex(kRulerColor));
|
||||
r.ruler_b = r.plot->add_h_ruler(0.0, plot::Color::hex(kRulerBColor));
|
||||
// The `mix` crossings, one pair per trace slot, hidden until that mode is on. Created
|
||||
// here because a `Plot` fixes its annotation set the same way it fixes its traces --
|
||||
// `add_annotation()` only appends -- so the markers are made up front and moved.
|
||||
for (int s = 0; s < kSlotsPerRow; ++s) {
|
||||
plot::Annotation a{0.0, 0.0, "", plot::Color::hex(kCursorAColor), r.axis_x, r.axis_y, false};
|
||||
plot::Annotation b{0.0, 0.0, "", plot::Color::hex(kCursorBColor), r.axis_x, r.axis_y, false};
|
||||
r.crossing_a[static_cast<std::size_t>(s)] = r.plot->add_annotation(a);
|
||||
r.crossing_b[static_cast<std::size_t>(s)] = r.plot->add_annotation(b);
|
||||
}
|
||||
// The measurements' readout marks: a fixed pool, parked. Levels are rulers (they read a y)
|
||||
// and times are markers (they read an x), so the pool has one of each per slot.
|
||||
for (int m = 0; m < kMeasureMarks; ++m) {
|
||||
const std::size_t mi = static_cast<std::size_t>(m);
|
||||
r.measure_h[mi] = r.plot->add_h_ruler(kParked, plot::Color::hex(kMeasureColor));
|
||||
r.measure_v[mi] = r.plot->add_cursor(kParked, plot::Color::hex(kMeasureColor));
|
||||
// Readouts, not instruments: the pointer must not be able to hover or grab one. Parking
|
||||
// them outside the axis and re-placing every frame does not stop a *drag* -- the
|
||||
// controller holds the marker for the gesture's life, so it follows the pointer between
|
||||
// frames and the readout looks draggable. Excluding it from the hit test is the only
|
||||
// place that can say no.
|
||||
r.plot->set_cursor_selectable(r.measure_h[mi], false);
|
||||
r.plot->set_cursor_selectable(r.measure_v[mi], false);
|
||||
}
|
||||
r.trigger_marker = r.plot->add_cursor(kParked, plot::Color::hex(kTriggerColor));
|
||||
// A marker, not an instrument: it must sit exactly on the edge's time, so its
|
||||
// snap is Free and the controller never rounds it onto a sample.
|
||||
@@ -303,13 +327,190 @@ void RowSet::set_y_range(int row_index, double lo, double hi) {
|
||||
|
||||
void RowSet::place_cursors(Row& r) {
|
||||
if (!r.plot) return;
|
||||
const bool show = r.cursors_visible;
|
||||
const double a = show ? r.cursor_a_x : kParked;
|
||||
const double b = show ? r.cursor_b_x : kParked;
|
||||
const double h = show ? r.ruler_y : kParked;
|
||||
r.plot->set_cursor_x(r.cursor_a, a);
|
||||
r.plot->set_cursor_x(r.cursor_b, b);
|
||||
r.plot->set_cursor_y(r.ruler, h);
|
||||
const bool on = cursors_on_;
|
||||
// Which instruments the mode uses; the rest are parked (a `Plot` cannot drop a
|
||||
// cursor, so hiding is the only way to take one off screen).
|
||||
const bool vertical = on && cursor_mode_ != CursorMode::Horizontal;
|
||||
const bool horizontal = on && (cursor_mode_ == CursorMode::Horizontal ||
|
||||
cursor_mode_ == CursorMode::Both);
|
||||
r.plot->set_cursor_x(r.cursor_a, vertical ? cursor_a_x_ : kParked);
|
||||
r.plot->set_cursor_x(r.cursor_b, vertical ? cursor_b_x_ : kParked);
|
||||
r.plot->set_cursor_y(r.ruler, horizontal ? r.ruler_y : kParked);
|
||||
r.plot->set_cursor_y(r.ruler_b, horizontal ? r.ruler_b_y : kParked);
|
||||
}
|
||||
|
||||
void RowSet::set_cursor_mode(CursorMode mode) {
|
||||
if (mode == cursor_mode_) return;
|
||||
cursor_mode_ = mode;
|
||||
for (int i = 0; i < kMaxRows; ++i) place_cursors(row(i));
|
||||
place_crossings();
|
||||
}
|
||||
|
||||
void RowSet::place_crossings() {
|
||||
const bool mix = cursor_mode_ == CursorMode::Mix && cursors_visible();
|
||||
for (int i = 0; i < kMaxRows; ++i) {
|
||||
Row& r = row(i);
|
||||
if (!r.plot) continue;
|
||||
for (int s = 0; s < kSlotsPerRow; ++s) {
|
||||
const std::size_t si = static_cast<std::size_t>(s);
|
||||
const plot::u32 ida = r.crossing_a[si];
|
||||
const plot::u32 idb = r.crossing_b[si];
|
||||
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 ya = 0.0;
|
||||
double yb = 0.0;
|
||||
const bool has_a = mix && r.plot->crosshair_value(static_cast<plot::u32>(s), cursor_a_x_, ya);
|
||||
const bool has_b = mix && r.plot->crosshair_value(static_cast<plot::u32>(s), cursor_b_x_, yb);
|
||||
a.visible = has_a;
|
||||
b.visible = has_b;
|
||||
if (has_a) {
|
||||
a.x = cursor_a_x_;
|
||||
a.y = ya;
|
||||
}
|
||||
if (has_b) {
|
||||
b.x = cursor_b_x_;
|
||||
b.y = yb;
|
||||
}
|
||||
r.plot->set_annotation(ida, a);
|
||||
r.plot->set_annotation(idb, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double RowSet::ruler_y(int r) const {
|
||||
return r >= 0 && r < rows_ ? row(r).ruler_y : 0.0;
|
||||
}
|
||||
|
||||
double RowSet::ruler_b_y(int r) const {
|
||||
return r >= 0 && r < rows_ ? row(r).ruler_b_y : 0.0;
|
||||
}
|
||||
|
||||
int RowSet::crossings_visible() const {
|
||||
int n = 0;
|
||||
for (int i = 0; i < rows_; ++i) {
|
||||
const Row& r = row(i);
|
||||
if (!r.plot) continue;
|
||||
for (int s = 0; s < kSlotsPerRow; ++s) {
|
||||
const std::size_t si = static_cast<std::size_t>(s);
|
||||
if (r.crossing_a[si] < r.plot->annotation_count() &&
|
||||
r.plot->annotation(r.crossing_a[si]).visible) {
|
||||
++n;
|
||||
}
|
||||
if (r.crossing_b[si] < r.plot->annotation_count() &&
|
||||
r.plot->annotation(r.crossing_b[si]).visible) {
|
||||
++n;
|
||||
}
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
bool RowSet::crossing_at(int i, int slot, bool second, double& x, double& y) const {
|
||||
if (i < 0 || i >= rows_ || slot < 0 || slot >= kSlotsPerRow) return false;
|
||||
const Row& r = row(i);
|
||||
if (!r.plot) return false;
|
||||
const std::size_t si = static_cast<std::size_t>(slot);
|
||||
const plot::u32 id = second ? r.crossing_b[si] : r.crossing_a[si];
|
||||
if (id >= r.plot->annotation_count()) return false;
|
||||
const plot::Annotation& a = r.plot->annotation(id);
|
||||
if (!a.visible) return false;
|
||||
x = a.x;
|
||||
y = a.y;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RowSet::set_measure_marks(const std::vector<MeasureMark>& marks) {
|
||||
measure_marks_placed_ = static_cast<int>(marks.size());
|
||||
for (int i = 0; i < kMaxRows; ++i) {
|
||||
Row& r = row(i);
|
||||
if (!r.plot) continue;
|
||||
int h = 0;
|
||||
int v = 0;
|
||||
for (const MeasureMark& m : marks) {
|
||||
if (m.row != i) continue;
|
||||
if (m.level) {
|
||||
if (h >= kMeasureMarks) continue;
|
||||
const std::size_t mi = static_cast<std::size_t>(h++);
|
||||
r.plot->set_cursor_y(r.measure_h[mi], m.value, plot::CursorPlacement::Free);
|
||||
r.plot->set_cursor_color(r.measure_h[mi], plot::Color::hex(m.color));
|
||||
} else {
|
||||
if (v >= kMeasureMarks) continue;
|
||||
const std::size_t mi = static_cast<std::size_t>(v++);
|
||||
r.plot->set_cursor_x(r.measure_v[mi], m.value, plot::CursorPlacement::Free);
|
||||
r.plot->set_cursor_color(r.measure_v[mi], plot::Color::hex(m.color));
|
||||
}
|
||||
}
|
||||
// What the measurements left over is parked rather than kept: a stale readout at the old
|
||||
// window's position is worse than none.
|
||||
for (; h < kMeasureMarks; ++h) {
|
||||
r.plot->set_cursor_y(r.measure_h[static_cast<std::size_t>(h)], kParked);
|
||||
}
|
||||
for (; v < kMeasureMarks; ++v) {
|
||||
r.plot->set_cursor_x(r.measure_v[static_cast<std::size_t>(v)], kParked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RowSet::fit_x() {
|
||||
Row& first = row(0);
|
||||
if (!first.plot || !figure_) return;
|
||||
first.plot->auto_fit_x();
|
||||
const plot::Range fitted = first.plot->x_range(first.axis_x);
|
||||
const double span = fitted.span();
|
||||
if (!std::isfinite(span) || !(span > 0.0)) return;
|
||||
// The same two paths `fit_all()` takes: a held event owns its window and states it
|
||||
// through the link group, a live view re-arms the follow and frames the newest span.
|
||||
if (pinned_) {
|
||||
window_s_ = span;
|
||||
figure_->set_x_link_range(x_group_, fitted);
|
||||
} else {
|
||||
set_time_window(span);
|
||||
}
|
||||
}
|
||||
|
||||
void RowSet::sync_cursors() {
|
||||
if (!cursors_visible()) return;
|
||||
// Only a mode that *shows* the vertical pair reads them back. With them parked (the
|
||||
// `horizontal` mode) whatever the plot reports is our own park coming back -- and a
|
||||
// snapped cursor parked far outside the axis reads back as something finite, which
|
||||
// would be adopted as a gesture and destroy the shared times.
|
||||
// Anything within this much of what we last pushed is our own placement coming back
|
||||
// (the plot stores f64, so an exact compare would be brittle across a rebuild).
|
||||
const auto moved = [](double a, double b) {
|
||||
return std::fabs(a - b) > 1.0e-9 * std::max(1.0, std::fabs(b));
|
||||
};
|
||||
bool push = false;
|
||||
for (int i = 0; i < rows_; ++i) {
|
||||
Row& r = row(i);
|
||||
if (!r.plot) continue;
|
||||
// A gesture writes the *plot*; this is the only place its result is visible. A
|
||||
// ruler is its own row's value -- nothing to share, but it still has to be read
|
||||
// back or the pane would keep showing wherever the app last put it.
|
||||
const bool rulers = cursor_mode_ == CursorMode::Horizontal || cursor_mode_ == CursorMode::Both;
|
||||
if (rulers) {
|
||||
const double y = r.plot->cursor(r.ruler).y;
|
||||
const double yb = r.plot->cursor(r.ruler_b).y;
|
||||
if (y > kParked * 0.5) r.ruler_y = y;
|
||||
if (yb > kParked * 0.5) r.ruler_b_y = yb;
|
||||
}
|
||||
if (cursor_mode_ == CursorMode::Horizontal) continue; // the pair is parked: not a value
|
||||
const double a = r.plot->cursor(r.cursor_a).x;
|
||||
const double b = r.plot->cursor(r.cursor_b).x;
|
||||
if (a < kParked * 0.5 || b < kParked * 0.5) continue; // parked: not a value
|
||||
if (moved(a, cursor_a_x_)) {
|
||||
cursor_a_x_ = a;
|
||||
push = true;
|
||||
}
|
||||
if (moved(b, cursor_b_x_)) {
|
||||
cursor_b_x_ = b;
|
||||
push = true;
|
||||
}
|
||||
}
|
||||
if (push) {
|
||||
for (int i = 0; i < rows_; ++i) place_cursors(row(i));
|
||||
}
|
||||
place_crossings();
|
||||
}
|
||||
|
||||
void RowSet::clamp_to_data() {
|
||||
@@ -442,11 +643,8 @@ void RowSet::set_trigger_marker(double x, bool visible) {
|
||||
}
|
||||
|
||||
void RowSet::set_cursors_visible(bool on) {
|
||||
for (int i = 0; i < kMaxRows; ++i) {
|
||||
Row& r = row(i);
|
||||
r.cursors_visible = on;
|
||||
place_cursors(r);
|
||||
}
|
||||
cursors_on_ = on;
|
||||
for (int i = 0; i < kMaxRows; ++i) place_cursors(row(i));
|
||||
}
|
||||
|
||||
void RowSet::reset_cursors() {
|
||||
@@ -455,13 +653,18 @@ void RowSet::reset_cursors() {
|
||||
if (!r.plot) continue;
|
||||
const plot::Range xr = r.plot->x_range(r.axis_x);
|
||||
const double span = xr.span() > 0.0 ? xr.span() : window_s_;
|
||||
r.cursor_a_x = xr.min + 0.25 * span;
|
||||
r.cursor_b_x = xr.min + 0.75 * span;
|
||||
r.ruler_y = r.plot->y_range(r.axis_y).min +
|
||||
0.5 * (r.plot->y_range(r.axis_y).span() > 0.0 ? r.plot->y_range(r.axis_y).span()
|
||||
: 1.0);
|
||||
place_cursors(r);
|
||||
cursor_a_x_ = xr.min + 0.25 * span;
|
||||
cursor_b_x_ = xr.min + 0.75 * span;
|
||||
const double y_lo = r.plot->y_range(r.axis_y).min;
|
||||
const double y_span = r.plot->y_range(r.axis_y).span();
|
||||
r.ruler_y = y_lo + 0.35 * (y_span > 0.0 ? y_span : 1.0);
|
||||
r.ruler_b_y = y_lo + 0.65 * (y_span > 0.0 ? y_span : 1.0);
|
||||
}
|
||||
// Every row's instruments are written from what was just set. Without this the plots
|
||||
// keep their build-time cursors (x = 0) and `sync_cursors()` -- which reads the plots
|
||||
// because a *gesture* writes them -- adopts that zero as a moved cursor.
|
||||
for (int i = 0; i < kMaxRows; ++i) place_cursors(row(i));
|
||||
place_crossings();
|
||||
}
|
||||
|
||||
} // namespace udpscope
|
||||
Reference in new issue
Block a user