G
YouTube TikTok Games
Skip to content
BackslashStudioPublic

About

A lightweight C++ plotting library packed with dev-friendly features: stand-alone thread, live cosmetic-control/data-edit, headless/svg output

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

sextant

Plotting for C++ that never gets in the way.

CI Latest release C++20 Windows | Linux | macOS MIT

A wave-interference heatmap, a surface over its own contour map, and the Lorenz attractor, drawn by sextant

sextant is a C++20 library for scientific data visualization. It exists so that looking at your data is as cheap in C++ as it is in Python: a few lines to a window, no notebook, no export step, no separate process.

fig->axes()->line(t, signal, {.color = sextant::Color::Blue, .name = "x(t)"}).grid();
fig->show(false);          // window opens, your thread keeps going

Aimed at engineers who want to see what a simulation is doing while it runs, or to inspect an array during a debugging session, without leaving the C++ ecosystem.


Why you might want it

  • show(false) returns immediately. The window runs on its own thread. Your program keeps computing, updates the data in place, and calls refresh() when it has something new to show. There is no event loop to hand main() over to.
  • Designated initializers instead of a parameter soup. {.color = ..., .linewidth = 2.0f, .name = "fit"}: you name what you set and nothing else, and the compiler checks it.
  • 2D and 3D in one grid. A 3D scene is just another subplot. It holds bars, surfaces, meshes, point clouds, paths, and 2D planes carrying any 2D plot, and orbits, pans and zooms with the mouse.
  • A control panel and a data panel come with the window. Titles, limits, ticks, colours and fonts are editable live; so is the data itself, in shaded tables, with every edit redrawn as you type (guide).
  • What you see is what you save. The window, the PNG and the SVG go through one layout pass, so a saved file matches the screen rather than approximating it. Overlapping translucent 3D geometry is ordered exactly in both files.
  • Stays interactive at scale. A million-point line pans and zooms smoothly; geometry is cached against a data generation counter, so redrawing a static plot re-uploads nothing.
  • Hands your data straight in. Every plot call takes std::span<const double>, so a std::vector, a std::array, a C array or an Armadillo arma::vec goes in as it is, with no conversion on your side.
  • Talks back. Clicks, keys and picks on the plot reach your callbacks, on your own thread, with the data coordinates and the object under the cursor.

Quick start

#include <sextant/sextant.h>
#include <cmath>
#include <vector>

int main() {
    std::vector<double> t(300), signal(300), envelope(300);
    for (int i = 0; i < 300; ++i) {
        t[i]        = i * 0.04;
        envelope[i] = std::exp(-0.25 * t[i]);
        signal[i]   = envelope[i] * std::sin(4.0 * t[i]);
    }

    auto fig = sextant::Figure::create({.width = 900, .height = 480});
    fig->axes()
        ->line(t, signal,   {.color = sextant::Color::Blue, .linewidth = 2.0f,
                             .name = "displacement"})
        .line(t, envelope,  {.color = sextant::Color::Red,
                             .linestyle = sextant::LineStyle::Dashed,
                             .name = "envelope"})
        .set_title("Damped oscillator")
        .set_xtitle("time (s)")
        .set_ytitle("x (m)")
        .legend()
        .grid();

    fig->savefig("quickstart.png");   // or fig->show(); for a live window
}

A damped sine curve with its dashed envelope

2D plots

Six subplots: styled lines with a legend, a series with error bars, a heatmap with labelled contours, a histogram, a bar chart, and a colour-mapped scatter

