The repository had no licence at all, so every source was all-rights-reserved by default. Add the verbatim MIT text at LICENSE (Copyright (c) 2026 Martino Ferrari) and a two-line SPDX header to each of the 113 first-party sources -- 48 include/plot, 26 src, 10 adapters, 5 examples, 21 tests, 3 tools -- so the grant travels with each file rather than relying on the root document alone. Scripts keep their shebang on line 1; the SPDX lines follow it. Genuinely vendored code is untouched: third_party/stb_truetype.h carries no header of ours. src/render/embedded_mono_font.cpp is generated, so tools/fonts/gen_atlas.py emit() now writes the two lines into its header comment and the file was regenerated -- gen_atlas.py --check passes and the regeneration adds only those comment lines to the previously committed file. README's provenance section now states MIT and links LICENSE. No code, test or public API change.
175 lines
8.3 KiB
Markdown
175 lines
8.3 KiB
Markdown
# 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 <glfw/glfw_host.hpp> // the optional GLFW adapter
|
|
#include <plot/plot.hpp>
|
|
|
|
#include <cmath>
|
|
#include <vector>
|
|
|
|
int main() {
|
|
plot::glfw::Host host;
|
|
plot::Plot& p = host.plot();
|
|
|
|
std::vector<plot::f32> y(512);
|
|
for (plot::usize i = 0; i < y.size(); ++i)
|
|
y[i] = static_cast<plot::f32>(std::sin(0.05 * static_cast<double>(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** is **MIT licensed**: the full text is in [`LICENSE`](LICENSE), and
|
|
every first-party file (C++, Python and shell) carries its SPDX identifier in the file
|
|
header — `// SPDX-License-Identifier: MIT` — so the grant travels with each source file,
|
|
not just the repository root.
|
|
- **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).
|