plot host: split rows share y, rulers per row

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-11-04 00:55:59 +01:00
1 parent 135310ceca
commit 5f0bc75c27
5 files changed
+174 -24

No files matched your search

+111 -3
View File
@@ -3974,6 +3974,18 @@ void split_probe_assign(App& a, int p, std::size_t c) {
a.rows->assign(p, 0, ch.name, ch.name, signal_color_index(a.model->signals(), ch.signal));
}
/// What the shared-y steps carry from the step that measures to the step that prints.
struct SharedYProbe {
int y_shared = 0;
int y_gutter = 0;
int ruler_mirror = 0;
std::string ranges;
std::string axes_w;
RowSet::CursorMode was_mode = RowSet::CursorMode::Mix;
bool was_on = false;
};
SharedYProbe g_shared_y;
void split_probe_clear_all(App& a) {
for (int r = 0; r < RowSet::kMaxRows; ++r) {
for (int s = 0; s < RowSet::kSlotsPerRow; ++s) a.rows->assign(r, s, "", "", 0);
@@ -4393,9 +4405,10 @@ gboolean split_probe_step(gpointer user) {
}
case 18: {
split_probe_report(*a, "after-assign");
// The cursors pane's ruler labels (`rebuild_cursors_pane()`, ruler modes): a plot of a split row is
// "plot <label>", a plot of an unsplit row keeps "row <index>". Both arrangements are read off the
// pane's own label widgets, the all-split one first and a mixed one second.
// The cursors pane's ruler labels (`rebuild_cursors_pane()`, ruler modes): one line per figure row,
// since a split row's cells share their y axis and their rulers. A split row is "row <figure row,
// from 1>" (one line, not one per cell); an unsplit row keeps "row <index>". Both arrangements are
// read off the pane's own label widgets, the all-split one first and a mixed one second.
const RowSet::CursorMode was_mode = rs.cursor_mode();
rs.set_cursor_mode(RowSet::CursorMode::Both);
const auto ruler_labels = [&]() {
@@ -4415,6 +4428,101 @@ gboolean split_probe_step(gpointer user) {
mixed_labels.c_str());
rs.set_cursor_mode(was_mode);
rebuild_cursors_pane(*a);
split_probe_next(*a);
break;
}
// --- Steps 19..21: a split row shares one y axis ---
case 19: {
// One figure row of two cells with a different trace in each (sine, ramp), rulers on. The next
// step sets a range on cell A against a frame that was laid out with this arrangement.
g_shared_y.was_mode = rs.cursor_mode();
g_shared_y.was_on = rs.cursors_visible();
rs.set_cells_per_row({2});
split_probe_clear_all(*a);
if (a->model->channel_count() >= 3) {
split_probe_assign(*a, 0, 0);
split_probe_assign(*a, 1, 2);
}
rs.set_cursor_mode(RowSet::CursorMode::Both);
rs.set_cursors_visible(true);
rs.reset_cursors();
split_probe_restructured(*a);
split_probe_next(*a);
break;
}
case 20: {
// A range set on cell A; the frame between this step and the next is the row's next layout.
rs.set_y_range(0, -3.0, 7.0);
split_probe_restructured(*a);
split_probe_next(*a);
break;
}
case 21: {
const RowSet::Row& ra = rs.row(0);
const RowSet::Row& rb = rs.row(1);
if (ra.plot == nullptr || rb.plot == nullptr) {
std::fprintf(stderr, "[split-probe] shared-y no plots\n");
break;
}
// 1. The range set on A is B's.
const plot::Range a_rng = ra.plot->y_range(ra.axis_y);
const plot::Range b_rng = rb.plot->y_range(rb.axis_y);
const int y_shared = (std::fabs(b_rng.min + 3.0) < 1e-9 && std::fabs(b_rng.max - 7.0) < 1e-9 &&
std::fabs(a_rng.min + 3.0) < 1e-9 && std::fabs(a_rng.max - 7.0) < 1e-9)
? 1 : 0;
// 2. Only the first cell has a gutter, and the two plots are the same width.
const plot::PlotLayout& la = ra.plot->layout();
const plot::PlotLayout& lb = rb.plot->layout();
const int y_gutter = (la.y_axis_width > 0.0f && lb.y_axis_width == 0.0f && lb.y_label_area.w == 0.0f &&
std::fabs(la.axes.w - lb.axes.w) <= 1.0f)
? 1 : 0;
const std::string ranges_before =
"A=" + std::to_string(a_rng.min) + ".." + std::to_string(a_rng.max) +
" B=" + std::to_string(b_rng.min) + ".." + std::to_string(b_rng.max);
const std::string axes_w = std::to_string(la.axes.w) + "," + std::to_string(lb.axes.w);
// 4. A ruler written on A's plot and read back by sync_cursors() is B's too.
ra.plot->set_cursor_y(ra.ruler, 1.25, plot::CursorPlacement::Free);
ra.plot->set_cursor_y(ra.ruler_b, 4.5, plot::CursorPlacement::Free);
rs.sync_cursors(0);
const int ruler_mirror = (rs.ruler_y(0) == 1.25 && rs.ruler_y(1) == 1.25 && rs.ruler_b_y(0) == 4.5 &&
rs.ruler_b_y(1) == 4.5 && rb.plot->cursor(rb.ruler).y == 1.25 &&
rb.plot->cursor(rb.ruler_b).y == 4.5)
? 1 : 0;
g_shared_y.y_shared = y_shared;
g_shared_y.y_gutter = y_gutter;
g_shared_y.ruler_mirror = ruler_mirror;
g_shared_y.ranges = ranges_before;
g_shared_y.axes_w = axes_w;
// 3. Fit all: the next frame's layout fits the union of both cells' traces.
rs.fit_all();
split_probe_restructured(*a);
split_probe_next(*a);
break;
}
case 22: {
const RowSet::Row& ra = rs.row(0);
const RowSet::Row& rb = rs.row(1);
if (ra.plot == nullptr || rb.plot == nullptr) break;
const plot::Range a_rng = ra.plot->y_range(ra.axis_y);
const plot::Range b_rng = rb.plot->y_range(rb.axis_y);
plot::Range da{};
plot::Range db{};
const bool have_a = ra.plot->y_data_bounds(ra.axis_y, da);
const bool have_b = rb.plot->y_data_bounds(rb.axis_y, db);
const double lo = have_a && have_b ? std::min(da.min, db.min) : 0.0;
const double hi = have_a && have_b ? std::max(da.max, db.max) : 0.0;
const int y_fit = (have_a && have_b && a_rng.min == b_rng.min && a_rng.max == b_rng.max &&
a_rng.min <= lo && a_rng.max >= hi && a_rng.max - a_rng.min < 10.0)
? 1 : 0;
std::fprintf(stderr,
"[split-probe] shared-y y_shared=%d y_gutter=%d y_fit=%d ruler_mirror=%d "
"ranges=[%s | fit A=%.9g..%.9g B=%.9g..%.9g data=%.4g..%.4g] axes_w=[%s] auto=%d\n",
g_shared_y.y_shared, g_shared_y.y_gutter, y_fit, g_shared_y.ruler_mirror,
g_shared_y.ranges.c_str(), a_rng.min, a_rng.max, b_rng.min, b_rng.max, lo, hi,
g_shared_y.axes_w.c_str(), rs.figure().row_y_auto(0) ? 1 : 0);
rs.set_cursor_mode(g_shared_y.was_mode);
rs.set_cursors_visible(g_shared_y.was_on);
rebuild_cursors_pane(*a);
break;
}
default: