feat(plot): an axis can write its labels relative to an origin, and time scales by prefix
A host zooming into a sub-second window cannot label it absolutely: Time formatting uses three significant digits, so 12.345671s and 12.345672s are the same string and a 1 ms step around a multi-second time is unlabelled. The axis gains an `origin`: every value it writes is `v - origin`, and the ticks are generated on the shifted grid, so the window reads 0, 500us, 1ms. `origin_follows_range` re-takes the origin from the axis' own range on every refresh -- the mode a *moving* window needs, since the edge is derived from the newest sample at layout time and a value the host wrote a frame earlier is a frame of scroll stale. `format_time` now scales its sub-second values by prefix, the way `format_si` does on a y axis: the unit that keeps the mantissa in [1, 1000). A fixed millisecond unit printed 0.005ms where 5us is the reading, and 1e-05ms where 10ns is. An exact zero prints `0`, the same convention `format_si` states. The origin subtraction's dust is snapped by a *relative* epsilon, so a tick on the origin reads 0 rather than 1.78e-12ms. Tested by `ticks_are_written_relative_to_an_axis_origin`, `a_range_origin_puts_the_zero_tick_on_the_left_edge` and the extended `format_time_shapes`; the suite is 359 cases, 0 failed.
This commit is contained in:
1 parent
211c5b97bc
commit
2dcd1f1864
7 files changed
+197
-14
No files matched your search
+17
-3
@@ -3,18 +3,32 @@
|
||||
|
||||
#include <plot/plot/axis.hpp>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace plot {
|
||||
|
||||
void Axis::format_value(f64 v, char* buf, usize n) const {
|
||||
// The one place a value on this axis becomes text, so the origin is applied here and
|
||||
// everything that goes through it -- tick labels, and the crosshair's readout -- reads in
|
||||
// the same frame of reference as the axis itself.
|
||||
// The subtraction leaves a residue when the value and the origin are the same distant number
|
||||
// (a tick exactly on the origin then formatted as `1.78e-12ms` instead of `0ms`). Snapping it
|
||||
// is a *relative* test, so a genuinely small value on a large axis is not swallowed -- what
|
||||
// this removes is arithmetic dust, not data.
|
||||
f64 shown = v - origin;
|
||||
if (std::fabs(shown) < 1.0e-12 * std::max(1.0, std::fabs(v))) shown = 0.0;
|
||||
if (format == TickFormat::Time) {
|
||||
format_time(v, buf, n);
|
||||
format_time(shown, buf, n);
|
||||
return;
|
||||
}
|
||||
format_si(v, buf, n);
|
||||
format_si(shown, buf, n);
|
||||
}
|
||||
|
||||
void Axis::refresh(f32 px, f32 min_px, bool include_minors) {
|
||||
generate_ticks(range, px, min_px, ticks, include_minors);
|
||||
// The range first, so a following origin is this frame's edge and not the last one's: the
|
||||
// zero tick then sits exactly on the window start, which is the whole point of asking.
|
||||
if (origin_follows_range) origin = range.min;
|
||||
generate_ticks(range, px, min_px, ticks, include_minors, origin);
|
||||
if (format != TickFormat::Time) return;
|
||||
// generate_ticks labels with format_si; a time axis relabels its majors here,
|
||||
// because the tick generator has no business knowing about a clock -- through the one
|
||||
|
||||
Reference in new issue
Block a user