feat: a measurement accumulates how it varies, so one reading can be told from a trend
This commit is contained in:
1 parent
d79810c7ba
commit
1a8218887c
2 files changed
+79
-8
No files matched your search
+36
-8
@@ -269,6 +269,11 @@ struct App {
|
||||
/// of the signal and survives the window it was measured in, so the rulers do not blink off when
|
||||
/// a pass lands on a window with no samples (a rebuild, a capture boundary, a mode change).
|
||||
std::vector<MeasureResult> measure_last_valid;
|
||||
/// Running statistics, one per entry of `measures` and reset whenever that vector is
|
||||
/// rebuilt. Not reset by a pause, a latch or a pan: those change what is measured *now*,
|
||||
/// not what the measurement means, and a statistic the user cannot keep across them is
|
||||
/// not worth having.
|
||||
std::vector<MeasureStatsAccum> measure_stats;
|
||||
/// The marks the last computation produced: re-placed every frame, which is what makes the
|
||||
/// measurement cursors read-only.
|
||||
///
|
||||
@@ -2179,6 +2184,13 @@ void sync_window_display(App& app, double seconds) {
|
||||
app.updating_panels = false;
|
||||
}
|
||||
|
||||
/// Clears every measurement's statistics. Called explicitly by the pane's Reset, and
|
||||
/// implicitly wherever the series of results stops being one series: a measurement edit
|
||||
/// (`rebuild_measures_pane()`), a change of display window, and a CONFIG.
|
||||
void reset_measure_stats(App& app) {
|
||||
for (MeasureStatsAccum& s : app.measure_stats) s.reset();
|
||||
}
|
||||
|
||||
/// The one place the time scale changes. Clamps to what the buffer can hold, moves the plot and
|
||||
/// the trigger's event span together -- its pre-trigger share is a share of *this* window -- and
|
||||
/// then restates the result in the control, so a clamped request reads back as the window that
|
||||
@@ -2209,6 +2221,9 @@ void apply_time_window(App& app, double seconds) {
|
||||
// The partition before the axis, so the axis is framed over the rings that will exist.
|
||||
app.rows->set_time_window(clamped);
|
||||
sync_window_display(app, clamped);
|
||||
// A measurement over a different window is a different figure -- a mean over 20 ms and a
|
||||
// mean over 2 s are not observations of one quantity -- so the history behind it goes.
|
||||
reset_measure_stats(app);
|
||||
schedule_layout_save(app);
|
||||
}
|
||||
|
||||
@@ -2659,6 +2674,9 @@ void rebuild_measures_pane(App& app) {
|
||||
}
|
||||
app.measure_labels.clear();
|
||||
app.measure_text.clear();
|
||||
// Editing a measurement changes what the figures *mean*, so the history behind them is no
|
||||
// longer one series. This is the implicit reset for an add, an edit and a delete alike.
|
||||
app.measure_stats.assign(app.measures.size(), MeasureStatsAccum{});
|
||||
if (app.measures.empty()) {
|
||||
GtkWidget* empty = gtk_label_new("no measurements -- [+] to add one");
|
||||
gtk_widget_add_css_class(empty, "dim-label");
|
||||
@@ -3422,16 +3440,17 @@ void update_measures(App& app, bool force) {
|
||||
const MeasureResult r = compute_measure(w.stats, level, m.type, m.options);
|
||||
app.measure_results[i] = r;
|
||||
if (r.valid) app.measure_last_valid[i] = r;
|
||||
// Time-valued types carry seconds and a frequency carries hertz whatever the signal's
|
||||
// own unit is; everything else carries that unit. Hoisted out of the valid branch below
|
||||
// because the statistics line needs the same unit.
|
||||
const bool is_time =
|
||||
m.type == MeasureType::PulseDuration || m.type == MeasureType::Period;
|
||||
const std::string unit = is_time ? "s"
|
||||
: m.type == MeasureType::Frequency ? "Hz"
|
||||
: measure_unit(app, m.signal);
|
||||
if (label != nullptr) {
|
||||
std::string text = "--";
|
||||
if (r.valid) {
|
||||
// Time-valued types carry seconds and a frequency carries hertz whatever the
|
||||
// signal's own unit is; everything else carries that unit.
|
||||
const bool is_time = m.type == MeasureType::PulseDuration ||
|
||||
m.type == MeasureType::Period;
|
||||
const std::string unit = is_time ? "s"
|
||||
: m.type == MeasureType::Frequency ? "Hz"
|
||||
: measure_unit(app, m.signal);
|
||||
text = format_measure(r.primary, unit);
|
||||
if (r.has_secondary) {
|
||||
// Pulse duration also reports the duty, which has no unit.
|
||||
@@ -3441,6 +3460,10 @@ void update_measures(App& app, bool force) {
|
||||
app.measure_text[i] = text;
|
||||
gtk_label_set_text(GTK_LABEL(label), text.c_str());
|
||||
}
|
||||
// One observation per recompute, and only when the pass produced one. An invalid pass
|
||||
// contributes nothing -- the marks' `measure_last_valid` fallback below is a *drawing*
|
||||
// convenience, and accumulating it would count one answer as many observations.
|
||||
if (r.valid && i < app.measure_stats.size()) app.measure_stats[i].add(r.primary);
|
||||
}
|
||||
|
||||
// The marks last, in their own pass over the measurements, so that no `continue` above can skip
|
||||
@@ -5891,7 +5914,12 @@ gboolean tick(gpointer user) {
|
||||
app->dbg_measures_pane_w = gtk_widget_get_width(app->measures_list);
|
||||
app->dbg_measures_pane_h = gtk_widget_get_height(app->measures_list);
|
||||
}
|
||||
if (model.generation() != app->signal_generation) rebuild_signal_panels(*app);
|
||||
if (model.generation() != app->signal_generation) {
|
||||
// A CONFIG re-describes the stream and drops the buffer, so nothing measured before it
|
||||
// belongs in the same series as what comes after.
|
||||
reset_measure_stats(*app);
|
||||
rebuild_signal_panels(*app);
|
||||
}
|
||||
// A gesture moves the *plot's* cursor; adopting it here is what makes the two
|
||||
// vertical markers one time across every row, and what the pane then reads.
|
||||
app->rows->update_time_labels();
|
||||
|
||||
Reference in new issue
Block a user