fix(plot,render,glfw): a hue-neutral crosshair, an em-dash placeholder, one layout per figure event, and a usable readout switch
This commit is contained in:
1 parent
10d5129fbf
commit
a14f333dbe
10 files changed
+175
-70
No files matched your search
@@ -650,16 +650,21 @@ PLOT_TEST(figure_crosshair_applies_one_x_to_every_row) {
|
||||
PLOT_CHECK(third.panel.w == 0.0f);
|
||||
|
||||
// Row 3's series starts at x = 100: it is a visible signal with nothing at x = 3, so it is
|
||||
// listed with **no value** rather than a value clamped to the end of its window.
|
||||
// listed with **no number** rather than one clamped to the end of its window -- and the absence
|
||||
// is said out loud with the documented placeholder, not left as a blank cell that would read as a
|
||||
// value still to come.
|
||||
const plot::CrosshairLayout& fourth = figure.plot(3u).layout().crosshair;
|
||||
PLOT_CHECK(fourth.rows.size() == 1u);
|
||||
PLOT_CHECK(fourth.rows[0].trace == 0u);
|
||||
PLOT_CHECK(fourth.rows[0].label == "elsewhere");
|
||||
PLOT_CHECK(!fourth.rows[0].valid);
|
||||
PLOT_CHECK(fourth.rows[0].value.empty());
|
||||
// ... and it is still listed, so the row's presence does not depend on having a value.
|
||||
PLOT_CHECK(fourth.rows[0].value == plot::kCrosshairNoValue);
|
||||
PLOT_CHECK(fourth.rows[0].marker.x == 0.0f && fourth.rows[0].marker.y == 0.0f); // no intersection
|
||||
// ... and it is still listed, with a value column, so the row's presence and its shape do not
|
||||
// depend on having a sample there.
|
||||
PLOT_CHECK(fourth.rows[0].listed);
|
||||
PLOT_CHECK(fourth.rows[0].value_box.w == 0.0f);
|
||||
PLOT_CHECK(fourth.rows[0].value_box.w > 0.0f);
|
||||
PLOT_CHECK(fourth.rows[0].value_box.right() <= fourth.panel.right());
|
||||
|
||||
// The crosshair is a data coordinate, so the x is the *same* whether or not the rows sample
|
||||
// there -- moving it moves every row, and the rows that have data report their own value at
|
||||
|
||||
+35
-6
@@ -2038,12 +2038,41 @@ PLOT_TEST(crosshair_readout_lists_every_visible_trace_including_unnamed_ones) {
|
||||
PLOT_CHECK(cleared.panel.w == 0.0f && cleared.panel.h == 0.0f);
|
||||
PLOT_CHECK(cleared.vertical.w == 0.0f && cleared.horizontal.h == 0.0f);
|
||||
|
||||
// A crosshair with the readout switched off still draws its lines -- and lists nothing.
|
||||
scene.plot.set_crosshair(2.0, true);
|
||||
plot::Crosshair no_readout = scene.plot.crosshair();
|
||||
PLOT_CHECK(no_readout.active);
|
||||
PLOT_CHECK(!no_readout.horizontal); // the one-coordinate form draws no row
|
||||
PLOT_CHECK(no_readout.show_readout);
|
||||
// A crosshair whose x is outside every trace's window: the row is still listed -- the trace is
|
||||
// one of the signals under the pointer -- and says so with the documented placeholder rather than
|
||||
// a blank cell or a fabricated number.
|
||||
scene.plot.set_crosshair(10.0, true); // past the last sample of the 4-sample series
|
||||
const plot::CrosshairLayout& past_the_end = scene.plot.layout().crosshair;
|
||||
PLOT_CHECK(past_the_end.rows.size() == 2u); // the two visible traces, both with no sample here
|
||||
PLOT_CHECK(!past_the_end.rows[0].valid);
|
||||
PLOT_CHECK(past_the_end.rows[0].value == plot::kCrosshairNoValue);
|
||||
PLOT_CHECK(!past_the_end.rows[0].value.empty());
|
||||
PLOT_CHECK(past_the_end.rows[0].value_box.w > 0.0f); // the dash has a box of its own
|
||||
PLOT_CHECK(past_the_end.rows[0].listed);
|
||||
PLOT_CHECK(past_the_end.rows[0].value_box.w <= past_the_end.panel.w);
|
||||
|
||||
// The readout switch, and the reason it is a switch rather than a field nobody can reach: with it
|
||||
// off the crosshair itself is untouched -- the lines are still placed and the trace is still a
|
||||
// row -- while nothing is listed in a panel, so a host that wants the pointer's column marked and
|
||||
// not a panel over its data can say so. Switching it back on lists the rows again.
|
||||
scene.plot.set_crosshair(1.5, true);
|
||||
scene.plot.set_crosshair_readout(false);
|
||||
const plot::CrosshairLayout& off = scene.plot.layout().crosshair;
|
||||
PLOT_CHECK(off.active);
|
||||
PLOT_CHECK(off.vertical.w > 0.0f); // the switch is the readout's: the lines are untouched
|
||||
PLOT_CHECK(off.rows.size() == 2u); // both traces are still there, with their markers
|
||||
for (const plot::CrosshairRow& row : off.rows) {
|
||||
PLOT_CHECK(!row.listed); // ... and neither is a line in a panel
|
||||
PLOT_CHECK(row.value_box.w == 0.0f);
|
||||
PLOT_CHECK(row.marker.x > 0.0f); // the marker is placed either way
|
||||
}
|
||||
PLOT_CHECK(off.panel.w == 0.0f && off.panel.h == 0.0f);
|
||||
scene.plot.set_crosshair_readout(true);
|
||||
const plot::CrosshairLayout& on = scene.plot.layout().crosshair;
|
||||
PLOT_CHECK(on.rows[0].listed);
|
||||
PLOT_CHECK(on.rows[1].listed);
|
||||
PLOT_CHECK(on.panel.w > 0.0f && on.panel.h > 0.0f);
|
||||
PLOT_CHECK(on.rows[0].value == "15");
|
||||
}
|
||||
|
||||
/// Spec §15, the panel's placement: near the pointer, flipped over it where it would otherwise
|
||||
|
||||
@@ -4575,18 +4575,54 @@ PLOT_TEST(horizontal_ruler_stays_inside_the_axes_at_the_edges) {
|
||||
|
||||
namespace {
|
||||
|
||||
/// The crosshair's own colour (0xB8E8FF) at real coverage: a pale cyan, green clearly above red
|
||||
/// and blue above green, which no other element of the frames this case draws produces. The two
|
||||
/// traces here are magenta and green -- deliberately *not* blue, which is the one other colour
|
||||
/// that would satisfy "blue above green and green above red" (0x40C0FF does). The axis text
|
||||
/// (0xC8D0DC) is grey enough to fail the green-over-red term, the chrome and the grid are darker
|
||||
/// still, and the same hue at lower coverage still passes, so a partially covered stroke counts
|
||||
/// as ink rather than the test needing a fully covered pixel to exist.
|
||||
/// The crosshair's own ink: **bright and hue-neutral**, which is the property the default colour
|
||||
/// has on purpose (see `Style::crosshair`) so that it can never be read as a trace of any hue.
|
||||
/// This predicate is therefore about brightness and chroma rather than about a hue, and it needs no
|
||||
/// "not this trace's colour" exception: every other element of these frames is either dark (the
|
||||
/// background, the grid), saturated (the magenta, green and orange traces) or dimmer than the
|
||||
/// crosshair (the axis text 0xC8D0DC, whose red channel is 200 against the crosshair's 230).
|
||||
bool crosshair_pixel(const plot::u8 px[4]) {
|
||||
return px[1] > px[0] + 25 && px[2] > px[1] + 12 && px[2] > 120;
|
||||
const int lo = std::min(px[0], std::min(px[1], px[2]));
|
||||
const int hi = std::max(px[0], std::max(px[1], px[2]));
|
||||
return px[0] > 215 && hi - lo <= 24;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/// The crosshair's colour has to be tellable from a trace, and telling colours apart by hue is the
|
||||
/// one thing a reader cannot be asked to do at a glance: the library's own default trace colour is a
|
||||
/// saturated cyan, so a saturated cyan crosshair would differ from it in lightness alone. The
|
||||
/// default is therefore near-white -- a colour no saturated trace of any hue can be -- and still
|
||||
/// plainly not the cursor accent. Both halves are asserted, so a future recolouring that makes it
|
||||
/// confusable with a trace or with a handle fails here rather than in a screenshot.
|
||||
PLOT_TEST(crosshair_colour_cannot_be_mistaken_for_a_trace) {
|
||||
const plot::render::Style style;
|
||||
const plot::Trace default_trace;
|
||||
// `Color` stores normalised channels; the comparisons below are in 0..255, like the hex literals
|
||||
// the colours are written with.
|
||||
const auto chan = [](plot::f32 c) { return static_cast<int>(c * 255.0f + 0.5f); };
|
||||
const int lo = std::min({chan(style.crosshair.r), chan(style.crosshair.g), chan(style.crosshair.b)});
|
||||
const int hi = std::max({chan(style.crosshair.r), chan(style.crosshair.g), chan(style.crosshair.b)});
|
||||
// Hue-neutral: its channels are within a few levels of each other, so it is a grey/white and not
|
||||
// a hue at all. A saturated colour -- the default trace's cyan included -- is 150+ levels apart.
|
||||
PLOT_CHECK(hi - lo <= 24);
|
||||
PLOT_CHECK(lo > 200); // bright chrome, not a mid-tone that a dimmed trace could reach
|
||||
// `Color` is a plain channel struct with no comparison of its own, so the two "not this colour"
|
||||
// assertions spell the channels out.
|
||||
const auto same = [](plot::Color a, plot::Color b) {
|
||||
return a.r == b.r && a.g == b.g && a.b == b.b;
|
||||
};
|
||||
PLOT_CHECK(!same(style.crosshair, default_trace.color));
|
||||
PLOT_CHECK(!same(style.crosshair, style.cursor));
|
||||
// ... and a saturated trace of any hue is out of reach for it by construction: the *most*
|
||||
// chromatic colour the crosshair could be confused with has to be within the chroma bound above,
|
||||
// and every trace colour in this library's demos is far outside it.
|
||||
const plot::Color demo_traces[3] = {plot::Color::hex(0x40C0FF), plot::Color::hex(0xFFB020),
|
||||
plot::Color::hex(0x35D0A8)};
|
||||
for (const plot::Color trace : demo_traces) {
|
||||
PLOT_CHECK(!same(style.crosshair, trace));
|
||||
}
|
||||
}
|
||||
namespace {
|
||||
|
||||
/// A third colour predicate for the lateral case below: the orange 0xFFB020 of a trace of its
|
||||
|
||||
Reference in new issue
Block a user