feat(plot)!: harden pipeline, XY series, dash/markers, touch, packaging
Breaking: Series::gather emits x relative to x0() and takes a GatherMode; consumers reconstruct absolute x in f64 before mapping (series.hpp documents the contract). Bugs, each pinned by a fail-before regression test: - render: f32 staging quantised epoch-time x into 128 px columns; every data-space draw now stages to_space(x) - to_space(origin) with the origin folded into the offset uniform in f64 (epoch/zero-origin twins rasterise identically) - ticks: major test was ULP-fragile and blanked epoch axes; majors are an integer-index test and are never truncated by the minor cap - gather: LOD envelope emitted buckets straddling [begin,end); ceil/floor bucket edges + raw lead/trail spans - live follow: log x axes held a linear span and zoomed every frame; span and offset now live in axis space (plot + both figure link-group paths) - input: a zero-notch wheel disarmed auto_scale on every axis; dy==0 pans x, factor == 1 / inf refused in wheel and drag zoom - render: the single text flush painted legend names over the crosshair readout panel; two batches, under flushed before the panel - data: NaN dropped from min/max unless it seeded the fold; non-finite samples are now excluded from every aggregate and from cached bounds Robustness: viewport normalisation (no clamp UB), non-finite range refusal, pyramid level cap at 57 (no shift/div UB through public gather), rounded ring capacity documented + pre-sized, GL blend func and UNPACK_ALIGNMENT save/restore, Renderer::release_gl() teardown, u64 buffer epochs, annotation captions clamped, button-aware GLFW row capture, cached figure row rects (no per-event layout), ruler-pair measurement refused, span-nonfinite guards in axis_ends. Features: XY series (sorted x, binary-search neighbours/snap, LOD over sample index); StreamSeries::freeze(); ArraySeries::push(); per-trace dash and markers (scatter at width 0) with auto or literal stride; GatherMode Envelope/Nearest/Mean via bucket_sum/sumsq; Style::line_aa_px; per-glyph text colour (readout values in trace colour); wheel-dx x pan; arrow-key pan; Escape cancels a drag and restores captured ranges; modifier-masked box zoom (Shift both, Ctrl x, Alt y); double-click reset via PointerEvent::time_s; ImGui key repeat unified; pinch gestures on Qt and GTK; pixelDelta wheel path and DevicePixelRatioChange handling; GNUInstallDirs, SOVERSION, SameMinorVersion, generated plot/version.hpp, relocatable plot.pc, licence install, FILES_MATCHING, GitHub Actions CI (debug/coverage/asan). Suite 283 -> 350 cases, 0 failed; coverage 93.9% (3730/3971) over 24 files, floor 80/90 held; ASAN preset suite green; all four demos run headless (the GTK scripted-resize check needs a window manager and fails identically on the parent commit); readme_main.cpp committed and compiles with the README command; pkg-config and find_package consumption verified from a relocated install prefix.
This commit is contained in:
1 parent
f132a5ec92
commit
38ef459897
77 files changed
+5905
-688
No files matched your search
+79
-36
@@ -1,8 +1,19 @@
|
||||
cmake_minimum_required(VERSION 3.24)
|
||||
project(plot VERSION 0.1.0 LANGUAGES CXX)
|
||||
project(plot VERSION 0.1.0
|
||||
DESCRIPTION "An embeddable OpenGL time-series plotting library for real-time, high-sample-rate data"
|
||||
LANGUAGES CXX)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
include(plot_options)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
# The version header: generated into the build tree at configure time, from the
|
||||
# template beside it. It is a public header -- the umbrella names it, the
|
||||
# self-containment and umbrella checks in tests/ hold it to the same rules, and
|
||||
# the install rules place it under include/plot/ with the rest of the surface --
|
||||
# so a consumer can ask what it compiled against (see include/plot/version.hpp.in).
|
||||
configure_file(include/plot/version.hpp.in
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/include/plot/version.hpp" @ONLY)
|
||||
|
||||
add_library(plot_core
|
||||
src/core/status.cpp
|
||||
@@ -11,6 +22,7 @@ add_library(plot_core
|
||||
src/data/series.cpp
|
||||
src/data/array_series.cpp
|
||||
src/data/stream_series.cpp
|
||||
src/data/xy_series.cpp
|
||||
src/gl/loader.cpp
|
||||
src/gl/handle.cpp
|
||||
src/render/shaders.cpp
|
||||
@@ -36,36 +48,32 @@ add_library(plot::core ALIAS plot_core)
|
||||
# and every consumer, and this project's own comments -- says plot::core.
|
||||
set_target_properties(plot_core PROPERTIES EXPORT_NAME core)
|
||||
|
||||
# The shared-library case names its file versions: the dynamic loader follows
|
||||
# the SONAME (libplot_core.so.<SOVERSION>, the major component), so an
|
||||
# ABI-breaking release moves it by bumping project()'s major. The static
|
||||
# archive -- the default, and the only shape the presets build -- carries no
|
||||
# such name, and setting the properties on it would be noise.
|
||||
if(BUILD_SHARED_LIBS)
|
||||
set_target_properties(plot_core PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR})
|
||||
endif()
|
||||
|
||||
target_include_directories(plot_core PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
# The generated version header lives here, not under the source root (see
|
||||
# configure_file above); everything that compiles against plot::core reaches
|
||||
# it through this interface, including the header checks in tests/.
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>)
|
||||
target_compile_features(plot_core PUBLIC cxx_std_20)
|
||||
target_link_libraries(plot_core PRIVATE ${CMAKE_DL_LIBS})
|
||||
plot_apply_warnings(plot_core)
|
||||
|
||||
# The optional runtime-font backend: plot::text::TrueTypeFont, which bakes a real
|
||||
# TrueType/OpenType file into a fixed-cell atlas at run time (see
|
||||
# include/plot/text/true_type_font.hpp). It is the one target that compiles the
|
||||
# vendored third_party/stb_truetype.h, so it is opt-in and separable -- the core
|
||||
# library, its public headers and its tests build without it -- and its default
|
||||
# follows the vendored header's presence: a tree that has the file wants the
|
||||
# feature, and one that does not cannot have it. Asking for it explicitly without
|
||||
# the header is an error rather than a silent downgrade, because the option is
|
||||
# then the only thing that would have said so.
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/stb_truetype.h")
|
||||
set(PLOT_VENDORED_STB_TRUETYPE TRUE)
|
||||
else()
|
||||
set(PLOT_VENDORED_STB_TRUETYPE FALSE)
|
||||
endif()
|
||||
option(PLOT_BUILD_TTF_FONT
|
||||
"Build the optional plot::text::TrueTypeFont backend (vendored stb_truetype)"
|
||||
${PLOT_VENDORED_STB_TRUETYPE})
|
||||
if(PLOT_BUILD_TTF_FONT AND NOT PLOT_VENDORED_STB_TRUETYPE)
|
||||
message(FATAL_ERROR
|
||||
"plot: PLOT_BUILD_TTF_FONT=ON needs third_party/stb_truetype.h, which this tree does "
|
||||
"not have. Restore it -- include/plot/text/true_type_font.hpp records the version and "
|
||||
"the source URL it came from -- or configure with -DPLOT_BUILD_TTF_FONT=OFF.")
|
||||
endif()
|
||||
# The optional runtime-font backend: plot::text::TrueTypeFont. The option and
|
||||
# its guard live in cmake/plot_options.cmake with every other PLOT_* option;
|
||||
# what is left here is the one thing an option cannot do for itself: adding the
|
||||
# target. See the option's comment there for why it is opt-in and separable.
|
||||
if(PLOT_BUILD_TTF_FONT)
|
||||
add_subdirectory(src/text)
|
||||
endif()
|
||||
@@ -162,35 +170,70 @@ if(PLOT_INSTALL)
|
||||
# asking for it twice puts the same directory in the export twice.
|
||||
install(TARGETS plot_core ${PLOT_ADAPTER_EXPORT_TARGETS}
|
||||
EXPORT plotTargets
|
||||
ARCHIVE DESTINATION lib
|
||||
LIBRARY DESTINATION lib
|
||||
RUNTIME DESTINATION bin)
|
||||
# The optional backend's header goes in only when that backend was built: an
|
||||
# installed header with no library behind it is a promise the prefix cannot keep,
|
||||
# and which configuration a prefix came from is not something a consumer can see.
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
# The public headers, and the generated version header with them. The
|
||||
# directory rule takes the source tree (FILES_MATCHING, so the version.hpp.in
|
||||
# template -- a .in, not a .hpp -- cannot travel); the generated
|
||||
# include/plot/version.hpp is not under the source root, so it goes in by
|
||||
# name. The optional backend's header goes in only when that backend was
|
||||
# built: an installed header with no library behind it is a promise the
|
||||
# prefix cannot keep, and which configuration a prefix came from is not
|
||||
# something a consumer can see.
|
||||
if(PLOT_BUILD_TTF_FONT)
|
||||
install(DIRECTORY include/plot DESTINATION include)
|
||||
install(DIRECTORY include/plot
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
FILES_MATCHING PATTERN "*.hpp")
|
||||
else()
|
||||
install(DIRECTORY include/plot DESTINATION include
|
||||
install(DIRECTORY include/plot
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
FILES_MATCHING PATTERN "*.hpp"
|
||||
PATTERN "true_type_font.hpp" EXCLUDE)
|
||||
endif()
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/include/plot/version.hpp"
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/plot)
|
||||
|
||||
# The licences that travel with the installed files: this library's MIT text,
|
||||
# and the OFL 1.1 text for the embedded font the headers bake in, so a
|
||||
# redistributor of the prefix holds both grants without a second checkout.
|
||||
install(FILES LICENSE licences/OFL-1.1.txt
|
||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/plot)
|
||||
|
||||
# pkg-config metadata, for consumers whose build is not CMake. find_package()
|
||||
# and the package config below remain the primary interface; see plot.pc.in.
|
||||
set(PLOT_PC_LIBDIR "${CMAKE_INSTALL_LIBDIR}")
|
||||
set(PLOT_PC_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
set(PLOT_PC_LIB "-lplot_core")
|
||||
set(PLOT_PC_LIBS_PRIVATE "")
|
||||
foreach(plot_pc_lib IN LISTS CMAKE_DL_LIBS)
|
||||
list(APPEND PLOT_PC_LIBS_PRIVATE "-l${plot_pc_lib}")
|
||||
endforeach()
|
||||
string(REPLACE ";" " " PLOT_PC_LIBS_PRIVATE "${PLOT_PC_LIBS_PRIVATE}")
|
||||
configure_file(plot.pc.in "${CMAKE_CURRENT_BINARY_DIR}/plot.pc" @ONLY)
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/plot.pc"
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||
|
||||
install(EXPORT plotTargets
|
||||
NAMESPACE plot::
|
||||
FILE plotTargets.cmake
|
||||
DESTINATION lib/cmake/plot)
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/plot)
|
||||
|
||||
configure_package_config_file(
|
||||
cmake/plotConfig.cmake.in
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/plotConfig.cmake"
|
||||
INSTALL_DESTINATION lib/cmake/plot)
|
||||
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/plot)
|
||||
# SameMinorVersion, deliberately: the project is 0.x, where the major
|
||||
# component carries no ABI promise and the minor is what moves when one
|
||||
# breaks, so a consumer asking for 0.1 gets 0.1.x and nothing newer. It goes
|
||||
# up to SameMajorVersion when the version leaves 0.
|
||||
write_basic_package_version_file(
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/plotConfigVersion.cmake"
|
||||
VERSION ${PROJECT_VERSION}
|
||||
COMPATIBILITY SameMajorVersion)
|
||||
COMPATIBILITY SameMinorVersion)
|
||||
|
||||
install(FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/plotConfig.cmake"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/plotConfigVersion.cmake"
|
||||
DESTINATION lib/cmake/plot)
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/plot)
|
||||
endif()
|
||||
Reference in new issue
Block a user