1use crate::paint::Bar;
5use pineal_render::Color;
6
7#[derive(Debug, Clone)]
9pub struct Histogram {
10 pub counts: Vec<f64>,
12 pub lo: f32,
13 pub hi: f32,
14 pub bin_width: f32,
16}
17
18impl Histogram {
19 pub fn new(values: &[f32], n_bins: usize) -> Self {
23 if n_bins == 0 || values.is_empty() {
24 return Self { counts: Vec::new(), lo: 0.0, hi: 0.0, bin_width: 0.0 };
25 }
26 let mut lo = f32::INFINITY;
27 let mut hi = f32::NEG_INFINITY;
28 for &v in values {
29 if v.is_nan() {
30 continue;
31 }
32 lo = lo.min(v);
33 hi = hi.max(v);
34 }
35 if !lo.is_finite() || !hi.is_finite() {
36 return Self { counts: vec![0.0; n_bins], lo: 0.0, hi: 0.0, bin_width: 0.0 };
37 }
38 if (hi - lo).abs() < f32::EPSILON {
40 hi = lo + 1.0;
41 }
42 let bin_width = (hi - lo) / n_bins as f32;
43 let mut counts = vec![0.0_f64; n_bins];
44 for &v in values {
45 if v.is_nan() {
46 continue;
47 }
48 let mut idx = ((v - lo) / bin_width) as usize;
49 if idx >= n_bins {
50 idx = n_bins - 1; }
52 counts[idx] += 1.0;
53 }
54 Self { counts, lo, hi, bin_width }
55 }
56
57 pub fn to_bars(&self, color: Color) -> Vec<Bar> {
60 self.counts.iter().map(|&c| Bar::new(c, color)).collect()
61 }
62
63 pub fn max_count(&self) -> f64 {
65 self.counts.iter().copied().fold(0.0, f64::max)
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 #[test]
74 fn counts_sum_to_sample_size() {
75 let v = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
76 let h = Histogram::new(&v, 5);
77 let total: f64 = h.counts.iter().sum();
78 assert_eq!(total, 10.0);
79 assert_eq!(h.counts.len(), 5);
80 }
81
82 #[test]
83 fn max_value_lands_in_last_bin() {
84 let v = [0.0, 10.0];
85 let h = Histogram::new(&v, 4);
86 assert_eq!(h.counts[3], 1.0, "el máximo cae en el último bin");
87 assert_eq!(h.counts[0], 1.0);
88 }
89
90 #[test]
91 fn empty_and_zero_bins_are_safe() {
92 assert!(Histogram::new(&[], 5).counts.iter().all(|&c| c == 0.0) || Histogram::new(&[], 5).counts.is_empty());
93 assert!(Histogram::new(&[1.0, 2.0], 0).counts.is_empty());
94 }
95
96 #[test]
97 fn degenerate_range_does_not_panic() {
98 let v = [3.0, 3.0, 3.0];
99 let h = Histogram::new(&v, 4);
100 assert_eq!(h.counts.iter().sum::<f64>(), 3.0);
101 }
102}