1use crate::aes::Aesthetic;
2use crate::data::{DataFrame, Value};
3use crate::scale::ScaleSet;
4
5use super::Stat;
6
7pub struct StatBindot {
9 pub bins: usize,
10}
11
12impl Default for StatBindot {
13 fn default() -> Self {
14 StatBindot { bins: 30 }
15 }
16}
17
18impl Stat for StatBindot {
19 fn compute_group(&self, data: &DataFrame, _scales: &ScaleSet) -> DataFrame {
20 let x_col = match data.column("x") {
21 Some(c) => c,
22 None => return DataFrame::new(),
23 };
24
25 let values: Vec<f64> = x_col.iter().filter_map(|v| v.as_f64()).collect();
26 if values.is_empty() {
27 return DataFrame::new();
28 }
29
30 let min = values.iter().cloned().fold(f64::INFINITY, f64::min);
31 let max = values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
32
33 let (min, max) = if (max - min).abs() < f64::EPSILON {
34 (min - 0.5, max + 0.5)
35 } else {
36 (min, max)
37 };
38
39 let bin_width = (max - min) / self.bins as f64;
40 let mut bin_counts = vec![0usize; self.bins];
41
42 let mut x_vals = Vec::with_capacity(values.len());
44 let mut y_vals = Vec::with_capacity(values.len());
45
46 for &v in &values {
47 let bin = ((v - min) / bin_width).floor() as usize;
48 let bin = bin.min(self.bins - 1);
49 let center = min + (bin as f64 + 0.5) * bin_width;
50 let stack_pos = bin_counts[bin];
51 bin_counts[bin] += 1;
52
53 x_vals.push(Value::Float(center));
54 y_vals.push(Value::Float(stack_pos as f64 + 0.5)); }
56
57 let mut result = DataFrame::new();
58 result.add_column("x".to_string(), x_vals);
59 result.add_column("y".to_string(), y_vals);
60
61 for col_name in &["color", "fill", "group"] {
63 if let Some(col) = data.column(col_name) {
64 if col.len() == values.len() {
65 result.add_column(col_name.to_string(), col.to_vec());
66 }
67 }
68 }
69
70 result
71 }
72
73 fn required_aes(&self) -> Vec<Aesthetic> {
74 vec![Aesthetic::X]
75 }
76
77 fn name(&self) -> &str {
78 "bindot"
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn test_bindot_basic() {
88 let mut data = DataFrame::new();
89 let x_vals: Vec<Value> = vec![1.0, 1.1, 1.2, 2.0, 2.1, 3.0]
90 .into_iter()
91 .map(Value::Float)
92 .collect();
93 data.add_column("x".to_string(), x_vals);
94
95 let stat = StatBindot { bins: 3 };
96 let scales = ScaleSet::new();
97 let result = stat.compute_group(&data, &scales);
98
99 assert_eq!(result.nrows(), 6);
100 assert!(result.column("x").is_some());
101 assert!(result.column("y").is_some());
102 }
103}