fix(render): floor the axes so chrome cannot erase it, one title row per titled axis
Two findings on the pass-1 fix: - The reserved strips were clamped only to the viewport, so a title wider than a narrow panel took the rest of it and the axes collapsed: draw_traces() returned on a zero-width rect (no trace drawn, nothing reported) and a zero-height one flattened every trace onto a single row. The chrome is now capped at half the viewport -- the label strip is served first, the title strip takes what is left of that allowance -- an over-long title is clipped to its strip by a walk that both measures and stages it, and draw_traces() tests the height as well as the width. - Every titled y axis was staged at the same centred position inside one strip, so a Left/Right pair of titles overprinted. Each titled axis now owns a text line: the x strip grows one line per titled x axis (x_title_height), and the y runs stack down the axes-tall strip, which needs no extra width for it. Tests: `narrow_panel_with_a_long_y_title_still_draws` and `short_panel_with_an_x_title_still_draws` pin the floor (the axes keeps half the panel and the trace still covers the columns, with the title clipped into its strip), `titled_axis_pairs_get_their_own_rows` pins one text row per titled axis in both directions, and `layout_stacks_one_title_line_per_titled_axis` pins the strip sizing. All fail against the pass-1 sources.
This commit is contained in:
1 parent
8f45108559
commit
1f5aa11de2
6 files changed
+330
-51
No files matched your search
+45
-19
@@ -11,6 +11,16 @@ namespace {
|
||||
/// changing, so a normal plot converges on the second pass and never reaches this.
|
||||
constexpr u32 kMaxLayoutPasses = 4;
|
||||
|
||||
/// Share of the viewport the axes rect keeps however much chrome the label and
|
||||
/// title strips ask for. A long SI label or a title wider than a narrow panel can
|
||||
/// ask for the whole viewport, and an axes rect with no width or no height draws
|
||||
/// nothing at all -- no grid, no tick, no trace -- without reporting anything, so
|
||||
/// the strips give way instead. The label strip is what makes the tick values
|
||||
/// readable, so it is served first and the title strip takes what is left; a title
|
||||
/// longer than its share of the strip is clipped to it rather than allowed to grow
|
||||
/// it (see `Renderer::draw_titles()`).
|
||||
constexpr f32 kMinAxesShare = 0.5f;
|
||||
|
||||
const Axis& default_axis() noexcept {
|
||||
static const Axis axis{};
|
||||
return axis;
|
||||
@@ -311,22 +321,33 @@ const PlotLayout& Plot::layout() {
|
||||
x_height = text_->height() + 2.0f * pad;
|
||||
}
|
||||
// The title strips, though, are needed before the loop and never change, unlike
|
||||
// the label strips: a strip holds exactly one text line, so no tick label can
|
||||
// make it wider. Nothing at all is reserved without a text backend -- there is
|
||||
// then no title to measure, which is what keeps the two defaults above
|
||||
// meaningful for the no-text-backend case. The x strip is one text line tall;
|
||||
// the y strip is one horizontal run wide (see y_title_strip_width).
|
||||
// the label strips: a strip holds exactly one text line per titled axis and no
|
||||
// tick label can change that. Nothing at all is reserved without a text backend
|
||||
// -- there is then no title to measure, which is what keeps the two defaults
|
||||
// above meaningful for the no-text-backend case. The x strip is one text line
|
||||
// per titled x axis tall (they are stacked in it); the y strip is as wide as the
|
||||
// widest y title, since a y title is a horizontal run that stacks down the strip
|
||||
// and so needs width rather than another row of it (see y_title_strip_width).
|
||||
const u32 x_titles = titled_x_axis_count();
|
||||
const f32 y_title_w = text_ != nullptr ? y_title_strip_width() : 0.0f;
|
||||
const f32 x_title_h = text_ != nullptr && has_x_title() ? text_->height() + 2.0f * pad : 0.0f;
|
||||
const f32 x_title_h = text_ != nullptr && x_titles != 0u
|
||||
? static_cast<f32>(x_titles) * text_->height() + 2.0f * pad
|
||||
: 0.0f;
|
||||
|
||||
for (u32 pass = 0;; ++pass) {
|
||||
// Clamped to the viewport so a huge label set can never push the axes rect
|
||||
// outside it (or make its width negative). The title strips take what is left
|
||||
// after the label strip, so the two can never add up past the viewport.
|
||||
// Chrome is measured against the viewport, then capped so the axes keeps
|
||||
// kMinAxesShare of it -- the label strip first, the title strip out of what is
|
||||
// left of the allowance -- which is what keeps axes.w and axes.h positive
|
||||
// however long the labels and titles are. `viewport_` is what a huge label set
|
||||
// is clamped to first, so neither strip can push the rect outside it.
|
||||
y_width = std::clamp(y_width, 0.0f, viewport_.w);
|
||||
x_height = std::clamp(x_height, 0.0f, viewport_.h);
|
||||
const f32 y_title_width = std::clamp(y_title_w, 0.0f, viewport_.w - y_width);
|
||||
const f32 x_title_height = std::clamp(x_title_h, 0.0f, viewport_.h - x_height);
|
||||
const f32 y_budget = (1.0f - kMinAxesShare) * viewport_.w;
|
||||
const f32 x_budget = (1.0f - kMinAxesShare) * viewport_.h;
|
||||
y_width = std::min(y_width, y_budget);
|
||||
x_height = std::min(x_height, x_budget);
|
||||
const f32 y_title_width = std::min(y_title_w, y_budget - y_width);
|
||||
const f32 x_title_height = std::min(x_title_h, x_budget - x_height);
|
||||
layout_.y_axis_width = y_width;
|
||||
layout_.x_axis_height = x_height;
|
||||
layout_.y_title_width = y_title_width;
|
||||
@@ -344,8 +365,11 @@ const PlotLayout& Plot::layout() {
|
||||
refresh_ticks();
|
||||
|
||||
if (text_ == nullptr || pass + 1 >= kMaxLayoutPasses) break;
|
||||
const f32 need_y = std::clamp(y_label_width(), 0.0f, viewport_.w);
|
||||
const f32 need_x = std::clamp(text_->height() + 2.0f * pad, 0.0f, viewport_.h);
|
||||
// The same caps as above, so this compares like with like and the loop still
|
||||
// reaches its fixed point on the second pass.
|
||||
const f32 need_y = std::min(std::clamp(y_label_width(), 0.0f, viewport_.w), y_budget);
|
||||
const f32 need_x =
|
||||
std::min(std::clamp(text_->height() + 2.0f * pad, 0.0f, viewport_.h), x_budget);
|
||||
if (need_y == y_width && need_x == x_height) break; // strips are a fixed point
|
||||
y_width = need_y;
|
||||
x_height = need_x;
|
||||
@@ -385,18 +409,20 @@ f64 Plot::snapped_x(const Cursor& c) const {
|
||||
return c.x;
|
||||
}
|
||||
|
||||
bool Plot::has_x_title() const noexcept {
|
||||
u32 Plot::titled_x_axis_count() const noexcept {
|
||||
u32 n = 0;
|
||||
for (const Axis& a : x_axes_) {
|
||||
if (!a.label.empty()) return true;
|
||||
if (!a.label.empty()) ++n;
|
||||
}
|
||||
return false;
|
||||
return n;
|
||||
}
|
||||
|
||||
bool Plot::has_y_title() const noexcept {
|
||||
u32 Plot::titled_y_axis_count() const noexcept {
|
||||
u32 n = 0;
|
||||
for (const Axis& a : y_axes_) {
|
||||
if (!a.label.empty()) return true;
|
||||
if (!a.label.empty()) ++n;
|
||||
}
|
||||
return false;
|
||||
return n;
|
||||
}
|
||||
|
||||
f32 Plot::y_title_strip_width() const {
|
||||
|
||||
Reference in new issue
Block a user