Plotting for C++ that never gets in the way.
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 goingAimed 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.
show(false)returns immediately. The window runs on its own thread. Your program keeps computing, updates the data in place, and callsrefresh()when it has something new to show. There is no event loop to handmain()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 astd::vector, astd::array, a C array or an Armadilloarma::vecgoes 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.
#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
}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.
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.
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.
- Scales.
set_xscale()/set_yscale()takeScale::LogorScale::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, andset_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, andregister_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.
Labels over the plot, with or without an arrow, and math in any string: titles, axis titles, legends, colorbars, tick labels and texts.
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:
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.
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 closedA 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.
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();
}- 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).
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.
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_distThat 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_distLinux (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_distmacOS (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_distThen 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.
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.
| 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 |
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.
Remote rendering, and embedding in Qt, GTK or wxWidgets applications.