line Polylines, solid, dashed, dotted or dash-dot, any width, open or closed, optionally stepped. Millions of points.
scatter Sixteen marker shapes, per-series colour and size, or a size per point (bubble charts).
scatter_z Scatter whose colour comes from a third value, with a colorbar.
bar Bar charts, vertical or horizontal, positive or diverging, stacked with bottom.
hist Histograms: binning on top of the same bar plot, with density and cumulative.
heatmap, imshow Colour-mapped matrices over real coordinates or indices, with traced and labelled contour lines, on linear, log or symlog axes.
imshow_rgb RGB and RGBA images, drawn as given.
heatmap_rec Cells of any size, between given edges (matplotlib's pcolormesh).
heatmap_tri Values on a triangle mesh, smoothly shaded (tripcolor).
hist2d Points binned into a heatmap, evenly or by edges.
contour, contourf Contour lines or filled bands of a gridded z, with labels and a stepped colorbar.
fill The area between two curves, or down to a value; Orientation::Vertical fills across. Gaps, steps and crossings handled.
stack Stacked areas with a zero, symmetric or streamgraph baseline.
quiver An arrow per point or per grid cell, optionally coloured through a colormap with a colorbar.
axline, axspan Reference lines and bands across the frame, horizontal, vertical or (lines) at any slope.
shapes Polygons, circles, wedges and rings, in data, frame-fraction or pixel coordinates.
pie Pie and donut charts with labels, percentages and exploded slices.
polar axes add_subplot_polar(): lines, markers, bars, fills and text over θ and r, with a full or partial circle.
error bars A capped whisker for an observed range plus a box for a spread, one- or two-sided, on lines, bars and both scatter kinds.

Drawn in one figure: a confidence band with a reference line and span, a streamgraph, an arrow field, a log-log plot with minor ticks, a second y axis, and stacked horizontal bars.

Six subplots: fill with a line and a span, a streamgraph, a quiver field, a log-log plot, twin y axes, and stacked horizontal bars

Series without a colour take the next one from a ten-colour cycle, so several lines in one axes come out distinct with no styling at all. Objects draw in the order you plot them, whatever their kind, unless a zorder says otherwise. Any of them can be hidden with visible = false (a hidden object also leaves the automatic limits), or removed by the ObjectId its call returned. Colour-mapped kinds take a log, two-slope or boundary norm.

3D plots

Six 3D subplots: bars, a colour-mapped surface, a triangulated mesh, point clouds, a coloured knot, and heatmap slices

auto ax3 = fig->add_subplot3d(1, 2, 2);          // beside a 2D cell in the same grid
ax3->surface(sextant::PlaneOrientation::XY, x, y, heights,
             {.colormap = true, .colorbar = true});
ax3->plane(sextant::PlaneOrientation::XY, -10.0) // a 2D plane inside the scene
   ->heatmap(field, rows, cols, {-3, 3}, {-3, 3});
bar3d Bars on a grid, standing along any axis, stackable with per-bar bases.
surface A sheet over a grid, flat or colour-mapped by height, with an optional wireframe.
surface_tri A sheet on a triangle mesh: give the triangles, or let sextant triangulate scattered points.
scatter3d Markers in space, flat or colour-mapped, with depth shading.
line3d A path through space, flat or colour-mapped along its length, open or closed.
quiver3d An arrow per point in space, flat or colour-mapped.
Plane2D A plane in the scene carrying any 2D plot: heatmaps, lines, scatter, bars, fills, arrows.
error bars Whiskers and boxes on scatter3d and line3d, along any axis.
camera Orthographic or perspective, orbit, pan and zoom, a box of any aspect.

Anything can be translucent: bars through a sheet through a plane are ordered correctly per pixel in the PNG and per polygon in the SVG.

Axes, layout and style

Three axes: log-log with a minor grid, a log y axis, and a symlog y axis

  • Scales. set_xscale() / set_yscale() take Scale::Log or Scale::Symlog; ticks fall on decades, minor ticks and a minor grid are one switch, and pan and zoom move whole decades. set_xtick_format("%.1f") formats every tick label.
  • Several axes in one frame. twinx() / twiny() add a second y (x) axis over the same frame, sharex() / sharey() tie limits and scales across subplots, and set_aspect(1.0) keeps a circle round.
  • Layout. add_subplot(rows, cols, index) grids, cells spanning several columns or rows, adjustable column and row ratios, and a figure sized by the whole or by the plot frame you want. Each subplot's frame fits its own titles, labels and legend.
  • Decoration. Titles, axis titles, legends inside or outside the frame, colorbars on any side, grids, a figure-wide suptitle, explicit ticks and tick labels, axes that cross at any point, per-axis fonts, colours and tick lengths.
  • Colormaps. 26 of them: perceptual (Viridis, Plasma, Inferno, Magma, Cividis, Turbo), sequential, diverging and cyclic ones that wrap. .reversed() flips any, and register_colormap() adds your own.
  • Styling. Figure and plot-area backgrounds (transparent if you like), hidden ticks per axis, an axis switched off entirely, marker outlines and hollow markers.
  • Read-back. Every plot's data, the titles and the limits as drawn can be read back, including what the user edited in the window.
  • Diagnostics. Figure::set_message_handler() routes sextant's warnings to your logger instead of stderr.

Text and math

Labels over the plot, with or without an arrow, and math in any string: titles, axis titles, legends, colorbars, tick labels and texts.

A damped oscillator whose title, axis titles and legend are math, with annotations holding a fraction, a root and an integral, beside a 3D surface with its maximum labelled and its minimum annotated

using sextant::Pos;
ax->set_title(R"(Damped oscillator: $x(t) = e^{-\gamma t}\sin(\omega t)$)")
   .text(R"($\omega_d = \sqrt{\omega^2 - \gamma^2}$)", 8.5, Pos::fraction(0.7))
   .annotate(t_peak, x_peak, "first peak",          // an arrow to the data point
             Pos::fraction(0.6), Pos::fraction(0.8));

Each coordinate is a data value or a fraction of the frame, so a label can follow the data on one axis and stay put on the other. Texts work in 3D too (text, text2d, annotate), keep their pixel size under zoom, and render the same in the window, the PNG and the SVG.

Math is written between dollars, TeX style, and laid out by sextant itself:

Each math string as written beside how it draws: scripts, fractions, roots, accents, sized delimiters, sums and integrals with limits, bold and blackboard letters

Sub- and superscripts, Greek letters and about 150 symbols, \frac and \binom, \sqrt[n]{}, accents (\hat, \vec, \widehat), \left( \right) and \big delimiters sized to their contents, \sum \int \lim with limits, \mathbf, \mathbb, \mathcal, and italic letters as in TeX. FigureOptions::math_fontset draws it in STIX or Computer Modern when installed.

React to the user

Callbacks get the mouse, the keyboard and what was clicked. They run on your thread, inside run(), wait_closed(), poll_events() or dispatch_events(), so they can change the figure freely.

ax->line(x, y, {.name = "fit"});
const sextant::ObjectId fit = ax->last_object();   // the id a pick reports

fig->connect(sextant::EventKind::MouseDown, [](const sextant::Event& e) {
    if (e.has_data) std::printf("clicked at (%g, %g)\n", e.xdata, e.ydata);
});
fig->connect(sextant::EventKind::Pick, [&](const sextant::Event& e) {
    if (e.pick_object == fit) std::printf("picked the fit, point %d\n", e.pick_index);
});
fig->show(false);
sextant::Figure::run();   // deliver events until every window is closed

A pick finds a point, marker or arrow within pick_radius pixels, or the bar, fill, heatmap cell, shape or pie slice the cursor is inside, and names it by its ObjectId.

Watch it run

Plot once, then replace the data in place and publish it. The window keeps up on its own thread; closing it ends the loop.

auto fig = sextant::Figure::create();
auto ax  = fig->axes();
ax->line(x, y);
fig->show(false);

while (!fig->wait_closed(1.0 / 60)) {   // until the window is closed
    simulate_step(y);
    ax->set_line_data(0, x, y);         // keeps titles, limits and styles
    fig->refresh();
}

Output

  • A live window: fig->show(). Pan, zoom, box-zoom with Shift+drag, orbit, hover for values, step back and forward through views, edit the plot and its data, save from the File menu.
  • PNG: fig->savefig("out.png"). Supersampled, at any DPI: {.dpi = 192} writes the same layout at twice the pixels.
  • SVG: fig->savefig("out.svg"). Vector, resolution-independent, 3D included, and written with no OpenGL context at all, so it works on a headless server.
  • In memory: render_png(), render_svg(), render_rgba(). The same bytes the files would hold, or the raw RGBA pixels, for a web server, a GUI or a test.
  • No display needed on Linux. PNG export runs in containers, CI and SSH sessions through EGL (Mesa or the GPU driver at run time).

Installing with vcpkg

sextant is a port in the BackslashStudio registry. Add the registry to your vcpkg-configuration.json:

{
  "default-registry": {
    "kind": "git",
    "repository": "https://github.com/microsoft/vcpkg",
    "baseline": "<a microsoft/vcpkg commit>"
  },
  "registries": [
    {
      "kind": "git",
      "repository": "https://github.com/BackslashStudio/vcpkg-registry",
      "baseline": "<a commit of the registry>",
      "packages": ["sextant"]
    }
  ]
}

depend on sextant in vcpkg.json, and link it as below. Windows, Linux and macOS, shared or static triplets. Optional features freetype, png and window-icon are on by default.

Building from source

You need CMake 3.21 or newer and a C++20 compiler; the window needs OpenGL 4.1. Everything else is vendored or downloaded on the first configure. Each recipe below builds the library and installs it into dist/ beside the source tree.

Windows (MSVC 2022 or later, from a vcvars64 shell)

cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build --target install_dist

That uses sextant's built-in text and PNG fallbacks. For FreeType and libpng, install them with vcpkg and pass its toolchain file; they are linked into sextant.dll:

vcpkg install freetype:x64-windows-static-md libpng:x64-windows-static-md zlib:x64-windows-static-md
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release ^
      -DCMAKE_TOOLCHAIN_FILE=C:/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake
cmake --build build --target install_dist

Linux (GCC or Clang)

sudo apt-get install ninja-build libfreetype-dev libpng-dev libgl-dev     libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxext-dev     libwayland-dev libxkbcommon-dev wayland-protocols
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build --target install_dist

macOS (11 or later, Apple Silicon, with the Command Line Tools)

brew install cmake ninja
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build --target install_dist

Then use it from your project (with vcpkg, skip the first line):

list(APPEND CMAKE_PREFIX_PATH "/path/to/sextant/dist")
find_package(sextant CONFIG REQUIRED)
target_link_libraries(my_app PRIVATE sextant::sextant)

On Windows and macOS the shared library is the only file you need at runtime; on Linux it also uses the system's FreeType and libpng. Build options, other install locations and static linking are in the build guide.

Documentation

doc/api.md is the user guide and full API reference: every method, every option struct, the interactive window, threading rules and error handling.

doc/panels.md is the guide to the Cosmetic and Data panels: a five-minute tour, every group, editing data, and how edits meet your program.

doc/3d.md covers the 3D plot kinds, the camera and planes, and doc/reference.md tabulates every option struct and enum.

Dependencies

License
GLFW 3.4 zlib/libpng fetched and statically linked
Dear ImGui MIT vendored
NanoVG zlib vendored
stb public domain vendored
GLAD MIT / public domain vendored
Roboto Apache-2.0 vendored (panel UI font)
FreeType FTL system (fetched on macOS); optional
libpng libpng system (fetched on macOS); optional

License

MIT; see LICENSE.

sextant bundles and links several third-party components, all permissively licensed. Their notices, and the credit FreeType asks for, are in THIRD_PARTY_NOTICES.md. Ship that file with any binary that embeds sextant.

Not in scope

Remote rendering, and embedding in Qt, GTK or wxWidgets applications.

About

A lightweight C++ plotting library packed with dev-friendly features: stand-alone thread, live cosmetic-control/data-edit, headless/svg output

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages