582 lines
19 KiB
Rust
582 lines
19 KiB
Rust
//! Ticks as data: nice numbers (1, 2 and 5 × 10ᵏ; minutes and hours on long
|
||
//! time axes), one SI prefix per axis, and time written as seconds from an
|
||
//! origin or as a clock (m:ss.fff). Everything writes into inline
|
||
//! [`Label`]s, so an axis is relabelled every frame without allocating.
|
||
//!
|
||
//! A tick's value is computed as an integer times the step's mantissa, scaled
|
||
//! by an exact power of ten, so 0.3 is 3 / 10 and never 0.30000000000000004.
|
||
|
||
use crate::label::Label;
|
||
use core::fmt::Write;
|
||
use libm::{ceil, fabs, floor, log10, round};
|
||
|
||
/// Evenly spaced ticks: tick `i` is `(k0 + i) · mantissa · 10^exp`.
|
||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||
pub struct TickSet {
|
||
/// The first tick's multiple of the step.
|
||
pub k0: i64,
|
||
/// How many ticks.
|
||
pub count: u32,
|
||
/// The step's mantissa: 1, 2 or 5, or a whole number of seconds on a
|
||
/// minute or hour time axis (exp 0).
|
||
pub mantissa: u32,
|
||
/// The step's power of ten.
|
||
pub exp: i32,
|
||
}
|
||
|
||
impl TickSet {
|
||
/// No ticks.
|
||
pub const EMPTY: TickSet = TickSet {
|
||
k0: 0,
|
||
count: 0,
|
||
mantissa: 1,
|
||
exp: 0,
|
||
};
|
||
|
||
/// The step between ticks.
|
||
pub fn step(&self) -> f64 {
|
||
scale10(f64::from(self.mantissa), self.exp)
|
||
}
|
||
|
||
/// Tick `i`'s value (exact to the last bit for decimal steps).
|
||
pub fn value(&self, i: u32) -> f64 {
|
||
let k = self.k0 + i64::from(i);
|
||
scale10(k as f64 * f64::from(self.mantissa), self.exp)
|
||
}
|
||
|
||
/// The first and last tick values, or None when there are none.
|
||
pub fn bounds(&self) -> Option<(f64, f64)> {
|
||
(self.count > 0).then(|| (self.value(0), self.value(self.count - 1)))
|
||
}
|
||
}
|
||
|
||
const POW10: [f64; 23] = [
|
||
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16,
|
||
1e17, 1e18, 1e19, 1e20, 1e21, 1e22,
|
||
];
|
||
|
||
/// 10^|e| exactly up to 10^22 (every power of ten up to there is a double).
|
||
fn pow10_abs(e: i32) -> f64 {
|
||
let a = e.unsigned_abs() as usize;
|
||
if a < POW10.len() {
|
||
POW10[a]
|
||
} else {
|
||
libm::pow(10.0, f64::from(e.unsigned_abs()))
|
||
}
|
||
}
|
||
|
||
/// `x · 10^e`, dividing for negative `e` so the result is correctly rounded.
|
||
pub fn scale10(x: f64, e: i32) -> f64 {
|
||
if e >= 0 {
|
||
x * pow10_abs(e)
|
||
} else {
|
||
x / pow10_abs(e)
|
||
}
|
||
}
|
||
|
||
/// The nice step for `span` split into about `target` intervals: a mantissa
|
||
/// of 1, 2 or 5 and a power of ten.
|
||
pub fn nice_step(span: f64, target: u32) -> (u32, i32) {
|
||
let raw = fabs(span) / f64::from(target.max(1));
|
||
if raw <= 0.0 || !raw.is_finite() {
|
||
return (1, 0);
|
||
}
|
||
let mut e = floor(log10(raw)) as i32;
|
||
let mut f = scale10(raw, -e);
|
||
// log10 can land a hair off at exact powers of ten.
|
||
if f >= 10.0 {
|
||
e += 1;
|
||
f = scale10(raw, -e);
|
||
} else if f < 1.0 {
|
||
e -= 1;
|
||
f = scale10(raw, -e);
|
||
}
|
||
if f < 1.5 {
|
||
(1, e)
|
||
} else if f < 3.5 {
|
||
(2, e)
|
||
} else if f < 7.5 {
|
||
(5, e)
|
||
} else {
|
||
(1, e + 1)
|
||
}
|
||
}
|
||
|
||
fn span_ticks(lo: f64, hi: f64, mantissa: u32, exp: i32) -> TickSet {
|
||
let step = scale10(f64::from(mantissa), exp);
|
||
if !(lo.is_finite() && hi.is_finite() && step > 0.0) || hi < lo {
|
||
return TickSet::EMPTY;
|
||
}
|
||
// A bound within a millionth of a step of a tick counts as on it.
|
||
let k0 = ceil(lo / step - 1e-6) as i64;
|
||
let k1 = floor(hi / step + 1e-6) as i64;
|
||
let count = (k1 - k0 + 1).clamp(0, 10_000) as u32;
|
||
TickSet {
|
||
k0,
|
||
count,
|
||
mantissa,
|
||
exp,
|
||
}
|
||
}
|
||
|
||
/// About `target` nice ticks inside `[lo, hi]`.
|
||
pub fn ticks(lo: f64, hi: f64, target: u32) -> TickSet {
|
||
let (m, e) = nice_step(hi - lo, target);
|
||
span_ticks(lo, hi, m, e)
|
||
}
|
||
|
||
/// `[lo, hi]` widened outward to whole multiples of its nice step, so an
|
||
/// axis begins and ends on a tick.
|
||
pub fn nice_bounds(lo: f64, hi: f64, target: u32) -> (f64, f64) {
|
||
let (m, e) = nice_step(hi - lo, target);
|
||
let step = scale10(f64::from(m), e);
|
||
if !(step > 0.0 && lo.is_finite() && hi.is_finite()) {
|
||
return (lo, hi);
|
||
}
|
||
let a = floor(lo / step + 1e-9);
|
||
let b = ceil(hi / step - 1e-9);
|
||
(scale10(a * f64::from(m), e), scale10(b * f64::from(m), e))
|
||
}
|
||
|
||
/// The SI prefix symbol for a power of ten that is a multiple of 3.
|
||
pub fn si_prefix(exp3: i32) -> &'static str {
|
||
match exp3 {
|
||
-15 => "f",
|
||
-12 => "p",
|
||
-9 => "n",
|
||
-6 => "µ",
|
||
-3 => "m",
|
||
3 => "k",
|
||
6 => "M",
|
||
9 => "G",
|
||
12 => "T",
|
||
15 => "P",
|
||
_ => "",
|
||
}
|
||
}
|
||
|
||
/// The prefix for a magnitude: below 1 the prefix that puts it in 1 … 999
|
||
/// (0.5 → 500 m); from 1 to 9 999 none; from 10 000 up, k, M, G, …
|
||
pub fn si_exp3(maxabs: f64) -> i32 {
|
||
if maxabs <= 0.0 || !maxabs.is_finite() {
|
||
return 0;
|
||
}
|
||
let l = floor(log10(maxabs) + 1e-12) as i32;
|
||
if (0..4).contains(&l) {
|
||
return 0;
|
||
}
|
||
(3 * l.div_euclid(3)).clamp(-15, 15)
|
||
}
|
||
|
||
/// Writes `x` with `decimals` decimals, a true minus sign (U+2212) and no
|
||
/// negative zero.
|
||
fn write_fixed(out: &mut Label, x: f64, decimals: u8) {
|
||
let half = 0.5 * scale10(1.0, -i32::from(decimals));
|
||
let mut x = x;
|
||
if fabs(x) < half {
|
||
x = 0.0;
|
||
}
|
||
if x < 0.0 {
|
||
out.push('−');
|
||
x = -x;
|
||
}
|
||
let _ = write!(out, "{:.*}", usize::from(decimals), x);
|
||
}
|
||
|
||
/// How the numbers on one value axis are written: one SI prefix for the whole
|
||
/// axis (it joins the unit: "mV") and as many decimals as the step needs.
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||
pub struct NumberFormat {
|
||
/// The prefix's power of ten (a multiple of 3; 0 for none).
|
||
pub exp3: i32,
|
||
/// Decimals after the prefix is applied.
|
||
pub decimals: u8,
|
||
}
|
||
|
||
impl NumberFormat {
|
||
/// The format for a tick set: with `si`, a prefix chosen by the largest
|
||
/// magnitude on the axis; decimals from the step.
|
||
pub fn for_ticks(ts: &TickSet, si: bool) -> NumberFormat {
|
||
let exp3 = if si {
|
||
match ts.bounds() {
|
||
Some((a, b)) => si_exp3(fabs(a).max(fabs(b))),
|
||
None => 0,
|
||
}
|
||
} else {
|
||
0
|
||
};
|
||
NumberFormat {
|
||
exp3,
|
||
decimals: (exp3 - ts.exp).clamp(0, 12) as u8,
|
||
}
|
||
}
|
||
|
||
/// The prefix symbol ("m", "k", "" …).
|
||
pub fn prefix(&self) -> &'static str {
|
||
si_prefix(self.exp3)
|
||
}
|
||
|
||
/// Writes `v` (in base units) into `out` after clearing it.
|
||
pub fn write(&self, v: f64, out: &mut Label) {
|
||
out.clear();
|
||
self.append(v, out);
|
||
}
|
||
|
||
/// Appends `v` (in base units) to `out`.
|
||
pub fn append(&self, v: f64, out: &mut Label) {
|
||
if !v.is_finite() {
|
||
out.push('—');
|
||
return;
|
||
}
|
||
write_fixed(out, scale10(v, -self.exp3), self.decimals);
|
||
}
|
||
|
||
/// The same prefix with `extra` more decimals (a readout reads finer than
|
||
/// the axis's ticks).
|
||
pub fn finer(&self, extra: u8) -> NumberFormat {
|
||
NumberFormat {
|
||
exp3: self.exp3,
|
||
decimals: (self.decimals + extra).min(12),
|
||
}
|
||
}
|
||
|
||
/// `prefix + unit` into `out` ("mV"); a unit marked not SI ("dB", "%")
|
||
/// gets no prefix, so the format must then have been made without one.
|
||
pub fn unit_label(&self, unit: &str, out: &mut Label) {
|
||
out.clear();
|
||
out.push_str(self.prefix());
|
||
out.push_str(unit);
|
||
}
|
||
}
|
||
|
||
/// Writes `v` with `sig` significant digits and its own SI prefix (when
|
||
/// `si`) into `out`, returning the prefix's power of ten.
|
||
pub fn write_value(v: f64, sig: u8, si: bool, out: &mut Label) -> i32 {
|
||
out.clear();
|
||
if !v.is_finite() {
|
||
out.push('—');
|
||
return 0;
|
||
}
|
||
let exp3 = if si { si_exp3(fabs(v)) } else { 0 };
|
||
let s = scale10(v, -exp3);
|
||
let l = if s == 0.0 {
|
||
0
|
||
} else {
|
||
floor(log10(fabs(s)) + 1e-12) as i32
|
||
};
|
||
let decimals = (i32::from(sig.max(1)) - 1 - l).clamp(0, 12) as u8;
|
||
write_fixed(out, s, decimals);
|
||
exp3
|
||
}
|
||
|
||
/// How a time axis reads.
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||
pub enum TimeStyle {
|
||
/// Seconds from an origin, signed: a live scope's "−2.5 … 0" before now,
|
||
/// or a triggered sweep's time from its trigger. In s, ms or µs; past two
|
||
/// minutes, −m:ss.
|
||
Relative,
|
||
/// A clock from an origin (a recording's start): m:ss, m:ss.fff, h:mm:ss.
|
||
Clock,
|
||
}
|
||
|
||
/// Steps for long time axes, in seconds.
|
||
const TIME_STEPS: [u32; 15] = [
|
||
10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600, 7200, 10_800, 21_600, 43_200, 86_400,
|
||
];
|
||
|
||
/// About `target` ticks on a time axis from `lo` to `hi` seconds (both
|
||
/// measured from the axis's origin): decimal steps below ten seconds, then
|
||
/// 10 s, 15 s, 30 s, minutes and hours.
|
||
pub fn time_ticks(lo: f64, hi: f64, target: u32) -> TickSet {
|
||
let raw = (hi - lo) / f64::from(target.max(1));
|
||
if !(raw.is_finite() && raw > 0.0) {
|
||
return TickSet::EMPTY;
|
||
}
|
||
if raw < 7.5 {
|
||
return ticks(lo, hi, target);
|
||
}
|
||
let mut best = TIME_STEPS[0];
|
||
let mut err = f64::INFINITY;
|
||
for &s in &TIME_STEPS {
|
||
let d = fabs(libm::log(f64::from(s) / raw));
|
||
if d < err {
|
||
err = d;
|
||
best = s;
|
||
}
|
||
}
|
||
span_ticks(lo, hi, best, 0)
|
||
}
|
||
|
||
/// How the labels of one time axis are written.
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||
pub struct TimeFormat {
|
||
/// The style.
|
||
pub style: TimeStyle,
|
||
/// The seconds' prefix for decimal labels (0, −3 or −6).
|
||
pub exp3: i32,
|
||
/// Decimals (of seconds after the prefix, or of the clock's seconds).
|
||
pub decimals: u8,
|
||
/// Written as a clock: 0 no, 1 m:ss, 2 h:mm:ss.
|
||
pub clock: u8,
|
||
}
|
||
|
||
impl TimeFormat {
|
||
/// The format for a tick set from [`time_ticks`] (values from the origin).
|
||
pub fn for_ticks(ts: &TickSet, style: TimeStyle) -> TimeFormat {
|
||
let (a, b) = ts.bounds().unwrap_or((0.0, 0.0));
|
||
let maxabs = fabs(a).max(fabs(b));
|
||
let clock_decimals = (-ts.exp).clamp(0, 6) as u8;
|
||
let clock_kind = if maxabs >= 3600.0 { 2 } else { 1 };
|
||
match style {
|
||
TimeStyle::Relative if maxabs < 120.0 => {
|
||
let exp3 = si_exp3(maxabs).clamp(-6, 0);
|
||
TimeFormat {
|
||
style,
|
||
exp3,
|
||
decimals: (exp3 - ts.exp).clamp(0, 9) as u8,
|
||
clock: 0,
|
||
}
|
||
}
|
||
_ => TimeFormat {
|
||
style,
|
||
exp3: 0,
|
||
decimals: clock_decimals,
|
||
clock: clock_kind,
|
||
},
|
||
}
|
||
}
|
||
|
||
/// The unit for the axis title: "s", "ms", "µs", "m:ss" or "h:mm:ss".
|
||
pub fn unit(&self) -> &'static str {
|
||
match (self.clock, self.exp3) {
|
||
(1, _) => "m:ss",
|
||
(2, _) => "h:mm:ss",
|
||
(_, -3) => "ms",
|
||
(_, -6) => "µs",
|
||
_ => "s",
|
||
}
|
||
}
|
||
|
||
/// The same format with `extra` more decimals (the cursor's time).
|
||
pub fn finer(&self, extra: u8) -> TimeFormat {
|
||
let cap = if self.clock > 0 { 6 } else { 9 };
|
||
TimeFormat {
|
||
decimals: (self.decimals + extra).min(cap),
|
||
..*self
|
||
}
|
||
}
|
||
|
||
/// Writes `t` seconds from the origin into `out` after clearing it.
|
||
pub fn write(&self, t: f64, out: &mut Label) {
|
||
out.clear();
|
||
if !t.is_finite() {
|
||
out.push('—');
|
||
return;
|
||
}
|
||
if self.clock == 0 {
|
||
write_fixed(out, scale10(t, -self.exp3), self.decimals);
|
||
return;
|
||
}
|
||
// Whole units of 10^-decimals seconds, split without float error.
|
||
let unit = scale10(1.0, -i32::from(self.decimals));
|
||
let mut n = round(t / unit) as i64;
|
||
if n < 0 {
|
||
out.push('−');
|
||
n = -n;
|
||
}
|
||
let per_s = scale10(1.0, i32::from(self.decimals)) as i64;
|
||
let frac = n % per_s;
|
||
let secs = n / per_s;
|
||
let (h, m, s) = (secs / 3600, (secs / 60) % 60, secs % 60);
|
||
if self.clock == 2 {
|
||
let _ = write!(out, "{h}:{m:02}:{s:02}");
|
||
} else {
|
||
let _ = write!(out, "{}:{s:02}", secs / 60);
|
||
}
|
||
if self.decimals > 0 {
|
||
let _ = write!(out, ".{:0width$}", frac, width = usize::from(self.decimals));
|
||
}
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
extern crate std;
|
||
use std::vec::Vec;
|
||
|
||
fn labels_num(ts: &TickSet, f: &NumberFormat) -> Vec<std::string::String> {
|
||
(0..ts.count)
|
||
.map(|i| {
|
||
let mut l = Label::new();
|
||
f.write(ts.value(i), &mut l);
|
||
std::string::String::from(l.as_str())
|
||
})
|
||
.collect()
|
||
}
|
||
|
||
fn labels_time(ts: &TickSet, f: &TimeFormat) -> Vec<std::string::String> {
|
||
(0..ts.count)
|
||
.map(|i| {
|
||
let mut l = Label::new();
|
||
f.write(ts.value(i), &mut l);
|
||
std::string::String::from(l.as_str())
|
||
})
|
||
.collect()
|
||
}
|
||
|
||
#[test]
|
||
fn nice_steps_are_1_2_5() {
|
||
assert_eq!(nice_step(10.0, 5), (2, 0));
|
||
assert_eq!(nice_step(1.0, 5), (2, -1));
|
||
assert_eq!(nice_step(7.0, 5), (1, 0));
|
||
assert_eq!(nice_step(0.003, 4), (1, -3));
|
||
assert_eq!(nice_step(100.0, 10), (1, 1));
|
||
assert_eq!(nice_step(1000.0, 10), (1, 2));
|
||
assert_eq!(nice_step(0.0, 5), (1, 0));
|
||
assert_eq!(nice_step(f64::NAN, 5), (1, 0));
|
||
// Every power of ten, both ways, lands on mantissa 1.
|
||
for e in -12..=12 {
|
||
assert_eq!(nice_step(scale10(5.0, e), 5), (1, e), "10^{e}");
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn tick_values_are_exact() {
|
||
let ts = ticks(0.1, 0.35, 5);
|
||
assert_eq!(ts.step(), 0.05);
|
||
let v: Vec<f64> = (0..ts.count).map(|i| ts.value(i)).collect();
|
||
assert_eq!(v, [0.1, 0.15, 0.2, 0.25, 0.3, 0.35]);
|
||
let ts = ticks(-1.0, 1.0, 5);
|
||
let v: Vec<f64> = (0..ts.count).map(|i| ts.value(i)).collect();
|
||
assert_eq!(v, [-1.0, -0.5, 0.0, 0.5, 1.0]);
|
||
// Bounds a hair inside a tick still include it.
|
||
let ts = ticks(0.30000000000000004, 0.9, 6);
|
||
assert_eq!(ts.value(0), 0.3);
|
||
}
|
||
|
||
#[test]
|
||
fn bounds_widen_to_ticks() {
|
||
assert_eq!(nice_bounds(0.13, 0.87, 5), (0.1, 0.9));
|
||
assert_eq!(nice_bounds(-3.2, 7.9, 5), (-4.0, 8.0));
|
||
assert_eq!(nice_bounds(2.0, 4.0, 4), (2.0, 4.0));
|
||
}
|
||
|
||
#[test]
|
||
fn si_prefixes_per_axis() {
|
||
let ts = ticks(0.0, 0.002, 4);
|
||
let f = NumberFormat::for_ticks(&ts, true);
|
||
assert_eq!(f.prefix(), "m");
|
||
assert_eq!(labels_num(&ts, &f), ["0.0", "0.5", "1.0", "1.5", "2.0"]);
|
||
let ts = ticks(0.0, 50_000.0, 5);
|
||
let f = NumberFormat::for_ticks(&ts, true);
|
||
assert_eq!(f.prefix(), "k");
|
||
assert_eq!(labels_num(&ts, &f), ["0", "10", "20", "30", "40", "50"]);
|
||
let ts = ticks(0.0, 2000.0, 4);
|
||
let f = NumberFormat::for_ticks(&ts, true);
|
||
assert_eq!(f.prefix(), "");
|
||
assert_eq!(labels_num(&ts, &f), ["0", "500", "1000", "1500", "2000"]);
|
||
let ts = ticks(-0.5, 0.5, 4);
|
||
let f = NumberFormat::for_ticks(&ts, true);
|
||
assert_eq!(f.prefix(), "m");
|
||
assert_eq!(labels_num(&ts, &f), ["−400", "−200", "0", "200", "400"]);
|
||
let ts = ticks(-1.0, 1.0, 4);
|
||
let f = NumberFormat::for_ticks(&ts, false);
|
||
assert_eq!(labels_num(&ts, &f), ["−1.0", "−0.5", "0.0", "0.5", "1.0"]);
|
||
let ts = ticks(0.0, 4e-7, 4);
|
||
let f = NumberFormat::for_ticks(&ts, true);
|
||
assert_eq!(f.prefix(), "n");
|
||
let mut u = Label::new();
|
||
f.unit_label("V", &mut u);
|
||
assert_eq!(u, "nV");
|
||
assert_eq!(si_exp3(0.5), -3);
|
||
assert_eq!(si_exp3(0.0005), -6);
|
||
assert_eq!(si_exp3(9999.0), 0);
|
||
assert_eq!(si_exp3(1e7), 6);
|
||
assert_eq!(si_exp3(1e30), 15);
|
||
}
|
||
|
||
#[test]
|
||
fn values_with_significant_digits() {
|
||
let mut l = Label::new();
|
||
assert_eq!(write_value(0.0123456, 4, true, &mut l), -3);
|
||
assert_eq!(l, "12.35");
|
||
assert_eq!(write_value(-1234.6, 4, true, &mut l), 0);
|
||
assert_eq!(l, "−1235");
|
||
assert_eq!(write_value(98_765.0, 3, true, &mut l), 3);
|
||
assert_eq!(l, "98.8");
|
||
write_value(f64::NAN, 3, true, &mut l);
|
||
assert_eq!(l, "—");
|
||
write_value(-0.0, 3, false, &mut l);
|
||
assert_eq!(l, "0.00");
|
||
}
|
||
|
||
#[test]
|
||
fn relative_time_axes() {
|
||
let ts = time_ticks(-30.0, 0.0, 6);
|
||
let f = TimeFormat::for_ticks(&ts, TimeStyle::Relative);
|
||
assert_eq!(f.unit(), "s");
|
||
assert_eq!(
|
||
labels_time(&ts, &f),
|
||
["−30", "−25", "−20", "−15", "−10", "−5", "0"]
|
||
);
|
||
let ts = time_ticks(-0.1, 0.0, 5);
|
||
let f = TimeFormat::for_ticks(&ts, TimeStyle::Relative);
|
||
assert_eq!(f.unit(), "ms");
|
||
assert_eq!(
|
||
labels_time(&ts, &f),
|
||
["−100", "−80", "−60", "−40", "−20", "0"]
|
||
);
|
||
let ts = time_ticks(-0.025, 0.075, 4);
|
||
let f = TimeFormat::for_ticks(&ts, TimeStyle::Relative);
|
||
assert_eq!(labels_time(&ts, &f), ["−20", "0", "20", "40", "60"]);
|
||
let ts = time_ticks(-2.5, 0.0, 5);
|
||
let f = TimeFormat::for_ticks(&ts, TimeStyle::Relative);
|
||
assert_eq!(
|
||
labels_time(&ts, &f),
|
||
["−2.5", "−2.0", "−1.5", "−1.0", "−0.5", "0.0"]
|
||
);
|
||
let ts = time_ticks(-300.0, 0.0, 5);
|
||
let f = TimeFormat::for_ticks(&ts, TimeStyle::Relative);
|
||
assert_eq!(f.unit(), "m:ss");
|
||
assert_eq!(
|
||
labels_time(&ts, &f),
|
||
["−5:00", "−4:00", "−3:00", "−2:00", "−1:00", "0:00"]
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn clock_time_axes() {
|
||
let ts = time_ticks(0.0, 300.0, 5);
|
||
let f = TimeFormat::for_ticks(&ts, TimeStyle::Clock);
|
||
assert_eq!(
|
||
labels_time(&ts, &f),
|
||
["0:00", "1:00", "2:00", "3:00", "4:00", "5:00"]
|
||
);
|
||
let ts = time_ticks(61.25, 61.75, 5);
|
||
let f = TimeFormat::for_ticks(&ts, TimeStyle::Clock);
|
||
assert_eq!(
|
||
labels_time(&ts, &f),
|
||
["1:01.3", "1:01.4", "1:01.5", "1:01.6", "1:01.7"]
|
||
);
|
||
let ts = time_ticks(3600.0, 7200.0, 6);
|
||
let f = TimeFormat::for_ticks(&ts, TimeStyle::Clock);
|
||
assert_eq!(f.unit(), "h:mm:ss");
|
||
assert_eq!(labels_time(&ts, &f)[..3], ["1:00:00", "1:10:00", "1:20:00"]);
|
||
let ts = time_ticks(0.0, 0.01, 5);
|
||
let f = TimeFormat::for_ticks(&ts, TimeStyle::Clock).finer(1);
|
||
let mut l = Label::new();
|
||
f.write(0.0123456, &mut l);
|
||
assert_eq!(l, "0:00.0123");
|
||
// Rounding never shows 60 seconds.
|
||
let f = TimeFormat {
|
||
style: TimeStyle::Clock,
|
||
exp3: 0,
|
||
decimals: 1,
|
||
clock: 1,
|
||
};
|
||
f.write(59.96, &mut l);
|
||
assert_eq!(l, "1:00.0");
|
||
}
|
||
}
|