Compare commits
2
Commits
c2c8c596f3
...
bee318870f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bee318870f | ||
|
|
58473db384 |
No files matched your search
@@ -138,6 +138,16 @@ class Figure {
|
||||
}
|
||||
f32 min_y_gutter() const noexcept { return min_y_gutter_; }
|
||||
|
||||
/// The empty band every row keeps to the right of its `axes`, in device pixels (0.0
|
||||
/// by default, so `axes` reaches the viewport's right edge). It is set here rather
|
||||
/// than row by row for the left gutter's reason: the rows' axes have to line up, and
|
||||
/// they end on a common column only if they are told one width. See
|
||||
/// `Plot::set_right_gutter()` for what it is for. Negative values are clamped to 0.
|
||||
void set_right_gutter(f32 device_px) noexcept {
|
||||
right_gutter_ = device_px > 0.0f ? device_px : 0.0f;
|
||||
}
|
||||
f32 right_gutter() const noexcept { return right_gutter_; }
|
||||
|
||||
/// Whether the shared x axis is the figure's: every row reserves no x label strip
|
||||
/// and one shared axis is drawn under the last row (the default), or every row
|
||||
/// draws its own labels and the figure draws nothing extra.
|
||||
@@ -326,6 +336,7 @@ class Figure {
|
||||
FigureLayout layout_{};
|
||||
f32 row_gap_ = 0.0f;
|
||||
f32 min_y_gutter_ = 0.0f;
|
||||
f32 right_gutter_ = 0.0f;
|
||||
bool labels_bottom_only_ = true;
|
||||
};
|
||||
|
||||
|
||||
@@ -88,6 +88,11 @@ struct PlotLayout {
|
||||
/// no text backend there is no title to lay out at all and both stay 0.0f.
|
||||
f32 y_title_width = 0.0f;
|
||||
f32 x_title_height = 0.0f;
|
||||
/// The empty band kept between `axes`' right edge and the viewport's, 0.0f unless a
|
||||
/// caller asked for one (see `Plot::set_right_gutter()`). Reported because it is
|
||||
/// clamped by the same cap as the strips above, so what was asked for and what was
|
||||
/// reserved are not always the same number.
|
||||
f32 right_gutter = 0.0f;
|
||||
/// The measured legend: inside `axes`, never reserving any part of it, and empty
|
||||
/// when there is nothing to draw. It is measured with the same text backend the
|
||||
/// strips above are, and against the same `axes` rect the caller reads back, so
|
||||
@@ -640,6 +645,20 @@ class Plot {
|
||||
}
|
||||
f32 min_y_gutter() const noexcept { return min_y_gutter_; }
|
||||
|
||||
/// An empty band reserved between `axes`' right edge and the viewport's, in device
|
||||
/// pixels. 0.0 -- the default -- is what the layout has always done: `axes` reaches
|
||||
/// the viewport's right edge, which leaves the rightmost x tick label, *centred* on
|
||||
/// that edge, half of it to be drawn in and nothing to draw it in -- and puts the
|
||||
/// newest sample's ink on the frame. It is the mirror of the left gutter in purpose
|
||||
/// and not in kind: nothing is drawn here, so there is nothing to measure and the
|
||||
/// value is the caller's to choose. Reserved inside the same cap as the label and
|
||||
/// title strips, so it can no more squeeze `axes` below the half-viewport floor than
|
||||
/// they can. Negative values are clamped to 0.
|
||||
void set_right_gutter(f32 device_px) noexcept {
|
||||
right_gutter_ = device_px > 0.0f ? device_px : 0.0f;
|
||||
}
|
||||
f32 right_gutter() const noexcept { return right_gutter_; }
|
||||
|
||||
/// Grid segments for the primary axis pair (x axis 0, y axis 0), in data space.
|
||||
const Grid& grid() const noexcept { return grid_; }
|
||||
|
||||
@@ -867,6 +886,9 @@ class Plot {
|
||||
/// The floor a figure imposes on the left gutter; see set_min_y_gutter(). 0.0 means
|
||||
/// none, which is what a plot standing on its own keeps.
|
||||
f32 min_y_gutter_ = 0.0f;
|
||||
/// The empty band kept to the right of `axes`; see set_right_gutter(). 0.0 means none,
|
||||
/// which is what the layout did before the setter existed.
|
||||
f32 right_gutter_ = 0.0f;
|
||||
/// Which handles a pointer is on, published by the input layer (see HotHandles).
|
||||
HotHandles hot_{};
|
||||
|
||||
|
||||
@@ -451,6 +451,10 @@ const FigureLayout& Figure::layout() {
|
||||
// not drawn once per row. A figure told to label every row keeps them everywhere,
|
||||
// and then there is no shared axis to speak of: each row has its own.
|
||||
p.set_x_strip_mode(labels_bottom_only_ && row != bottom_row ? XStrip::None : XStrip::Both);
|
||||
// Before either pass below, and the same value for every row: nothing is measured on
|
||||
// the right, so unlike the left gutter this needs no measurement pass -- the rows end
|
||||
// on a common column because they are all told the one width.
|
||||
p.set_right_gutter(right_gutter_);
|
||||
}
|
||||
// After the viewports, so a row's ticks and grid -- built from its axes rect -- and
|
||||
// its transforms describe this frame's rects; and before the rows lay themselves
|
||||
|
||||
+10
-1
@@ -1039,12 +1039,21 @@ const PlotLayout& Plot::layout() {
|
||||
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);
|
||||
// The right-hand band, out of what is left of the same allowance the two left
|
||||
// strips were served from -- so the three together cannot take more than
|
||||
// `1 - kMinAxesShare` of the width however much each asks for. Nothing is drawn
|
||||
// in it, so unlike the strips above it is a want and never a measurement, and it
|
||||
// cannot feed the fixed point below: the y labels are chosen from the row's range
|
||||
// and height, so a narrower `axes` does not change what they need.
|
||||
const f32 right_gutter =
|
||||
std::min(right_gutter_, y_budget - (y_width + y_title_width));
|
||||
layout_.y_axis_width = y_width;
|
||||
layout_.x_axis_height = x_height;
|
||||
layout_.y_title_width = y_title_width;
|
||||
layout_.x_title_height = x_title_height;
|
||||
layout_.right_gutter = right_gutter;
|
||||
layout_.axes = Rect{viewport_.x + y_width + y_title_width, viewport_.y,
|
||||
vw - (y_width + y_title_width),
|
||||
vw - (y_width + y_title_width + right_gutter),
|
||||
vh - (x_height + x_title_height)};
|
||||
layout_.y_title_area = Rect{viewport_.x, layout_.axes.y, y_title_width, layout_.axes.h};
|
||||
layout_.y_label_area =
|
||||
|
||||
+9
-2
@@ -98,7 +98,10 @@ void format_time(f64 seconds, char* buf, usize n) {
|
||||
const char* const sign = seconds < 0.0 ? "-" : "";
|
||||
const f64 t = std::fabs(seconds);
|
||||
|
||||
if (t < 1.0) {
|
||||
// `0.9995` and not `1.0`: three significant figures of 0.9999 s are `1e+03ms`, so a value
|
||||
// that *rounds* to a second belongs to the whole-second branch below, which writes it `1s`.
|
||||
// The same spill one rung down is handled inside the ladder.
|
||||
if (t < 0.9995) {
|
||||
// Sub-second: the prefix that keeps the mantissa in [1, 1000) -- the same rule the SI
|
||||
// formatter uses on a y axis, and the reading a zoomed-in window needs. A fixed
|
||||
// millisecond unit printed `0.005ms` where `5us` is what the axis is showing, and
|
||||
@@ -119,8 +122,12 @@ void format_time(f64 seconds, char* buf, usize n) {
|
||||
{1.0e-9, "ns"},
|
||||
{1.0e-12, "ps"},
|
||||
};
|
||||
// The rung is picked by the *rounded* mantissa, which is what `format_si`'s own 999.5
|
||||
// check buys there: 0.9999 ms is `999.9µs` before rounding and `1e+03µs` after, so it is
|
||||
// the millisecond rung's value and reads `1ms`. Testing the raw `t < u.scale` let the
|
||||
// mantissa reach 1000 at every rung boundary.
|
||||
for (const auto& u : kUnits) {
|
||||
if (t < u.scale) continue;
|
||||
if (t < u.scale * 0.9995) continue;
|
||||
std::snprintf(buf, n, "%s%.3g%s", sign, t / u.scale, u.unit);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -425,6 +425,52 @@ PLOT_TEST(figure_gutter_never_squeezes_the_axes_below_the_floor) {
|
||||
}
|
||||
}
|
||||
|
||||
/// The other side of the same band. `axes` reached the viewport's right edge, which left
|
||||
/// the rightmost x tick label -- centred on it -- half outside the plot, so a figure can
|
||||
/// ask for an empty band there. One value for every row, like the left gutter, because
|
||||
/// the rows' axes have to end on a common column as well as start on one.
|
||||
PLOT_TEST(figure_rows_share_one_right_gutter) {
|
||||
plot::Figure figure = different_magnitude_figure();
|
||||
figure.layout();
|
||||
const plot::f32 flush = figure.plot(0u).layout().axes.right();
|
||||
PLOT_CHECK_NEAR(static_cast<plot::f64>(figure.right_gutter()), 0.0, 1e-12);
|
||||
// The default is the layout as it always was: nothing between the data and the frame.
|
||||
PLOT_CHECK_NEAR(static_cast<plot::f64>(flush), static_cast<plot::f64>(kW), 1e-4);
|
||||
|
||||
figure.set_right_gutter(30.0f);
|
||||
figure.layout();
|
||||
const plot::Rect first = figure.plot(0u).layout().axes;
|
||||
for (plot::u32 row = 0; row < 3u; ++row) {
|
||||
const plot::PlotLayout& rl = figure.plot(row).layout();
|
||||
PLOT_CHECK_NEAR(static_cast<plot::f64>(rl.right_gutter), 30.0, 1e-4);
|
||||
PLOT_CHECK_NEAR(static_cast<plot::f64>(rl.axes.right()), static_cast<plot::f64>(kW) - 30.0,
|
||||
1e-4);
|
||||
// The band comes off the right alone: the left gutter the rows agreed on is untouched,
|
||||
// so a host can balance the two sides without the one moving when it sets the other.
|
||||
PLOT_CHECK_NEAR(static_cast<plot::f64>(rl.axes.x), static_cast<plot::f64>(first.x), 1e-4);
|
||||
PLOT_CHECK_NEAR(static_cast<plot::f64>(rl.axes.w), static_cast<plot::f64>(first.w), 1e-4);
|
||||
// ... and the x strip follows the data area rather than the viewport, or the shared
|
||||
// axis would be labelled over a band that holds no data.
|
||||
PLOT_CHECK_NEAR(static_cast<plot::f64>(rl.x_label_area.w), static_cast<plot::f64>(rl.axes.w),
|
||||
1e-4);
|
||||
}
|
||||
// Clamped by the same cap as the strips: a band wider than the whole figure cannot
|
||||
// squeeze `axes` below the floor `Plot::layout()` guarantees.
|
||||
figure.set_right_gutter(10.0f * kW);
|
||||
figure.layout();
|
||||
for (plot::u32 row = 0; row < 3u; ++row) {
|
||||
const plot::Rect& axes = figure.plot(row).layout().axes;
|
||||
PLOT_CHECK(axes.w >= 0.5f * kW - 1e-3f);
|
||||
}
|
||||
// Negative means "none", like the gutter and the row gap, and the data area goes back
|
||||
// to the viewport's edge.
|
||||
figure.set_right_gutter(-5.0f);
|
||||
PLOT_CHECK_NEAR(static_cast<plot::f64>(figure.right_gutter()), 0.0, 1e-12);
|
||||
figure.layout();
|
||||
PLOT_CHECK_NEAR(static_cast<plot::f64>(figure.plot(0u).layout().axes.right()),
|
||||
static_cast<plot::f64>(flush), 1e-4);
|
||||
}
|
||||
|
||||
/// The degenerate neighbour: a figure of one row imposes exactly what that row
|
||||
/// measured, so it lays out as the plot would on its own -- the gutter cannot move a
|
||||
/// row that has no sibling to line up with.
|
||||
|
||||
@@ -263,6 +263,18 @@ PLOT_TEST(format_time_never_lets_a_field_reach_its_radix) {
|
||||
plot::format_time(86399.5, b, sizeof b); PLOT_CHECK(std::string(b) == "1d");
|
||||
plot::format_time(86400.0, b, sizeof b); PLOT_CHECK(std::string(b) == "1d");
|
||||
|
||||
// A mantissa may not reach its own radix either: three significant figures of a value a
|
||||
// hair under a rung's top round up to 1000, and `%.3g` writes that `1e+03`. A one-second
|
||||
// tick computed as 0.9999999 s read `1e+03ms` on a live axis, which is where this was seen.
|
||||
plot::format_time(0.9999999, b, sizeof b); PLOT_CHECK(std::string(b) == "1s");
|
||||
plot::format_time(0.9995, b, sizeof b); PLOT_CHECK(std::string(b) == "1s");
|
||||
plot::format_time(0.9994, b, sizeof b); PLOT_CHECK(std::string(b) == "999ms");
|
||||
plot::format_time(0.9999999e-3, b, sizeof b); PLOT_CHECK(std::string(b) == "1ms");
|
||||
plot::format_time(0.9994e-3, b, sizeof b); PLOT_CHECK(std::string(b) == "999\u00b5s");
|
||||
plot::format_time(0.9999999e-6, b, sizeof b); PLOT_CHECK(std::string(b) == "1\u00b5s");
|
||||
plot::format_time(0.9999999e-9, b, sizeof b); PLOT_CHECK(std::string(b) == "1ns");
|
||||
plot::format_time(-0.9999999, b, sizeof b); PLOT_CHECK(std::string(b) == "-1s");
|
||||
|
||||
// Sweep the boundary neighbourhoods and check every emitted field.
|
||||
const double values[] = {0.4, 0.9995, 1.0, 59.4, 59.4999, 59.5, 59.9, 60.0, 90.0, 119.5, 3599.4,
|
||||
3599.5, 3599.9, 86399.4, 86399.5, 86399.9, 86400.0, 172800.0, -119.5,
|
||||
|
||||
Reference in new issue
Block a user