79 lines
3.6 KiB
C++
79 lines
3.6 KiB
C++
#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. Negatives keep their sign; a non-finite value falls back to
|
|
/// `"%.3g"`.
|
|
///
|
|
/// The whole value is rounded to a single whole-second count first and every
|
|
/// field is derived from that one number, so no field can ever reach its radix:
|
|
/// 119.5 s is `2:00`, not `1:60`, and 3599.5 s is `1:00:00`.
|
|
///
|
|
/// Shapes, by magnitude: below 1 s `500ms`; below 60 s `1.5s`; below 1 h `1:30`;
|
|
/// at or above 24 h, as a decimal day count `2.5d`. Between 1 h and 24 h the
|
|
/// shape is `1:01:01` -- and because a value that rounds up to 24 h takes the day
|
|
/// shape, the hour field of that shape is always below 24. The 60 s and 1 h
|
|
/// boundaries are inclusive of the rounded value: 59.5 s is `1:00`.
|
|
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
|