# plot An embeddable OpenGL time-series plotting library for real-time, high-sample-rate data. The core (`plot::core`, C++20, OpenGL 3.3 core) has no third-party dependency at all; the host integrations that own a window and an event loop are separate, opt-in targets built on top of it. ## Hosts, and what a frame costs | host | CMake target | source | |---|---|---| | GLFW | `plot::glfw` | `adapters/glfw/` | | Dear ImGui | `plot::imgui` (build-tree only) | `adapters/imgui/` | | Qt6 | `plot::qt6` | `adapters/qt6/` | | GTK4 | `plot::gtk4` | `adapters/gtk4/` | 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 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 run touched a GPU, so read it as an upper bound and a record of the software path. ## Features - grid with independent major/minor line classes (`Style::grid_major_visible`, `grid_minor_visible`); - x and y axes, with a vertical (bottom-to-top) y-axis title; - cursors in both orientations — vertical markers and horizontal rulers; - draggable regions and measurement bands with exact interval statistics; - a signal legend anchored to an axes corner; - 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; - 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 (`plot::font_ttf`). ## Build and install Needs a C++20 compiler and CMake **3.24+**. Presets live in `CMakePresets.json` (`linux-debug`, `linux-release`, `linux-asan`, `linux-coverage`). ```sh cmake --preset linux-release 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: ```cmake find_package(plot 0.1 REQUIRED) target_link_libraries(my_app PRIVATE plot::core) ``` ## 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: ```cpp #include // the optional GLFW adapter #include #include #include int main() { plot::glfw::Host host; plot::Plot& p = host.plot(); std::vector y(512); for (plot::usize i = 0; i < y.size(); ++i) y[i] = static_cast(std::sin(0.05 * static_cast(i))); plot::data::ArraySeries series(std::move(y), 0.0, 1.0); const plot::u32 x = p.add_x_axis({"x", {0.0, 511.0}, true}); const plot::u32 sig = p.add_y_axis({"signal", {-1.0, 1.0}, true, plot::AxisPlace::Left}); p.add_trace({&series, plot::Color::hex(0x4FA3FF), 1.5f, x, sig, "sine"}); plot::Status st; return host.run({800, 480, "plot"}, [](plot::Plot&, plot::f64) {}, &st) ? 0 : 1; } ``` 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): ```sh g++ -std=c++20 -I include -I adapters readme_main.cpp \ build/debug/adapters/glfw/libplot_glfw.a build/debug/libplot_core.a \ $(pkg-config --libs glfw3) -ldl -lpthread -o plot_readme ``` ## Documentation The user guide lives under `docs/`; `docs/superpowers/` is the design record, not user documentation. - [Getting started](docs/getting-started.md) — prerequisites, build/install/consume, and a minimal GLFW program against `plot::core`. - [Architecture](docs/architecture.md) — the layers (`core`/`math` → `gl` → `data` → `render` → `plot` → `input`), what each owns, and the dependency rules. - [Embedding into a host](docs/embedding.md) — the four adapters, the `ui_scale`/DPI rule per host, and the headless demo invocations. - [Data and performance](docs/data-and-performance.md) — the sample stores, the single-producer/single-consumer contract, the LOD pyramid, and the measured frame times. - [Interaction](docs/interaction.md) — cursors, regions, the measurement band, the legend, the crosshair, the gesture table, and live follow. - [Styling](docs/styling.md) — every `Style` field with its default and unit, the grid classes, and installing a text backend. - [Building and testing](docs/building-and-testing.md) — presets, `PLOT_BUILD_*` options, the suite, the headless demo modes, and the coverage command. - [Coverage](docs/coverage.md) — the coverage method and the measured per-file table. ## Testing and coverage ```sh cmake --preset linux-debug cmake --build build/debug ctest --test-dir build/debug --output-on-failure # 283 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 `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. ## Licence and third-party provenance - **This library** ships **no licence file** — there is no `LICENSE`/`COPYING` in the repository, so the sources carry no granted licence and a distributor must add one. - **Adwaita Mono** (the embedded font, `render::EmbeddedMonoFont`) is under **SIL OFL 1.1**, committed verbatim at [`licences/OFL-1.1.txt`](licences/OFL-1.1.txt). The backend class is named neutrally (`EmbeddedMonoFont`, not "Adwaita Mono") because OFL reserves the font name for modified redistributions. The baked atlas is generated at authoring time by `tools/fonts/gen_atlas.py` and committed as data, so `plot_core` rasterises nothing at build or run time. - **stb_truetype v1.26** (public domain, Sean Barrett / RAD Game Tools) is vendored at `third_party/stb_truetype.h` and compiled **only** by the optional `plot_font_ttf` target; its include directory is `PRIVATE`, so no consumer and no other target can reach it. `PLOT_BUILD_TTF_FONT` defaults on exactly when the vendored header is present. - **`plot_core` is third-party-free.** It links only `${CMAKE_DL_LIBS}`, and no public header under `include/plot/` includes a third-party header. The header self-containment and umbrella checks in `tests/CMakeLists.txt` fail the build if either rule breaks. ## Limitations - **Timings are llvmpipe under Xvfb** (`tools/coverage.sh`, the demo summaries). No measurement here used a GPU; there are no hardware numbers. - **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. - **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. - **`render` ↔ `plot` is not a strict include chain.** `Renderer` includes `plot/plot.hpp` because drawing *is* being handed a `Plot`; the `plot` layer names only `render`'s value types (`Style`, `TextBackend`), never `Renderer`. The cycle and its direction are documented in [docs/architecture.md](docs/architecture.md).