crosshair: axis badges, clustered cursor labels, and the shared-y horizontal line
- Axis badges: the y value in the y label strip and the x time in the x strip, each a full-opacity panel in the crosshair's colour, **centred on the line it names**. The tick text is flushed before the fill -- the over-text batch is flushed after every line batch, so without that flush the label behind printed through the panel (the "transparent backplate" defect). - Cursor labels: collected across the cursor loop and clustered at its end, so two marks in one column share a panel with one line each instead of overprinting, and a label stays on its own mark's row. - Figure: a split row's cells share one y (`set_share_row_y`), so the horizontal line is drawn in every cell of the hovered row -- one value, one scale, spanning the row -- and still in no other row. Tests: `figure_crosshair_horizontal_spans_a_split_rows_cells`, plus the render-smoke additions for the badge placement/flush and the clustered labels.
This commit is contained in:
1 parent
2cb31cbb91
commit
1d5b286611
9 files changed
+469
-49
No files matched your search
+88
-11
@@ -5750,17 +5750,22 @@ PLOT_TEST(horizontal_ruler_label_reads_its_value) {
|
||||
|
||||
const InkExtent ink = ink_extent(frame, kW, kH, ruler_pixel);
|
||||
PLOT_CHECK(ink.pixels > 0u);
|
||||
// One horizontal run: shallow in rows, spanning essentially the axes' own width.
|
||||
PLOT_CHECK(ink.rows() <= 3u);
|
||||
// One horizontal run: the stroke itself is shallow, but the label's backplate is drawn in
|
||||
// the ruler's own colour too (see the clustered cursor labels), and its border spans one
|
||||
// text line plus its padding above and below the stroke -- measured 28 rows on the suite's
|
||||
// own font. The bound is that panel with a stroke's worth of slack, not the old bare-text
|
||||
// run of three.
|
||||
PLOT_CHECK(ink.rows() <= 32u);
|
||||
PLOT_CHECK(ink.cols() + 2u >= static_cast<plot::u32>(axes.w));
|
||||
// And at the row the transform says -- a ruler drawn at the wrong y, or a vertical column
|
||||
// that happens to be inside the rect, cannot satisfy both of these.
|
||||
PLOT_CHECK(static_cast<plot::f32>(ink.top) <= expected_row + 1.0f);
|
||||
PLOT_CHECK(static_cast<plot::f32>(ink.bottom) >= expected_row - 1.0f);
|
||||
|
||||
// The label: text ink just under the ruler at its left end, inside the axes. The tick
|
||||
// The label: text ink centred on the ruler at its left end, inside the axes. The tick
|
||||
// labels are outside `axes`, in their own strips, so this can only be the ruler's value.
|
||||
const plot::Rect label{axes.x + 1.0f, expected_row, 60.0f, 16.0f};
|
||||
// Centred, not below: the badge straddles the ruler's own row (see the clustered labels).
|
||||
const plot::Rect label{axes.x + 1.0f, expected_row - 9.0f, 60.0f, 18.0f};
|
||||
PLOT_CHECK(predicate_pixels(frame, kW, label, text_pixel) > 0u);
|
||||
|
||||
// The clip: every ruler-coloured pixel of the frame is inside `axes`.
|
||||
@@ -6096,15 +6101,65 @@ PLOT_TEST(crosshair_ink_stays_inside_the_axes) {
|
||||
PLOT_CHECK(crosshair.rows[2].marker.x < axes.x);
|
||||
PLOT_CHECK(crosshair.rows[2].marker.x >= layout.y_label_area.x);
|
||||
|
||||
// Every crosshair-coloured pixel of the frame is inside `axes`, and there are some: not one in a
|
||||
// label or title strip, not one over the host's surface outside the viewport.
|
||||
// Every crosshair-coloured pixel of the frame is inside `axes` or inside one of the two
|
||||
// axis badges, and there are some. The badges are the deliberate exception -- the
|
||||
// crosshair's own axis readings, drawn in the strips the ticks are written in (see
|
||||
// `Crosshair::axis_labels`) -- and the layout named their rects, so the exception is
|
||||
// checked against exactly what was placed, not against a loose region.
|
||||
PLOT_CHECK(crosshair.y_axis_badge.w > 0.0f);
|
||||
PLOT_CHECK(crosshair.x_axis_badge.w > 0.0f);
|
||||
PLOT_CHECK(crosshair.y_axis_badge.right() <= axes.x + 0.5f); // beside the y ticks
|
||||
PLOT_CHECK(crosshair.x_axis_badge.y >= axes.bottom() - 0.5f); // beside the x ticks
|
||||
const plot::u32 inside = predicate_pixels(frame, kW, axes, crosshair_pixel);
|
||||
// The badge rects are the panels' own frames, and a hairline's anti-aliased fringe can sit
|
||||
// half a pixel outside one -- the badge's own ink either way, so the counts read over the
|
||||
// rects inflated by a pixel. The inflation is held out of the axes: a badge sits *against*
|
||||
// an axis line, and a fringe column read into the axes' own pixels would be counted twice.
|
||||
const auto inflate_badge = [&axes](const plot::Rect& rect, bool toward_axes) {
|
||||
plot::Rect out{rect.x - 1.0f, rect.y - 1.0f, rect.w + 2.0f, rect.h + 2.0f};
|
||||
if (toward_axes && out.right() > axes.x) out.w -= out.right() - axes.x;
|
||||
if (!toward_axes && out.y < axes.bottom()) {
|
||||
const f32 d = axes.bottom() - out.y;
|
||||
out.y += d;
|
||||
out.h -= d;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
const plot::Rect y_badge_rect = inflate_badge(crosshair.y_axis_badge, true);
|
||||
const plot::Rect x_badge_rect = inflate_badge(crosshair.x_axis_badge, false);
|
||||
const plot::u32 y_badge = predicate_pixels(frame, kW, y_badge_rect, crosshair_pixel);
|
||||
const plot::u32 x_badge = predicate_pixels(frame, kW, x_badge_rect, crosshair_pixel);
|
||||
PLOT_CHECK(inside > 0u);
|
||||
PLOT_CHECK(predicate_pixels(frame, kW, kFrameRect, crosshair_pixel) == inside);
|
||||
PLOT_CHECK(predicate_pixels(frame, kW, layout.y_label_area, crosshair_pixel) == 0u);
|
||||
PLOT_CHECK(predicate_pixels(frame, kW, layout.y_title_area, crosshair_pixel) == 0u);
|
||||
PLOT_CHECK(predicate_pixels(frame, kW, layout.x_label_area, crosshair_pixel) == 0u);
|
||||
PLOT_CHECK(predicate_pixels(frame, kW, layout.x_title_area, crosshair_pixel) == 0u);
|
||||
PLOT_CHECK(y_badge > 0u);
|
||||
PLOT_CHECK(x_badge > 0u);
|
||||
PLOT_CHECK(predicate_pixels(frame, kW, kFrameRect, crosshair_pixel) ==
|
||||
inside + y_badge + x_badge);
|
||||
// The badges may reach past the label strip into the title strip beside it -- a badge is as
|
||||
// wide as its own text and is capped by nothing but the strips' union -- so the title
|
||||
// strips' rule is: **every crosshair-coloured pixel is the badge's own**, i.e. within one
|
||||
// badge rect's one-pixel fringe. A stray pixel there is a second drawing in a strip that
|
||||
// belongs to the axis title.
|
||||
const auto title_stray = [&](const plot::Rect& strip, const plot::Rect& badge) {
|
||||
const plot::Rect frame_rect{badge.x - 1.0f, badge.y - 1.0f, badge.w + 2.0f, badge.h + 2.0f};
|
||||
plot::u32 stray = 0;
|
||||
for (plot::i32 y = static_cast<plot::i32>(strip.y);
|
||||
y < static_cast<plot::i32>(strip.bottom()); ++y) {
|
||||
for (plot::i32 x = static_cast<plot::i32>(strip.x);
|
||||
x < static_cast<plot::i32>(strip.right()); ++x) {
|
||||
const plot::f32 fx = static_cast<plot::f32>(x);
|
||||
const plot::f32 fy = static_cast<plot::f32>(y);
|
||||
const plot::u8* pixel =
|
||||
&frame[(static_cast<plot::usize>(y) * kW + static_cast<plot::usize>(x)) * 4u];
|
||||
if (!crosshair_pixel(pixel)) continue;
|
||||
const bool own = fx >= frame_rect.x && fx < frame_rect.x + frame_rect.w &&
|
||||
fy >= frame_rect.y && fy < frame_rect.y + frame_rect.h;
|
||||
if (!own) ++stray;
|
||||
}
|
||||
}
|
||||
return stray;
|
||||
};
|
||||
PLOT_CHECK(title_stray(layout.y_title_area, crosshair.y_axis_badge) == 0u);
|
||||
PLOT_CHECK(title_stray(layout.x_title_area, crosshair.x_axis_badge) == 0u);
|
||||
plot::u8 host[4] = {};
|
||||
PLOT_CHECK(ctx.read_pixel(4u, kH - 4u, host));
|
||||
PLOT_CHECK(host[0] > 100 && host[0] < 160 && host[1] > 100 && host[2] > 100);
|
||||
@@ -6133,6 +6188,21 @@ PLOT_TEST(crosshair_ink_stays_inside_the_axes) {
|
||||
PLOT_CHECK(predicate_pixels(frame, kW, kFrameRect, magenta_pixel) ==
|
||||
predicate_pixels(frame, kW, axes, magenta_pixel));
|
||||
|
||||
// The same plot with the badges off: the layout places none, and the strips go back to
|
||||
// being clean of the crosshair's colour -- the flag is the whole difference between the
|
||||
// two frames, and this leg is what makes it a setting rather than a behaviour.
|
||||
p.set_crosshair_axis_labels(false);
|
||||
p.layout();
|
||||
r.begin_frame(kW, kH, vp);
|
||||
r.draw(p);
|
||||
r.end_frame();
|
||||
PLOT_CHECK(!r.last_frame_had_gl_error());
|
||||
const std::vector<plot::u8> off_frame = read_frame(ctx);
|
||||
PLOT_CHECK(p.layout().crosshair.y_axis_badge.w == 0.0f);
|
||||
PLOT_CHECK(p.layout().crosshair.x_axis_badge.w == 0.0f);
|
||||
PLOT_CHECK(predicate_pixels(off_frame, kW, layout.y_label_area, crosshair_pixel) == 0u);
|
||||
PLOT_CHECK(predicate_pixels(off_frame, kW, layout.x_label_area, crosshair_pixel) == 0u);
|
||||
|
||||
ctx.destroy();
|
||||
}
|
||||
|
||||
@@ -6278,6 +6348,13 @@ PLOT_TEST(crosshair_dash_is_configurable_and_zero_off_means_solid) {
|
||||
// 4 on / 4 off over the axes' width: many dashes, not one line.
|
||||
const plot::u32 dashed = runs(4.0f);
|
||||
PLOT_CHECK(dashed > 4u);
|
||||
|
||||
// The y axis badge is **centred** on the line it names, the way the x badge is centred on
|
||||
// its column -- its middle is the line's own screen row. A badge hung from the line (top
|
||||
// edge on it) is half a badge off, which is exactly what this reads for.
|
||||
const plot::Rect badge = p.layout().crosshair.y_axis_badge;
|
||||
PLOT_CHECK(badge.w > 0.0f);
|
||||
PLOT_CHECK_NEAR(badge.y + 0.5f * badge.h, cyp, 0.6f);
|
||||
// Off == 0 is solid: one run, the whole width, no gap anywhere.
|
||||
PLOT_CHECK(runs(0.0f) == 1u);
|
||||
// A longer gap is a longer period, so fewer dashes over the same width.
|
||||
|
||||
Reference in new issue
Block a user