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_bins_read_values_on_the_shared_axis() {
let means: Vec<f32> = (1..=400).map(|i| 0.001 * 1.02f32.powi(i)).collect();
let binned = Binned::new(&means, Scale::Log);
let filled = binned.counts.iter().filter(|&&c| c > 0).count();
assert!(filled >= 40, "{filled} of {} bins", binned.counts.len());
let ticks = binned.bins.ticks(binned.kmin, binned.kmax());
assert!(ticks.iter().any(|&(_, v)| v == 0.01), "{ticks:?}");
let (kmin, kmax) = (binned.kmin, binned.kmax());
assert!(
ticks.iter().all(|&(k, _)| (kmin..=kmax + 1).contains(&k)),
"{ticks:?}"
);
}
#[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,
y_max: None,
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,
y_max: None,
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,
y_max: None,
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])
);
}
#[test]
fn a_fixed_y_max_shares_the_scale() {
let draw = |y_max| {
let counts = [2usize, 4];
let area = Rect::new(0, 0, 6 + 4, 12);
let mut buf = Buffer::empty(area);
HistPlot {
bins: Binning::with_width(Scale::Linear, 1.0),
kmin: 0,
counts: &counts,
style: &|_| PLAIN,
subset: None,
y_scale: Scale::Linear,
y_max,
pointer: None,
marks: Vec::new(),
x_label: Some(&|_| None),
tick_every: None,
}
.render(&mut buf, area);
(0..10).filter(|&y| buf[(8, y)].symbol() == "█").count()
};
assert!(draw(Some(8.0)) < draw(None));
assert_eq!(draw(Some(1.0)), draw(None), "never below the tallest bar");
}
fn draw_mirror(plot: &MirrorPlot<'_, f64>, w: u16, h: u16) -> Vec<String> {
let area = Rect::new(0, 0, w, h);
let mut buf = Buffer::empty(area);
plot.render(&mut buf, area);
(0..h)
.map(|y| (0..w).map(|x| buf[(x, y)].symbol()).collect())
.collect()
}
#[test]
fn a_mirror_grows_one_side_up_and_the_other_down() {
let (up, down) = ([4.0, 0.0, 2.0], [0.0, 4.0, 1.0]);
let side = |counts| MirrorSide {
counts,
subset: None,
style: PLAIN,
name: "",
};
let label = |i: usize| (i == 0).then(|| "A".to_string());
let plot = MirrorPlot {
up: side(&up),
down: side(&down),
y_scale: Scale::Linear,
y_max: None,
y_labels: None,
pointer: Some(1),
x_label: Some(&label),
};
let rows = draw_mirror(&plot, 10, 9);
let col = |x: usize| -> String {
rows.iter()
.take(7)
.map(|r| r.chars().nth(x).unwrap())
.collect()
};
assert_eq!(col(GUTTER as usize), "███─ ", "tallest up fills its side");
assert_eq!(
col(GUTTER as usize + 1),
"┊┊┊─███",
"down, under the pointer"
);
assert_eq!(col(GUTTER as usize + 2), " ▄█─█ ", "halves round to cells");
assert!(rows[0].trim_start().starts_with('4'), "{rows:?}");
assert!(rows[6].trim_start().starts_with('4'), "{rows:?}");
assert!(rows[8].contains('A'));
assert!(rows[7].contains('▲'));
}
#[test]
fn base64_pads_as_the_standard_does() {
assert_eq!(base64(b""), "");
assert_eq!(base64(b"f"), "Zg==");
assert_eq!(base64(b"fo"), "Zm8=");
assert_eq!(base64(b"foo"), "Zm9v");
assert_eq!(base64(b"--qc-mads=3"), "LS1xYy1tYWRzPTM=");
}