feat(plot): nice-number ticks, axes, grid, traces, cursors, measurement queries

This commit is contained in:
Martino Ferrari committed 2026-09-27 22:21:22 +02:00
1 parent b4e88fe0c1
commit 93fc06f050
18 files changed
+1582

No files matched your search

+68
View File
@@ -0,0 +1,68 @@
#pragma once
#include <string>
#include <vector>
#include <plot/core/types.hpp>
namespace plot {
/// One axis tick. `value` is in data coordinates; `major` marks the labelled
/// ticks (minor ticks carry an empty label); `label` is UTF-8.
struct Tick {
f64 value = 0.0;
bool major = true;
std::string label;
};
/// How an axis renders its tick labels.
enum class TickFormat {
Si, ///< SI-prefixed numbers, e.g. `1.5u`, `2.5G`, `1.23k`.
Time, ///< clock strings, e.g. `1:30`, `1:00:00`.
};
/// Writes an SI-prefixed rendering of `v` to `buf`, always NUL-terminated and
/// never writing past `n` bytes (a too-small buffer truncates, it does not
/// overflow). ASCII plus the UTF-8 micro sign U+00B5 (0xC2 0xB5) for 1e-6, so
/// `plot::render::BitmapFont` renders it instead of mojibake.
///
/// `format_si(0.0)` is exactly `"0"`. Values use three significant digits, so
/// `1234.0` renders as `1.23k`. The sign of a negative value is kept. A value
/// whose engineering exponent falls outside the prefix table (or a label that
/// would exceed 12 characters) falls back to `"%.3g"`, and a non-finite value
/// always does -- `nan`/`inf` are never silently dropped, they are printed by the
/// fallback exactly as `%.3g` prints them.
///
/// `unit` is appended after the prefix when non-null, which is how a caller
/// spells a unit the library cannot know: `format_si(v, buf, n, "\xC2\xB0")` for
/// degrees, `"\xCE\xA9"` for ohms.
void format_si(f64 v, char* buf, usize n, const char* unit = nullptr);
/// Writes `seconds` as a compact clock string to `buf`, NUL-terminated and never
/// past `n` bytes. Shapes: `500ms`, `1.5s`, `1:30`, `1:00:00`, `2.5d`; negatives
/// keep their sign. A non-finite value falls back to `"%.3g"`.
void format_time(f64 seconds, char* buf, usize n);
/// The hard cap on `generate_ticks` output, minor ticks included. A range dense
/// enough to need more is truncated rather than allowed to grow without bound.
inline constexpr usize kMaxTicks = 256;
/// Nice-number ticks for the closed interval `range` on an axis `px` pixels long,
/// with major ticks at least `min_px` pixels apart. Steps are 1/2/5 x 10^n, chosen
/// by rounding the ideal step *up* to the next nice value so major spacing really
/// does meet `min_px`. Ticks are appended to `out`, which is cleared first, in
/// ascending value order and labelled by `format_si`.
///
/// `include_minors` adds, for grid geometry, the minor ticks subdividing each
/// major step by 5 (by 4 when the step's leading digit is 2). Without it `out`
/// holds exactly the labelled major ticks -- the labelled set a caller draws
/// numbers for, with no unlabelled entries to filter. Either way the total is
/// capped at kMaxTicks.
///
/// Hostile input clears `out` and returns: a non-finite endpoint, `max <= min`
/// (a zero or negative span, which includes the collapsed range of a
/// single-sample series), a non-positive `px` or `min_px`, and a span whose step
/// overflows to infinity.
void generate_ticks(Range range, f32 px, f32 min_px, std::vector<Tick>& out,
bool include_minors = false);
} // namespace plot