feat: a span declared invalid is shaded behind the data it is missing from
This commit is contained in:
1 parent
1ae72d4903
commit
ebc67bd318
6 files changed
+250
No files matched your search
@@ -2,6 +2,8 @@
|
||||
// SPDX-FileCopyrightText: 2026 Martino Ferrari
|
||||
|
||||
#pragma once
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <plot/core/rect.hpp>
|
||||
@@ -105,6 +107,13 @@ struct PlotLayout {
|
||||
/// controller's hit test reads the very same rects, so where a region is drawn and
|
||||
/// where a pointer can grab it cannot drift apart.
|
||||
std::vector<RegionLayout> regions;
|
||||
/// Every invalid span's shading, in `Plot::invalid_spans()` order and clipped to the axes'
|
||||
/// columns. Measured here for the regions' first reason -- the frame draws exactly these
|
||||
/// numbers -- and reserving nothing, so a hole cannot move the strips the labels were
|
||||
/// measured into. A span entirely outside the window contributes no rect at all, so this is
|
||||
/// not index-aligned with `invalid_spans()` and nothing needs it to be: there is no hit test
|
||||
/// on a band and no label beside it.
|
||||
std::vector<Rect> invalid_spans;
|
||||
/// The crosshair's geometry, when one is active: the two lines' stroke boxes, the
|
||||
/// readout panel and one row per visible trace with the value of the sample nearest the
|
||||
/// pointer. Placed here rather than derived by the renderer, exactly as the legend and
|
||||
@@ -525,6 +534,21 @@ class Plot {
|
||||
void set_x_note(std::string text);
|
||||
const std::string& x_note() const noexcept { return x_note_; }
|
||||
|
||||
/// Spans of the primary x axis the host has declared invalid -- in udpscope's case a reserved
|
||||
/// hole: samples the stream's counter says exist and that have not been delivered. Each pair
|
||||
/// is `(x_begin, x_end)` in **data** coordinates, `Region`'s convention, and is shaded behind
|
||||
/// the traces and over the grid.
|
||||
///
|
||||
/// Deliberately *not* a `Region`: a region is interactive -- draggable bounds, hot zones, a
|
||||
/// label, a cached statistic, a controller hit test -- and a plot can carry dozens of holes at
|
||||
/// once, which would put a hundred-odd grab targets in the way of every pointer event for
|
||||
/// marks nobody can usefully drag. This is passive shading and nothing else.
|
||||
///
|
||||
/// The span is copied, so the caller's storage need not outlive the call. Spans need not be
|
||||
/// sorted and may overlap; an empty call clears the shading.
|
||||
void set_invalid_spans(std::span<const std::pair<f64, f64>> spans);
|
||||
std::span<const std::pair<f64, f64>> invalid_spans() const noexcept { return invalid_spans_; }
|
||||
|
||||
/// The measurement for `id`. Re-evaluated only when a cursor moved, when the
|
||||
/// measure was added, or when the series underneath it changed: a different
|
||||
/// series, a longer one, or a sliding window whose sample count stayed the same
|
||||
@@ -852,6 +876,7 @@ class Plot {
|
||||
/// region's pixel geometry is derived -- the controller hit-tests what this
|
||||
/// measured instead of mapping the bounds through the transform again.
|
||||
void build_regions();
|
||||
void build_invalid_spans();
|
||||
/// Exact statistics of `region` over `series`, or an invalid result when the
|
||||
/// interval covers no sample. The one place the region arithmetic lives.
|
||||
static RegionStats evaluate_region_stats(const Region& region, const data::Series& series);
|
||||
@@ -899,6 +924,7 @@ class Plot {
|
||||
std::vector<MeasureSlot> measures_;
|
||||
std::vector<Annotation> annotations_;
|
||||
std::vector<Region> regions_;
|
||||
std::vector<std::pair<f64, f64>> invalid_spans_;
|
||||
/// The (region, trace) statistics cache, filled on demand by `region_stats()`
|
||||
/// and so mutable: a lookup on a const Plot may cache, exactly as `measures_`
|
||||
/// does. Entries are created for the pairs a caller actually asks about, never
|
||||
|
||||
@@ -513,6 +513,12 @@ class Renderer {
|
||||
void draw_plot(Plot& plot, const PlotLayout& layout);
|
||||
|
||||
void draw_grid(const Plot& plot);
|
||||
/// `Plot::invalid_spans()`, as `PlotLayout::invalid_spans` measured them: one fill per span in
|
||||
/// `style_.invalid_fill`. Over the grid and under everything else inside the axes, so a hole
|
||||
/// reads as a property of the plot's background -- the grid shows through it and a trace that
|
||||
/// does have data in those columns is drawn on top, which is what distinguishes a reserved
|
||||
/// hole from a trace that has gone quiet.
|
||||
void draw_invalid_spans(const PlotLayout& layout);
|
||||
void draw_axis_lines(const Plot& plot);
|
||||
/// The chrome helpers take the frame's layout: `Plot::layout()` recomputes the
|
||||
/// ticks, the strips and the grid, so it is computed once, in `draw()`, and handed
|
||||
|
||||
@@ -55,6 +55,13 @@ struct Style {
|
||||
Color axis_text = Color::hex(0xC8D0DC);
|
||||
Color cursor = Color::hex(0xFFB020);
|
||||
Color measure_fill = Color::hex(0xFFB020, 0.15f);
|
||||
/// The shading over a span the host has declared invalid (`Plot::set_invalid_spans()`): the
|
||||
/// axis furniture's own neutral grey at a low alpha, on the same model as `measure_fill`
|
||||
/// above. Neutral and not a trace colour on purpose -- a band is the *absence* of data, and
|
||||
/// one in a hue a trace could have would read as a signal rather than as a gap. The alpha is
|
||||
/// low enough that the grid under it stays legible, which is what makes the band read as a
|
||||
/// region of the plot rather than as a panel laid over it.
|
||||
Color invalid_fill = Color::hex(0x8A94A6, 0.18f);
|
||||
/// The crosshair's colour: its two lines and the readout panel's border (spec §15). Markers are
|
||||
/// deliberately *not* this colour -- they carry each trace's own, because a marker says *which*
|
||||
/// trace and the line says *where*.
|
||||
|
||||
Reference in new issue
Block a user