Files
plotter/README.md
T

8.1 KiB

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).

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:

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:

#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 for the same program written against plot::core directly, with no adapter):

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 — prerequisites, build/install/consume, and a minimal GLFW program against plot::core.
  • Architecture — the layers (core/math → gl → data → render → plot → input), what each owns, and the dependency rules.
  • Embedding into a host — the four adapters, the ui_scale/DPI rule per host, and the headless demo invocations.
  • Data and performance — the sample stores, the single-producer/single-consumer contract, the LOD pyramid, and the measured frame times.
  • Interaction — cursors, regions, the measurement band, the legend, the crosshair, the gesture table, and live follow.
  • Styling — every Style field with its default and unit, the grid classes, and installing a text backend.
  • Building and testing — presets, PLOT_BUILD_* options, the suite, the headless demo modes, and the coverage command.
  • Coverage — the coverage method and the measured per-file table.

Testing and coverage

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, 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. 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.