pub fn number_of_digits(tick_spacing: f64) -> usize {
let nfrac = -(tick_spacing.log10().floor());
if nfrac < 0.0 { 0 } else { nfrac as usize }
}
pub fn nice_num_generic(value: f64, nice_fractions: Option<&[f64]>, is_round: bool) -> f64 {
if value == 0.0 {
return value;
}
match nice_fractions {
None => {
const NICE: [f64; 4] = [1.0, 2.0, 5.0, 10.0];
let round: [f64; 4] = if is_round {
[1.5, 3.0, 7.0, 10.0]
} else {
NICE
};
nice_num_inner(value, &NICE, &round)
}
Some(nice) => {
let mut round = nice.to_vec();
if is_round {
for i in 0..round.len().saturating_sub(1) {
round[i] = (nice[i] + nice[i + 1]) / 2.0;
}
}
nice_num_inner(value, nice, &round)
}
}
}
fn nice_num_inner(value: f64, nice: &[f64], round: &[f64]) -> f64 {
let highest = *nice.last().expect("nice fractions non-empty");
let expvalue = (value.ln() / highest.ln()).floor();
let frac = value / highest.powf(expvalue);
for (&nice_frac, &round_frac) in nice.iter().zip(round.iter()) {
if frac <= round_frac {
return nice_frac * highest.powf(expvalue);
}
}
highest * highest.powf(expvalue)
}
pub fn nice_num(value: f64, is_round: bool) -> f64 {
nice_num_generic(value, None, is_round)
}
pub fn nice_numbers(vmin: f64, vmax: f64, n_ticks: usize) -> (f64, f64, f64, usize) {
let vrange = nice_num(vmax - vmin, false);
let spacing = nice_num(vrange / n_ticks as f64, true);
let graph_min = (vmin / spacing).floor() * spacing;
let graph_max = (vmax / spacing).ceil() * spacing;
let nfrac = number_of_digits(spacing);
(graph_min, graph_max, spacing, nfrac)
}
pub const TICK_LABELS_PER_INCH: f64 = 1.3;
pub const REFERENCE_DPI: f64 = 92.0;
pub const TICK_LABELS_PER_INCH_MICROSECONDS: f64 = 1.0;
pub fn adaptive_n_ticks(physical_len_px: f64) -> usize {
adaptive_n_ticks_density(physical_len_px, TICK_LABELS_PER_INCH)
}
pub fn adaptive_n_ticks_density(physical_len_px: f64, labels_per_inch: f64) -> usize {
let nticks = (labels_per_inch * physical_len_px / REFERENCE_DPI).round_ties_even();
nticks.max(2.0) as usize
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nice_num_zero_is_zero() {
assert_eq!(nice_num(0.0, false), 0.0);
assert_eq!(nice_num(0.0, true), 0.0);
assert_eq!(
nice_num_generic(0.0, Some(&[1.0, 2.0, 3.0, 12.0]), true),
0.0
);
}
#[test]
fn nice_num_default_round_thresholds_are_inclusive() {
assert_eq!(nice_num(1.5, true), 1.0); assert_eq!(nice_num(3.0, true), 2.0); assert_eq!(nice_num(7.0, true), 5.0); assert_eq!(nice_num(8.0, true), 10.0); }
#[test]
fn nice_num_default_floor_thresholds() {
assert_eq!(nice_num(1.0, false), 1.0);
assert_eq!(nice_num(2.0, false), 2.0);
assert_eq!(nice_num(5.0, false), 5.0);
assert_eq!(nice_num(6.0, false), 10.0);
}
#[test]
fn nice_num_custom_fractions_average_the_round_table() {
let custom = [1.0, 2.0, 5.0, 10.0];
assert_eq!(nice_num_generic(3.2, Some(&custom), true), 2.0); assert_eq!(nice_num(3.2, true), 5.0);
}
#[test]
fn nice_num_custom_base_from_last_fraction() {
let hours = [1.0, 2.0, 3.0, 4.0, 6.0, 12.0];
assert_eq!(nice_num_generic(6.0, Some(&hours), false), 6.0);
assert_eq!(nice_num_generic(5.0, Some(&hours), false), 6.0); }
#[test]
fn number_of_digits_matches_silx() {
assert_eq!(number_of_digits(1.0), 0); assert_eq!(number_of_digits(0.1), 1); assert_eq!(number_of_digits(0.01), 2);
assert_eq!(number_of_digits(0.5), 1); assert_eq!(number_of_digits(10.0), 0); }
#[test]
fn nice_numbers_simple_decade() {
let (gmin, gmax, spacing, nfrac) = nice_numbers(0.0, 10.0, 5);
assert_eq!(gmin, 0.0);
assert_eq!(gmax, 10.0);
assert_eq!(spacing, 2.0);
assert_eq!(nfrac, 0);
}
#[test]
fn nice_numbers_divides_by_n_ticks_not_n_minus_one() {
let (_gmin, _gmax, spacing, _nfrac) = nice_numbers(0.0, 100.0, 5);
assert_eq!(spacing, 20.0);
}
#[test]
fn nice_numbers_fractional_spacing_sets_nfrac() {
let (gmin, gmax, spacing, nfrac) = nice_numbers(0.0, 1.0, 5);
assert_eq!(gmin, 0.0);
assert_eq!(gmax, 1.0);
assert!((spacing - 0.2).abs() < 1e-12);
assert_eq!(nfrac, 1);
}
#[test]
fn adaptive_n_ticks_is_one_point_three_labels_per_92px() {
assert_eq!(adaptive_n_ticks(92.0), 2);
assert_eq!(adaptive_n_ticks(920.0), 13);
assert_eq!(adaptive_n_ticks(600.0), 8);
}
#[test]
fn adaptive_n_ticks_floors_at_two() {
assert_eq!(adaptive_n_ticks(0.0), 2);
assert_eq!(adaptive_n_ticks(10.0), 2); }
#[test]
fn adaptive_n_ticks_rounds_to_nearest_like_silx() {
assert_eq!(adaptive_n_ticks(355.0), 5);
assert_eq!(adaptive_n_ticks(390.0), 6);
}
#[test]
fn adaptive_n_ticks_density_reduces_count_in_microseconds_regime() {
assert_eq!(
adaptive_n_ticks_density(920.0, TICK_LABELS_PER_INCH),
adaptive_n_ticks(920.0)
);
assert_eq!(
adaptive_n_ticks_density(920.0, TICK_LABELS_PER_INCH_MICROSECONDS),
10
);
assert!(
adaptive_n_ticks_density(920.0, TICK_LABELS_PER_INCH_MICROSECONDS)
< adaptive_n_ticks(920.0)
);
assert_eq!(
adaptive_n_ticks_density(10.0, TICK_LABELS_PER_INCH_MICROSECONDS),
2
);
}
}