use super::*;
#[test]
fn lower_edges_open_their_bin_on_every_scale() {
for scale in [Scale::Log, Scale::Sqrt, Scale::Linear] {
let b = Binning::new(scale, 5000.0, true);
for k in 1..60 {
let e = b.lower_edge(k);
assert!(b.key(e as f64) >= k, "{scale:?} k={k} e={e}");
assert!(e == 0 || b.key((e - 1) as f64) < k, "{scale:?} k={k} e={e}");
}
}
}
#[test]
fn continuous_bins_cover_the_range() {
for scale in [Scale::Log, Scale::Sqrt, Scale::Linear] {
let vals = [0.0f32, 0.5, 3.2, 18.0];
let binned = Binned::new(&vals, scale);
assert!(binned.counts.len() > 1, "{scale:?}");
assert_eq!(binned.counts.iter().sum::<usize>(), vals.len(), "{scale:?}");
let subset = binned.count([0.5f32, 18.0].into_iter());
assert_eq!(subset.len(), binned.counts.len(), "{scale:?}");
assert_eq!(subset.iter().sum::<usize>(), 2, "{scale:?}");
}
}
#[test]
fn compact_labels() {
assert_eq!(compact(0.0), "0");
assert_eq!(compact(0.25), "0.25");
assert_eq!(compact(950.0), "950");
assert_eq!(compact(1234.0), "1.2k");
assert_eq!(compact(35_000.0), "35k");
assert_eq!(compact(1_100_000.0), "1.1M");
}
#[test]
fn log_lower_edges_match_the_printed_histogram_bins() {
let b = Binning::new(Scale::Log, 1e6, true);
for k in 0..60 {
let e = b.lower_edge(k);
assert!(crate::qc::log_bin_key(e as f64) >= k, "k={k} e={e}");
assert!(
e == 0 || crate::qc::log_bin_key((e - 1) as f64) < k,
"k={k} e={e}"
);
}
}
#[test]
fn whole_number_data_gets_whole_count_bins() {
let whole = Binned::new(&[0.0, 3.0, 7.0, 400.0], Scale::Linear);
let edges: Vec<usize> = (0..5).map(|k| whole.bins.lower_edge(k)).collect();
assert!(edges.windows(2).all(|w| w[1] > w[0]), "{edges:?}");
let fractional = Binned::new(&[0.0, 0.25, 1.5], Scale::Linear);
assert!(fractional.counts.len() > 2);
}
#[test]
fn unit_width_bins_are_one_bar_per_index() {
let b = Binning::with_width(Scale::Linear, 1.0);
for k in 0..500 {
assert_eq!(b.key(k as f64), k);
}
}
fn axis_rows(plot: &HistPlot, width: u16, height: u16) -> (String, String) {
let area = Rect::new(0, 0, width, height);
let mut buf = Buffer::empty(area);
plot.render(&mut buf, area);
let row = |y: u16| -> String { (0..width).map(|x| buf[(x, y)].symbol()).collect() };
(row(height - 2), row(height - 1))
}
#[test]
fn custom_labels_and_tick_spacing() {
let counts = vec![3usize; 12];
let label = |k: i32| (k % 4 == 0).then(|| format!("L{k}"));
let plot = HistPlot {
bins: Binning::with_width(Scale::Linear, 1.0),
kmin: 0,
counts: &counts,
style: &|_| PLAIN,
subset: None,
y_scale: Scale::Linear,
pointer: None,
marks: Vec::new(),
x_label: Some(&label),
tick_every: Some(2),
};
let (axis, labels) = axis_rows(&plot, 6 + 24, 8);
assert_eq!(axis.matches('┴').count(), 3, "{axis}");
assert!(labels.contains("L0") && labels.contains("L4") && labels.contains("L8"));
assert!(!labels.contains("L2") && !labels.contains("L6"), "{labels}");
}
#[test]
fn default_labels_are_unchanged() {
let counts = vec![1usize; 10];
let base = |x_label, tick_every| HistPlot {
bins: Binning::with_width(Scale::Linear, 1.0),
kmin: 0,
counts: &counts,
style: &|_| PLAIN,
subset: None,
y_scale: Scale::Linear,
pointer: None,
marks: Vec::new(),
x_label,
tick_every,
};
let (_, labels) = axis_rows(&base(None, None), 26, 8);
assert!(labels.trim_start().starts_with('0'), "{labels}");
}
fn bar_cells<T: BarValue>(counts: &[T]) -> String {
let area = Rect::new(0, 0, 6 + counts.len() as u16 * 2, 10);
let mut buf = Buffer::empty(area);
HistPlot {
bins: Binning::with_width(Scale::Linear, 1.0),
kmin: 0,
counts,
style: &|_| PLAIN,
subset: None,
y_scale: Scale::Linear,
pointer: None,
marks: Vec::new(),
x_label: Some(&|_| None),
tick_every: None,
}
.render(&mut buf, area);
(0..area.height - 2)
.flat_map(|y| (6..area.width).map(move |x| (x, y)))
.map(|(x, y)| buf[(x, y)].symbol().to_string())
.collect()
}
#[test]
fn real_valued_bars_draw_like_counts() {
assert_eq!(
bar_cells(&[0usize, 2, 4, 8]),
bar_cells(&[0.0f64, 0.2, 0.4, 0.8])
);
}