fix(plot): a figure row's strip shows labels and title independently, and the shared axis is the bottom row's strip

This commit is contained in:
Martino Ferrari committed 2026-09-28 15:07:39 +02:00
1 parent 8f1e60a105
commit 770da4c2b2
9 files changed
+415 -175

No files matched your search

+26 -34
View File
@@ -364,27 +364,22 @@ void Renderer::draw(Figure& figure) {
for (u32 row = 0; row < rows; ++row) prepare_plot(figure.plot(row));
// The figure's one layout for the frame. It hands every row its rect and its
// x-label policy, advances the link groups, and lays every row out against its own
// x-strip mode, advances the link groups, and lays every row out against its own
// rect -- which is why the rows below are drawn with the layout it produced rather
// than laying each row out a second time. Nothing draws before this: a row's
// viewport is the figure's to set.
const FigureLayout& layout = figure.layout();
//
// The figure's one x axis needs no pass of its own: it is the bottom row's own, so
// that row stages it in its own strip through `draw_tick_labels()` like any other
// plot's axis -- and every row above it reserved no x chrome, which is what keeps
// the labels from appearing once per row without a second staging here.
figure.layout();
for (u32 row = 0; row < rows; ++row) {
Plot& plot = figure.plot(row);
// The layout the figure just computed, read through the non-recomputing
// overload -- the one layout of this frame, not a second derivation of it.
draw_plot(plot, static_cast<const Plot&>(plot).layout());
}
// The figure's one shared x axis, staged last so it joins the frame's single text
// flush: the labels of the last row's x axes, in the strip the figure measured
// under it. The rows reserved no strip of their own for them (see
// `Plot::set_x_label_strip()`), which is what keeps one set of labels from being
// drawn once per row; with the figure set to label every row, there is no shared
// strip and each row drew its own above.
if (rows != 0u && figure.x_labels_only_on_bottom() && layout.x_axis_strip.h > 0.0f) {
draw_x_tick_labels(figure.plot(rows - 1u), layout.x_axis_strip);
}
flush_text();
}
@@ -654,32 +649,29 @@ void Renderer::draw_axis_lines(const Plot& plot) {
}
}
void Renderer::draw_x_tick_labels(const Plot& plot, const Rect& strip) {
// Centred under their tick, on the strip's single text line -- the one rule for a
// plot's own label strip and for the strip a figure shares out under its last row.
// A plot that reserved no strip of its own hands its labels to the figure, which
// is why this takes the rect rather than reading the layout it came from.
const f32 line_h = font_backend_.height();
for (u32 i = 0; i < plot.x_axis_count(); ++i) {
const Transform transform = plot.x_transform(i);
for (const Tick& tick : plot.x_axis(i).ticks) {
if (!tick.major || tick.label.empty()) continue;
const f32 width = font_backend_.measure(tick.label.c_str());
stage_text({static_cast<f32>(transform.to_screen_x(tick.value)) - 0.5f * width,
strip.y + 0.5f * (strip.h - line_h)},
tick.label.c_str());
}
}
}
void Renderer::draw_tick_labels(const Plot& plot, const PlotLayout& layout) {
const f32 line_h = font_backend_.height();
// X labels: unless the plot gave its x label strip away -- a row of a `Figure`
// whose figure drew one shared axis instead -- in which case it has no strip to
// put them in and their absence is the point. A plot with no strip would otherwise
// stage its labels just above `axes`' bottom edge, i.e. over its own data.
if (plot.x_label_strip()) draw_x_tick_labels(plot, layout.x_label_area);
// that reserves no x chrome, because the figure draws one shared axis in the bottom
// row's own strip instead -- in which case it has no strip to put them in and their
// absence is the point. A plot with no strip would otherwise stage its labels just
// above `axes`' bottom edge, i.e. over its own data. Centred under their tick on the
// strip's single text line, which is the one rule for a plot's labels and a figure's
// shared axis alike -- the figure's axis is drawn by this same pass, in the bottom
// row's own `x_label_area`.
if (x_strip_has_labels(plot.x_strip_mode())) {
for (u32 i = 0; i < plot.x_axis_count(); ++i) {
const Transform transform = plot.x_transform(i);
for (const Tick& tick : plot.x_axis(i).ticks) {
if (!tick.major || tick.label.empty()) continue;
const f32 width = font_backend_.measure(tick.label.c_str());
stage_text({static_cast<f32>(transform.to_screen_x(tick.value)) - 0.5f * width,
layout.x_label_area.y + 0.5f * (layout.x_label_area.h - line_h)},
tick.label.c_str());
}
}
}
// Y labels: right-aligned in their strip, centred on the tick and pulled back
// inside the strip at the two extremes (a label centred on the top tick would