feat(plot)!: harden pipeline, XY series, dash/markers, touch, packaging
Breaking: Series::gather emits x relative to x0() and takes a GatherMode; consumers reconstruct absolute x in f64 before mapping (series.hpp documents the contract). Bugs, each pinned by a fail-before regression test: - render: f32 staging quantised epoch-time x into 128 px columns; every data-space draw now stages to_space(x) - to_space(origin) with the origin folded into the offset uniform in f64 (epoch/zero-origin twins rasterise identically) - ticks: major test was ULP-fragile and blanked epoch axes; majors are an integer-index test and are never truncated by the minor cap - gather: LOD envelope emitted buckets straddling [begin,end); ceil/floor bucket edges + raw lead/trail spans - live follow: log x axes held a linear span and zoomed every frame; span and offset now live in axis space (plot + both figure link-group paths) - input: a zero-notch wheel disarmed auto_scale on every axis; dy==0 pans x, factor == 1 / inf refused in wheel and drag zoom - render: the single text flush painted legend names over the crosshair readout panel; two batches, under flushed before the panel - data: NaN dropped from min/max unless it seeded the fold; non-finite samples are now excluded from every aggregate and from cached bounds Robustness: viewport normalisation (no clamp UB), non-finite range refusal, pyramid level cap at 57 (no shift/div UB through public gather), rounded ring capacity documented + pre-sized, GL blend func and UNPACK_ALIGNMENT save/restore, Renderer::release_gl() teardown, u64 buffer epochs, annotation captions clamped, button-aware GLFW row capture, cached figure row rects (no per-event layout), ruler-pair measurement refused, span-nonfinite guards in axis_ends. Features: XY series (sorted x, binary-search neighbours/snap, LOD over sample index); StreamSeries::freeze(); ArraySeries::push(); per-trace dash and markers (scatter at width 0) with auto or literal stride; GatherMode Envelope/Nearest/Mean via bucket_sum/sumsq; Style::line_aa_px; per-glyph text colour (readout values in trace colour); wheel-dx x pan; arrow-key pan; Escape cancels a drag and restores captured ranges; modifier-masked box zoom (Shift both, Ctrl x, Alt y); double-click reset via PointerEvent::time_s; ImGui key repeat unified; pinch gestures on Qt and GTK; pixelDelta wheel path and DevicePixelRatioChange handling; GNUInstallDirs, SOVERSION, SameMinorVersion, generated plot/version.hpp, relocatable plot.pc, licence install, FILES_MATCHING, GitHub Actions CI (debug/coverage/asan). Suite 283 -> 350 cases, 0 failed; coverage 93.9% (3730/3971) over 24 files, floor 80/90 held; ASAN preset suite green; all four demos run headless (the GTK scripted-resize check needs a window manager and fails identically on the parent commit); readme_main.cpp committed and compiles with the README command; pkg-config and find_package consumption verified from a relocated install prefix.
This commit is contained in:
1 parent
f132a5ec92
commit
38ef459897
77 files changed
+5905
-688
No files matched your search
@@ -16,7 +16,7 @@ built on top of it.
|
||||
|
||||
Per-frame rendering cost is O(visible pixels + samples produced since the last frame),
|
||||
not O(samples): a trace denser than one sample per pixel is drawn from a min/max LOD
|
||||
pyramid rather than from raw samples. The `plot_multiplot_demo` run stages **18,651 GPU
|
||||
pyramid rather than from raw samples. The `plot_multiplot_demo` run stages **20,901 GPU
|
||||
segments** to draw **~4.5 million visible samples** across six traces — the frame is
|
||||
bounded by the pixels it lights, not by the sample count behind them. That figure is from
|
||||
a `linux-release` build under `xvfb-run` on **llvmpipe** (Mesa's software rasteriser); no
|
||||
@@ -33,6 +33,21 @@ run touched a GPU, so read it as an upper bound and a record of the software pat
|
||||
- a hover crosshair with a per-signal, interpolated readout;
|
||||
- a multi-plot `Figure` of stacked rows sharing one synchronised x axis;
|
||||
- live follow (`XFollow::Live`) with relative (multiplicative) zoom and mouse panning;
|
||||
- XY series for irregularly sampled data (`plot/data/xy_series.hpp`): sorted-ascending x
|
||||
with binary-searched crosshair, measurement and index snapping;
|
||||
- per-trace draw modes: dash patterns (`Trace::dash_on`/`dash_off`, logical px, 0 = solid)
|
||||
and point markers (`Trace::marker`: cross, circle, square) with a manual
|
||||
`marker_every` stride or an automatic one; `width == 0` draws the markers alone
|
||||
(scatter);
|
||||
- three downsample policies per trace (`Trace::mode`, `GatherMode`): `Envelope`
|
||||
(min/max pairs — the default), `Nearest` (one raw sample per bucket) and `Mean`
|
||||
(one averaged point per bucket);
|
||||
- readout text colour: crosshair value rows render in their row's trace colour, labels in
|
||||
the axis text colour;
|
||||
- input parity across hosts: pinch-zoom and pan gestures on Qt6 and GTK4 drive the same
|
||||
controller paths as mouse and wheel (touch parity), with keyboard panning
|
||||
(arrow keys), Escape cancelling a drag or box-zoom, double-click re-fitting the axes,
|
||||
and modifier-masked box zoom (Shift = both axes, Ctrl = x only, Alt = y only);
|
||||
- DPI-consistent sizing: metrics are logical units converted once through
|
||||
`Style::ui_scale`;
|
||||
- an anti-aliased embedded monospaced font, plus an optional runtime TrueType backend
|
||||
@@ -49,20 +64,32 @@ cmake --build build/release
|
||||
cmake --install build/release --prefix /where/you/want
|
||||
```
|
||||
|
||||
Install is on by default (`PLOT_INSTALL=ON`) and installs the `plot_core` archive, the
|
||||
public headers under `include/plot/`, and a CMake package config under `lib/cmake/plot/`.
|
||||
A consumer then needs only:
|
||||
Install is on by default (`PLOT_INSTALL=ON`) and, via `GNUInstallDirs`, installs the
|
||||
`plot_core` archive under `lib/`, the public headers under `include/plot/` — including the
|
||||
generated `plot/version.hpp` — a CMake package config under `lib/cmake/plot/`, a pkg-config
|
||||
file under `lib/pkgconfig/plot.pc`, and the licences under `share/licenses/plot/`. A
|
||||
consumer then needs only:
|
||||
|
||||
```cmake
|
||||
find_package(plot 0.1 REQUIRED)
|
||||
target_link_libraries(my_app PRIVATE plot::core)
|
||||
```
|
||||
|
||||
**Versioning.** The package config carries **SameMinorVersion** compatibility: at 0.x the
|
||||
major component promises nothing and the minor is what moves when the surface breaks, so
|
||||
`find_package(plot 0.1 REQUIRED)` accepts 0.1.x and refuses anything newer. A shared build
|
||||
(`BUILD_SHARED_LIBS=ON`) names its ABI on the file itself — `libplot_core.so.0`, the
|
||||
SOVERSION being the major component. What a header set *is* is in its generated
|
||||
`<plot/version.hpp>`: `plot::version::major`/`minor`/`patch`/`string`, or the
|
||||
`PLOT_VERSION_*` macros for a preprocessor test. The full layout is tabled in
|
||||
[docs/building-and-testing.md](docs/building-and-testing.md).
|
||||
|
||||
## Embedding
|
||||
|
||||
A host owns the window, the GL context and the event loop; `plot` owns none of them. This
|
||||
is the whole of a minimal GLFW program — the adapter opens the window, runs the loop and
|
||||
feeds the controller, and the callback is where live data would be pushed:
|
||||
feeds the controller, and the callback is where live data would be pushed. It is committed
|
||||
at the repository root as `readme_main.cpp`:
|
||||
|
||||
```cpp
|
||||
#include <glfw/glfw_host.hpp> // the optional GLFW adapter
|
||||
@@ -90,10 +117,12 @@ int main() {
|
||||
```
|
||||
|
||||
It compiles and links against a built tree (see [docs/getting-started.md](docs/getting-started.md)
|
||||
for the same program written against `plot::core` directly, with no adapter):
|
||||
for the same program written against `plot::core` directly, with no adapter). The one
|
||||
non-obvious flag is `-I build/debug/include`: that is where CMake generates
|
||||
`plot/version.hpp`, which the umbrella pulls in:
|
||||
|
||||
```sh
|
||||
g++ -std=c++20 -I include -I adapters readme_main.cpp \
|
||||
g++ -std=c++20 -I include -I adapters -I build/debug/include readme_main.cpp \
|
||||
build/debug/adapters/glfw/libplot_glfw.a build/debug/libplot_core.a \
|
||||
$(pkg-config --libs glfw3) -ldl -lpthread -o plot_readme
|
||||
```
|
||||
@@ -124,18 +153,18 @@ documentation.
|
||||
```sh
|
||||
cmake --preset linux-debug
|
||||
cmake --build build/debug
|
||||
ctest --test-dir build/debug --output-on-failure # 283 cases, 0 failed
|
||||
ctest --test-dir build/debug --output-on-failure # 350 cases, 0 failed
|
||||
|
||||
tools/coverage.sh # configure+build+run+capture+floor check
|
||||
```
|
||||
|
||||
The `linux-debug` suite runs **283 cases**; the `linux-coverage` preset builds **272** of
|
||||
them, because its adapters are off and 11 cases need an adapter target (8 in
|
||||
`test_imgui_widget.cpp`, 3 in `test_glfw_host.cpp`). Coverage is enforced by
|
||||
The `linux-debug` suite runs **350 cases**; the `linux-coverage` preset builds **333** of
|
||||
them, because its adapters are off and 17 cases need an adapter target (11 in
|
||||
`test_imgui_widget.cpp`, 6 in `test_glfw_host.cpp`). Coverage is enforced by
|
||||
`tools/coverage.sh` against `tools/coverage_floor.py`: **80%** per file under `src/` and
|
||||
**90%** aggregate. The current measurement is an aggregate **95.3% (3284/3447 lines)** over
|
||||
23 files, every file at or above 80%; the per-file table is in
|
||||
[docs/coverage.md](docs/coverage.md), which the command regenerates.
|
||||
**90%** aggregate. The measured aggregate and per-file table live in
|
||||
[docs/coverage.md](docs/coverage.md), which the command regenerates — it is the single
|
||||
source for those numbers, so this page carries none.
|
||||
|
||||
## Licence and third-party provenance
|
||||
|
||||
@@ -164,7 +193,12 @@ them, because its adapters are off and 11 cases need an adapter target (8 in
|
||||
- **macOS and Windows are untested.** Development and CI are Linux/EGL + X11 only.
|
||||
- **Fractional-scale compositing and touchpad scroll calibration are unverified** — the
|
||||
`ui_scale` sources are correct per host, but no run exercised a real fractional DPI or a
|
||||
calibrated scroll device.
|
||||
calibrated scroll device. The new gesture paths (Qt6 and GTK4 pinch, the keyboard and
|
||||
wheel paths behind the parity list above) are tested at the toolkit-free seams and
|
||||
headless; no run has touched real touch hardware.
|
||||
- **Figure row rects are one frame stale during a resize.** The hosts cache
|
||||
`Figure::layout()`'s rows and refresh them once per frame, so an event arriving mid-resize
|
||||
routes against the previous frame's rects (see [docs/embedding.md](docs/embedding.md)).
|
||||
- **Untrusted font files are out of scope** for the runtime `plot::font_ttf` backend:
|
||||
stb_truetype does no range checking, so `from_file()` must only be handed files the host
|
||||
trusts.
|
||||
|
||||
Reference in new issue
Block a user