Date: 2026-01-20 Purpose: Evaluate rendering backends for a high-performance plotting library
No. After extensive research, no single library satisfies all requirements:
| Requirement | Needed | Best Available |
|---|---|---|
| C++17/20 | ✅ | Many options |
| Cross-platform (Linux/macOS/Windows) | ✅ | Many options |
| WebAssembly/WebGL | ✅ | Sokol, bgfx, Skia |
| GPU acceleration | ✅ | Many options |
| Millions of points @ 50Hz | ✅ | Datoviz only |
| Qt independence | ✅ | Most options |
| Permissive license | ✅ | Most options |
| All combined | ❌ | None |
The gap: Libraries optimized for millions of points (Datoviz) lack WASM support. Libraries with WASM support (Sokol, bgfx) are rendering abstractions, not plotting libraries.
Conclusion: A custom plotting library built on top of a rendering abstraction (Sokol or bgfx) is the correct approach.
From docs/requirements.md:
- Language: C++17/20
- Build: CMake
- Package Manager: Conan
- Platforms: Linux, macOS, Windows, WebAssembly
- Backends: OpenGL, WebGL, Metal, Vulkan (optional)
- Performance: Millions of points at 50 Hz
- Architecture: Qt-independent core with optional Qt backend
- License: Permissive (MIT, BSD, zlib)
These provide a unified graphics API across multiple backends. You build your plotting logic on top.
| Attribute | Value |
|---|---|
| Repository | https://github.com/floooh/sokol |
| Language | C (C++ compatible) |
| License | zlib/libpng (permissive) ✅ |
| Backends | OpenGL 3.3, OpenGL ES 2/3, WebGL2, D3D11, Metal |
| WebGPU | Experimental backend in development |
| Vulkan | ❌ Not supported |
| WASM | ✅ First-class citizen |
| Compute Shaders | ❌ Not yet (planned) |
Components:
sokol_gfx.h- 3D API wrapper (main rendering)sokol_app.h- Cross-platform window/context/inputsokol_gl.h- OpenGL 1.x-style immediate mode APIsokol_time.h- Time measurementsokol_debugtext.h- Debug text rendering
Pros:
- Explicitly designed for minimal WASM footprint
- Single-header files, trivial to integrate
- Very clean, easy-to-understand API
- Active development by experienced author (Andre Weissflog)
- No external dependencies
- Excellent documentation and examples
Cons:
- No Vulkan backend
- No compute shader support (yet)
- Less battle-tested than bgfx in production games
Why recommended for Splot:
- WASM is a first-class citizen matching your web deployment needs
- Minimal footprint keeps the library lightweight
- Simple API lets you focus on plotting, not graphics plumbing
- WebGPU backend coming for future-proofing
Resources:
- Main repo: https://github.com/floooh/sokol
- Samples: https://github.com/floooh/sokol-samples
- WebGPU backend blog: https://floooh.github.io/2023/10/16/sokol-webgpu.html
| Attribute | Value |
|---|---|
| Repository | https://github.com/bkaradzic/bgfx |
| Language | C/C++ |
| License | BSD-2-Clause (permissive) ✅ |
| Backends | D3D9, D3D11, D3D12, Metal, OpenGL 2.1-4.0, OpenGL ES 2-3.1, Vulkan, WebGL 1/2, WebGPU |
| WASM | ✅ Supported via Emscripten |
| Compute Shaders | ✅ Supported |
| Maturity | 12+ years, used in commercial games |
Pros:
- Most comprehensive backend support (including Vulkan)
- Battle-tested in production (games on Switch, PS4, Xbox)
- Excellent shader cross-compilation tooling (shaderc)
- WebGPU backend available
- Large community and extensive examples
Cons:
- Larger than Sokol
- More complex API ("mid-level" abstraction)
- Heavier integration burden
Why consider for Splot:
- If Vulkan support is mandatory
- If you want the most production-proven solution
- If compute shaders are needed for data processing
Resources:
- Main repo: https://github.com/bkaradzic/bgfx
- Documentation: https://bkaradzic.github.io/bgfx/overview.html
- WebGPU backend blog: https://bkaradzic.github.io/posts/webgpu/
| Attribute | Value |
|---|---|
| wgpu-native | https://github.com/gfx-rs/wgpu-native |
| Dawn (Google) | https://dawn.googlesource.com/dawn |
| Language | C API (Rust or C++ implementation) |
| License | Apache 2.0 / BSD-3-Clause (permissive) ✅ |
| Backends | Vulkan, Metal, D3D12, OpenGL ES (fallback) |
| Browser | Native WebGPU API |
| WASM | ✅ Via browser's WebGPU |
Browser Support (fully available as of late 2025):
- Chrome: April 2023
- Edge: April 2023
- Firefox: July 2025 (v141)
- Safari: June 2025 (v26)
Pros:
- Modern API designed for next-generation graphics
- Same code runs native and in browser
- Strong industry backing (Google, Mozilla, Apple)
- Excellent compute shader support
- Future-proof (expected to replace WebGL)
Cons:
- Younger ecosystem than OpenGL-based solutions
- More verbose than OpenGL for simple 2D
- wgpu-native is Rust with C bindings (not pure C++)
Resources:
- Learn WebGPU for C++: https://eliemichel.github.io/LearnWebGPU/
- wgpu-native releases: https://github.com/gfx-rs/wgpu-native/releases
- gpu.cpp (C++ wrapper): https://github.com/AnswerDotAI/gpu.cpp
| Attribute | Value |
|---|---|
| Repository | https://github.com/DiligentGraphics/DiligentEngine |
| Language | C++ |
| License | Apache 2.0 (permissive) ✅ |
| Backends | D3D11, D3D12, OpenGL, OpenGL ES, Vulkan, Metal, WebGPU |
| WASM | ✅ Via Emscripten |
Pros:
- Modern C++ API designed for next-gen APIs
- Same source code works across all platforms without macros
- Efficient shader resource binding
- Multithreaded command recording
Cons:
- More complex than Sokol/bgfx
- Smaller community
- Heavier weight
Resources:
- Website: https://diligentgraphics.com/diligent-engine/
- Tutorial: https://github.com/DiligentGraphics/DiligentSamples
| Attribute | Value |
|---|---|
| Repository | https://github.com/grimfang4/sdl-gpu |
| Language | C |
| License | MIT (permissive) ✅ |
| Backends | OpenGL 1.1-4.0, OpenGL ES 1.1-3.0 |
| WASM | ✅ Via Emscripten |
Note: SDL3 includes a new GPU API that may supersede this.
Pros:
- Pure C, extremely portable
- Minimal dependencies
- Very small codebase
Cons:
- No Vulkan/Metal
- Less actively maintained than alternatives
These are complete visualization solutions, not just rendering backends.
| Attribute | Value |
|---|---|
| Repository | https://github.com/datoviz/datoviz |
| Documentation | https://datoviz.org/ |
| Language | C/C++ |
| License | MIT (permissive) ✅ |
| Backend | Vulkan only |
| WASM | ❌ Not supported |
Performance Benchmarks (2019 high-end NVIDIA GPU):
| Visualization | Data Size | Frame Rate |
|---|---|---|
| 2D scatter plot | 10M points | 250 FPS |
| 3D mesh | 10M triangles | 400 FPS |
| 1000 signals | 30M vertices total | 200 FPS |
Comparison with Matplotlib:
- Datoviz is up to 10,000× faster
- Matplotlib becomes sluggish or fails with large datasets
Pros:
- Exactly the performance profile you need
- Written by VisPy creator (GPU visualization expert)
- Proven with millions of points
- Clean C API
Cons:
- Vulkan only - no WebGL/WASM support
- More of a complete library than a backend
- Would require significant work to add WebGL backend
Why important for Splot:
- Study Datoviz's architecture and techniques
- Their approach to GPU data upload and rendering is excellent reference material
- IEEE paper: https://ieeexplore.ieee.org/document/9500108/
| Attribute | Value |
|---|---|
| Repository | https://github.com/epezent/implot |
| Language | C++ |
| License | MIT (permissive) ✅ |
| Depends on | Dear ImGui |
| Performance | Tens to hundreds of thousands of points |
Limitations:
- "Don't expect millions to be a buttery smooth experience"
- Requires downsampling for very large datasets
- 16-bit index limit by default (must enable 32-bit)
Pros:
- Excellent immediate-mode API design
- Good reference for plotting API patterns
- Easy integration with ImGui applications
Cons:
- Cannot handle millions of points without downsampling
- Tied to Dear ImGui ecosystem
Experimental GPU backend: Available in backends branch but not production-ready.
| Attribute | Value |
|---|---|
| Repository | https://github.com/alandefreitas/matplotplusplus |
| Language | C++ |
| License | MIT (permissive) ✅ |
| Backends | GnuPlot, experimental OpenGL |
Not recommended: Primarily GnuPlot-based, not suitable for real-time high-performance rendering.
General-purpose 2D rendering, not specialized for data visualization.
| Attribute | Value |
|---|---|
| Repository | https://skia.org/ |
| Language | C++ |
| License | BSD-3-Clause (permissive) ✅ |
| Backends | OpenGL, Vulkan, Metal, CPU, WebGL (CanvasKit) |
| WASM | ✅ Via CanvasKit |
| Used by | Chrome, Android, Flutter, Firefox |
Pros:
- Extremely mature and battle-tested
- Excellent 2D path rendering and antialiasing
- Rich text rendering
- CanvasKit provides WASM support
Cons:
- Large WASM binary (~10MB)
- Not optimized for millions of data points
- Complex build system
- Overkill for plotting (designed for general UI)
| Attribute | Value |
|---|---|
| Repository | https://blend2d.com/ |
| Language | C++ |
| License | zlib (permissive) ✅ |
| Rendering | CPU with JIT optimization |
| GPU | ❌ Not supported |
Not recommended: CPU-only, doesn't meet GPU acceleration requirement.
Critical for your use case - rendering millions of line segments efficiently.
"Drawing lines might not sound like rocket science, but it's damn difficult to do well in OpenGL, particularly WebGL." — Matt DesLauriers
OpenGL GL_LINES limitations:
- No line joins or caps
- Max width often ~10px
- Non-integer widths not supported
- Poor/inconsistent antialiasing
Each line segment = 2 triangles (6 vertices) with:
- Position, width, color as vertex attributes
- Fragment shader for antialiasing
Performance (AMD FirePro W5000):
| Segments | Frame Rate |
|---|---|
| 500,000 | 60 FPS |
| 2,400,000 | Max capacity |
Instead of MSAA, calculate distance to line in fragment shader:
- 100× faster than hardware MSAA
- Better quality
- Works consistently across all GPUs
From your requirements doc - reduces millions of points to ~1000-2000 vertical line segments:
- One vertical line per pixel column
- Line spans min to max Y in that column's X range
- Query any range in O(log n)
- Append new points in O(log n)
Combined approach for Splot:
- Min-Max Tree decimation → ~2000 segments
- Instanced quad rendering → GPU draws segments
- Fragment shader AA → smooth lines
- Drawing Lines is Hard: https://mattdesl.svbtle.com/drawing-lines-is-hard
- Instanced Line Rendering: https://wwwtyro.net/2019/11/18/instanced-lines.html
- GeoJS Implementation: https://www.kitware.com/drawing-lines-in-geojs/
- NVIDIA GPU Gems (Prefiltered Lines): https://developer.nvidia.com/gpugems/gpugems2/part-iii-high-quality-rendering/chapter-22-fast-prefiltered-lines
- Shader-based Polylines (JCGT paper): https://jcgt.org/published/0002/02/08/
| Library | License | WASM | Vulkan | Metal | WebGPU | Size | Maturity |
|---|---|---|---|---|---|---|---|
| Sokol | zlib ✅ | ✅⭐ | ❌ | ✅ | 🔄 | Tiny | Medium |
| bgfx | BSD-2 ✅ | ✅ | ✅ | ✅ | ✅ | Medium | High ⭐ |
| WebGPU | Apache ✅ | ✅⭐ | ✅ | ✅ | ✅⭐ | Medium | Low |
| Diligent | Apache ✅ | ✅ | ✅ | ✅ | ✅ | Large | Medium |
| SDL_gpu | MIT ✅ | ✅ | ❌ | ❌ | ❌ | Tiny | Low |
| Library | License | WASM | Millions pts | Real-time |
|---|---|---|---|---|
| Datoviz | MIT ✅ | ❌ | ✅⭐ | ✅⭐ |
| ImPlot | MIT ✅ | ✅ | ❌ | ✅ |
| Skia | BSD-3 ✅ | ✅ | ❌ | ✅ |
Splot Architecture with Sokol
─────────────────────────────
┌─────────────────────────────────────┐
│ Application │
│ (PlotJuggler or other) │
└──────────────┬──────────────────────┘
│
┌──────────────▼──────────────────────┐
│ Splot Core │ ← Platform-agnostic
│ • Data series interfaces │
│ • Min-Max Tree decimation │
│ • Scale/axis calculations │
│ • Coordinate transforms │
│ • Event abstraction │
└──────────────┬──────────────────────┘
│
┌──────────────▼──────────────────────┐
│ Splot Renderer │ ← GPU rendering logic
│ • Instanced line rendering │
│ • Point/marker shaders │
│ • Text rendering │
│ • Fragment shader AA │
└──────────────┬──────────────────────┘
│
┌──────────────▼──────────────────────┐
│ sokol_gfx.h │ ← Backend abstraction
├─────────────────────────────────────┤
│ GL3.3 │ WebGL2 │ Metal │ D3D11│
└─────────────────────────────────────┘
- WASM-first design - explicitly optimized for web deployment
- Simpler API - focus on plotting logic, not graphics complexity
- Smaller footprint - important for web bundle size
- WebGPU coming - future-proof without switching libraries
- Vulkan backend is mandatory
- Compute shaders needed for data processing
- Maximum production maturity required
- Targeting game consoles
- GPU buffer management patterns
- Efficient data upload strategies
- Vulkan-specific optimizations (if adding Vulkan later)
- Scientific visualization best practices
- Prototype with Sokol - Create minimal line rendering demo
- Implement Min-Max Tree - Core data structure for decimation
- Develop line shaders - Instanced quads with fragment AA
- Benchmark - Verify millions of points at 50Hz is achievable
- Add backends - Qt integration, then evaluate WebGPU
- Sokol: https://github.com/floooh/sokol
- bgfx: https://bkaradzic.github.io/bgfx/overview.html
- Datoviz: https://datoviz.org/
- Learn WebGPU: https://eliemichel.github.io/LearnWebGPU/
- Datoviz IEEE Paper: https://ieeexplore.ieee.org/document/9500108/
- Shader-based Polylines: https://jcgt.org/published/0002/02/08/
- Drawing Lines is Hard: https://mattdesl.svbtle.com/drawing-lines-is-hard
- Instanced Lines: https://wwwtyro.net/2019/11/18/instanced-lines.html
- KDAB Min-Max Tree: https://www.kdab.com/a-speed-up-for-charting-on-embedded/
- Comparison of Graphics Libraries: https://dev.to/funatsufumiya/comparison-of-c-low-level-graphics-cross-platform-frameworks-and-libraries-58e5
- GeoJS Line Drawing: https://www.kitware.com/drawing-lines-in-geojs/
- Datoviz Khronos Blog: https://www.khronos.org/blog/datoviz-ultra-fast-high-performance-gpu-scientific-visualization-library-built-on-vulkan
Source: Code analysis of https://github.com/datoviz/datoviz (cloned locally)
Datoviz achieves 10M points @ 250 FPS through a combination of architectural decisions and GPU-optimized techniques:
File: src/alloc.c
Instead of creating many small Vulkan buffers (expensive), Datoviz allocates a few large shared buffers and sub-allocates regions internally.
// Linked-list based allocator with coalescing
struct DvzAlloc {
DvzSize total_size;
DvzSize alignment;
Block* blocks; // Linked list of free/allocated blocks
DvzSize allocated_size;
};Key features:
- First-fit allocation with alignment support
- Automatic coalescing of adjacent free blocks on deallocation
- Auto-resize (doubles buffer size when full)
- Reduces Vulkan API overhead dramatically
File: src/scene/dual.c
The "Dual" abstraction links CPU arrays with GPU buffers and tracks dirty regions.
struct DvzDual {
DvzBatch* batch;
DvzArray* array; // CPU-side data
DvzId dat; // GPU-side buffer ID
uint32_t dirty_first; // First dirty element
uint32_t dirty_last; // Last dirty element
};Optimization: Only uploads the changed portion of data:
void dvz_dual_update(DvzDual* dual) {
// Only upload dirty range, not entire buffer
DvzSize offset = dual->dirty_first * item_size;
DvzSize size = (dirty_last - dirty_first) * item_size;
dvz_upload_dat(batch, dat, offset, size, data, 0);
}This is critical for streaming data - appending 1000 new points only uploads those 1000 points, not the entire million.
File: src/scene/baker.c
The Baker transforms user data into GPU-optimal vertex buffers before upload. This "baking" step:
- Multiplexes multiple attributes into interleaved vertex buffers
- Handles data repetition (e.g., one color for many vertices)
- Manages index buffers for instanced rendering
// Column-wise data setting with repetition
void dvz_baker_repeat(baker, attr_idx, first, count, repeats, data);
// Quad triangulation (6 vertices per quad)
void dvz_baker_quads(baker, attr_idx, first, count, tl_br);Datoviz renders collections of similar objects in single draw calls:
- 10M points = 1 draw call (not 10M draw calls)
- All attributes (position, color, size) packed into vertex buffers
- GPU processes all points in parallel
File: include/datoviz/scene/glsl/antialias.glsl
Instead of expensive MSAA, Datoviz computes antialiasing analytically in the fragment shader:
vec3 compute_distance(float distance, float linewidth) {
float t = linewidth / 2.0 - antialias;
float border_distance = abs(distance) - t;
float alpha = border_distance / antialias;
alpha = exp(-alpha * alpha); // Gaussian falloff
return vec3(signed_distance, border_distance, alpha);
}
vec4 stroke(float distance, float linewidth, vec4 fg_color) {
vec3 dis = compute_distance(distance, linewidth);
if (dis.y < 0.0)
return fg_color;
else
return vec4(fg_color.rgb, fg_color.a * dis.z); // Alpha blend at edges
}Result: 100× faster than hardware MSAA with better quality.
File: include/datoviz/scene/glsl/markers.glsl, src/scene/glsl/graphics_marker.frag
Markers (circles, squares, stars, etc.) are rendered using Signed Distance Functions:
float marker_disc(vec2 P, float size) {
return length(P) - size / 2;
}
float marker_diamond(vec2 P, float size) {
float x = M_SQRT2 / 2.0 * (P.x - P.y);
float y = M_SQRT2 / 2.0 * (P.x + P.y);
return max(abs(x), abs(y)) - size / (2.0 * M_SQRT2);
}Benefits:
- One quad per marker (4 vertices)
- Shape computed in fragment shader
- Perfect antialiasing at any size
- 20+ marker shapes with no texture overhead
File: src/scene/glsl/graphics_path.vert
For connected lines (paths), the vertex shader:
- Takes 4 consecutive points (p0, p1, p2, p3)
- Computes miter joins at p1-p2 connection
- Expands each segment into a quad
- Handles caps at path endpoints
// Per-segment: 4 vertices forming a quad
// Input: p0 (prev), p1 (start), p2 (end), p3 (next)
vec2 miter_a = normalize(n0 + n1); // Miter at p1
vec2 miter_b = normalize(n1 + n2); // Miter at p2
float length_a = w / dot(miter_a, n1); // Miter lengthFile: src/scene/font.c, shaders use msdfgen
Text uses Multi-channel Signed Distance Fields (MSDF):
- Pre-generated texture atlas of glyphs
- Sharp text at any zoom level
- Single texture lookup per fragment
// MSDF sampling in marker shader
vec3 msd = texture(tex, P + .5).rgb;
float sd = median(msd.r, msd.g, msd.b);
distance = 4 * sd * size_ / params.tex_scale - 2;File: src/transfers.c
CPU-GPU data transfers run on a dedicated thread:
// Separate thread for uploads/downloads
static void* _thread_transfers(void* user_data) {
DvzTransfers* transfers = (DvzTransfers*)user_data;
dvz_deq_dequeue_loop(transfers->deq, DVZ_TRANSFER_PROC_UD);
return NULL;
}Queues:
DVZ_TRANSFER_DEQ_UL- Upload queueDVZ_TRANSFER_DEQ_DL- Download queueDVZ_TRANSFER_DEQ_COPY- GPU-GPU copy queue
This prevents rendering stalls while data is being uploaded.
- Explicit memory management - No driver overhead
- Command buffer recording - Pre-recorded, reusable
- Pipeline caching - Compiled once, reused
- Descriptor sets - Efficient uniform binding
┌─────────────────────────────────────────────────────────┐
│ Scene API │
│ (Figure, Panel, Visual, Transform, Axes) │
├─────────────────────────────────────────────────────────┤
│ Visuals Library │
│ (Marker, Path, Image, Mesh, Glyph, Volume) │
├─────────────────────────────────────────────────────────┤
│ Baker + Dual + Array │
│ (CPU-GPU sync, data transformation, dirty tracking) │
├─────────────────────────────────────────────────────────┤
│ Datoviz Intermediate Protocol │
│ (Message-based requests: create/update/delete) │
├─────────────────────────────────────────────────────────┤
│ Renderer │
│ (Request processor, command recording, presentation) │
├─────────────────────────────────────────────────────────┤
│ vklite │
│ (Thin Vulkan wrapper: buffers, pipelines, commands) │
├─────────────────────────────────────────────────────────┤
│ Vulkan API │
└─────────────────────────────────────────────────────────┘
| Technique | Datoviz Implementation | Splot Adaptation |
|---|---|---|
| Slab Allocation | Custom allocator for GPU buffers | Implement similar for Sokol buffers |
| Dirty Tracking | Dual system with dirty_first/last | Track modified ranges, upload only changes |
| Fragment AA | Gaussian distance falloff | Port shaders to GLSL 330 / WebGL2 |
| SDF Markers | 20+ shapes via distance functions | Implement subset for plotting |
| Batch Rendering | One draw call per visual type | Group points/lines by style |
| MSDF Text | msdfgen atlas generation | Use msdf-atlas-gen or similar |
| Async Transfers | Dedicated transfer thread | Use Sokol's async buffer updates |
- No Min-Max Tree - Datoviz renders all points, relies on raw GPU power
- No LOD/Decimation - No automatic downsampling for large datasets
- No WebGL/WASM - Vulkan only
For Splot, you need to add Min-Max Tree decimation on top of Datoviz-style rendering techniques to handle millions of points efficiently on WebGL.
Repository: https://github.com/memononen/nanovg
NanoVG is a small antialiased 2D vector graphics library with an HTML5 Canvas-like API. It's designed for UI rendering and visualizations.
| Use Case | NanoVG Suitability | Recommendation |
|---|---|---|
| Line Antialiasing | Good but not optimal | Use custom fragment shader AA instead (faster) |
| Text Rendering | Good (stb_truetype) | Consider MSDF for sharper scaling |
| UI Elements | Excellent | Good for legends, labels, tooltips |
| Millions of Points | ❌ Poor | Not designed for this |
Antialiasing Approach:
- Geometry-based AA (expands geometry to include AA fringe)
- Can use MSAA as alternative
- Uses stencil buffer for complex shapes
Backends:
nanovg_gl.h- OpenGL 2.0, 3.2, ES 2.0, ES 3.0, WebGL
Text:
- Uses stb_truetype for font rendering
- Font atlas texture
- Not as sharp as MSDF at extreme scales
Don't use NanoVG for core curve rendering. Here's why:
-
Performance: NanoVG expands geometry for AA, creating many more vertices. For millions of points, this is expensive.
-
Better Alternative: Fragment shader antialiasing (like Datoviz) is:
- 100× faster
- Better quality
- Works with instanced rendering
Consider NanoVG (or similar) for:
- Axis labels and tick marks
- Legend rendering
- Tooltips and annotations
- UI overlays
Better alternatives for text:
- MSDF text rendering (like Datoviz uses) - sharper at all sizes
- Sokol_fontstash - integrates with Sokol
- stb_truetype directly - if you want full control
There's an improved fork with exact coverage antialiasing:
- https://github.com/styluslabs/nanovgXC
- Faster than Skia for some use cases
- Better SDF text support
- Still not suitable for millions of data points
| For | Use |
|---|---|
| Curve rendering | Custom shaders (Datoviz-style) |
| Point rendering | SDF markers in fragment shader |
| Text rendering | MSDF atlas or sokol_fontstash |
| UI/Legends | NanoVG acceptable, but not required |
Final verdict: NanoVG adds complexity without solving your core performance problem. Build custom shaders based on Datoviz patterns instead.
Based on this research, here's the recommended implementation order:
- Set up Sokol integration
- Implement basic line rendering with fragment shader AA
- Implement Min-Max Tree for decimation
- Benchmark: target 1M points @ 60 FPS
- Implement SDF markers (disc, square, triangle, cross)
- Add text rendering (MSDF or sokol_fontstash)
- Implement axis system with tick generation
- Add grid lines
- Pan/zoom with coordinate transforms
- Mouse tracking / crosshair
- Rectangle zoom selection
- Legend system
- Export (PNG, SVG)
- Qt backend integration
- WebAssembly build
- Profile and optimize hot paths
- Add streaming data support
- Implement dirty tracking (Dual-style)
- Multi-curve batching
Decision: Use sokol_fontstash.h (part of Sokol), NOT NanoVG.
Rationale:
- sokol_fontstash uses the same stb_truetype as NanoVG
- Zero additional dependencies (already using Sokol)
- Simpler integration
- Same text quality
Future upgrade path: If sharper text at extreme zooms is needed, migrate to MSDF text rendering (like Datoviz). This is an optimization, not a starting requirement.
NanoVG rejected because:
- Adds unnecessary dependency
- No quality advantage over sokol_fontstash for text
- Geometry-based approach not needed when we have fragment shader AA
| Component | Library | Reason |
|---|---|---|
| Graphics abstraction | Sokol | WASM-first, minimal, clean API |
| Text rendering | sokol_fontstash.h | Part of Sokol, uses stb_truetype |
| Font parsing | stb_truetype | Pulled in by fontstash |
| Math | cglm or custom | Lightweight, header-only |
| Library | Reason for Rejection |
|---|---|
| NanoVG | Redundant with sokol_fontstash, adds complexity |
| Skia | Too large (~10MB WASM), overkill |
| bgfx | More complex than needed (Sokol preferred) |
| ImPlot | Can't handle millions of points |