use crate::scale::Ticks;
#[derive(Debug, Clone, PartialEq)]
pub struct Bins {
start: f64,
width: f64,
counts: Vec<u64>,
}
impl Bins {
pub fn new(start: f64, width: f64, bins: usize) -> Bins {
assert!(
start.is_finite() && width.is_finite() && width > 0.0 && bins > 0,
"Bins::new requires a finite start, positive width, and at least one bin"
);
Bins {
start,
width,
counts: vec![0; bins],
}
}
pub fn auto(values: &[f64], limit: usize) -> Option<Bins> {
let mut finite: Vec<f64> = values.iter().copied().filter(|v| v.is_finite()).collect();
if finite.is_empty() {
return None;
}
let n = finite.len();
let (min, max) = finite
.iter()
.fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), &v| {
(lo.min(v), hi.max(v))
});
if min == max {
let mut bins = Bins::new(min - 0.5, 1.0, 1);
bins.counts[0] = n as u64;
return Some(bins);
}
let sturges = (n as f64).log2().ceil() as usize + 1;
let quarter = n / 4;
let (_, q1, _) = finite.select_nth_unstable_by(quarter, f64::total_cmp);
let q1 = *q1;
let upper = (3 * n) / 4;
let (_, q3, _) = finite.select_nth_unstable_by(upper.min(n - 1), f64::total_cmp);
let q3 = *q3;
let iqr = q3 - q1;
let fd = if iqr > 0.0 {
let width = 2.0 * iqr / (n as f64).cbrt();
((max - min) / width).ceil() as usize
} else {
0
};
let target = sturges.max(fd).clamp(1, limit.max(1));
let ticks = Ticks::linear(min, max, target.min(50));
let width = if ticks.step() > 0.0 {
ticks.step()
} else {
(max - min) / target as f64
};
let start = (min / width).floor() * width;
let bins = (((max - start) / width).ceil() as usize).max(1);
let mut result = Bins::new(start, width, bins.min(limit.max(1) * 2));
for &value in &finite {
result.add(value);
}
Some(result)
}
pub fn add(&mut self, value: f64) {
if !value.is_finite() {
return;
}
let position = (value - self.start) / self.width;
if position < 0.0 {
return;
}
let index = position as usize;
if index < self.counts.len() {
self.counts[index] += 1;
} else if index == self.counts.len() && value <= self.end() {
*self.counts.last_mut().expect("at least one bin") += 1;
}
}
pub fn merge(&mut self, other: &Bins) {
assert!(
self.start == other.start
&& self.width == other.width
&& self.counts.len() == other.counts.len(),
"Bins::merge requires identical geometry"
);
for (mine, theirs) in self.counts.iter_mut().zip(other.counts.iter()) {
*mine += theirs;
}
}
pub fn start(&self) -> f64 {
self.start
}
pub fn width(&self) -> f64 {
self.width
}
pub fn end(&self) -> f64 {
self.start + self.width * self.counts.len() as f64
}
pub fn counts(&self) -> &[u64] {
&self.counts
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Grid {
pub counts: Vec<f64>,
pub columns: usize,
pub x: (f64, f64),
pub y: (f64, f64),
}
pub fn bins2(x: &[f64], y: &[f64], columns: usize, rows: usize) -> Option<Grid> {
assert_eq!(x.len(), y.len(), "bins2 requires series of equal length");
assert!(columns > 0 && rows > 0, "bins2 requires a non-empty grid");
let mut x_extent: Option<(f64, f64)> = None;
let mut y_extent: Option<(f64, f64)> = None;
for (&xv, &yv) in x.iter().zip(y.iter()) {
if !xv.is_finite() || !yv.is_finite() {
continue;
}
x_extent = Some(x_extent.map_or((xv, xv), |(lo, hi)| (lo.min(xv), hi.max(xv))));
y_extent = Some(y_extent.map_or((yv, yv), |(lo, hi)| (lo.min(yv), hi.max(yv))));
}
let (x_extent, y_extent) = (x_extent?, y_extent?);
let width = (x_extent.1 - x_extent.0).max(f64::MIN_POSITIVE);
let height = (y_extent.1 - y_extent.0).max(f64::MIN_POSITIVE);
let mut counts = vec![0.0f64; columns * rows];
for (&xv, &yv) in x.iter().zip(y.iter()) {
if !xv.is_finite() || !yv.is_finite() {
continue;
}
let column = (((xv - x_extent.0) / width) * columns as f64) as usize;
let row = (((yv - y_extent.0) / height) * rows as f64) as usize;
counts[row.min(rows - 1) * columns + column.min(columns - 1)] += 1.0;
}
Some(Grid {
counts,
columns,
x: x_extent,
y: y_extent,
})
}
#[cfg(test)]
#[path = "tests/bin_tests.rs"]
mod tests;