Skip to main content

ggplot_rs/position/
dodge2.rs

1use crate::data::{DataFrame, Value};
2
3use super::{Position, PositionParams};
4
5/// Like position_dodge but preserves total width and adds padding between groups.
6pub struct PositionDodge2 {
7    pub padding: f64,
8}
9
10impl PositionDodge2 {
11    pub fn new(padding: f64) -> Self {
12        PositionDodge2 { padding }
13    }
14}
15
16impl Default for PositionDodge2 {
17    fn default() -> Self {
18        PositionDodge2 { padding: 0.1 }
19    }
20}
21
22impl Position for PositionDodge2 {
23    fn compute(&self, data: &mut DataFrame, params: &PositionParams) {
24        let x_col = match data.column("x") {
25            Some(c) => c.to_vec(),
26            None => return,
27        };
28
29        let group_col = data
30            .column("fill")
31            .or_else(|| data.column("color"))
32            .or_else(|| data.column("group"));
33
34        let group_keys: Vec<String> = match group_col {
35            Some(col) => col.iter().map(|v| v.to_group_key()).collect(),
36            None => return,
37        };
38
39        let mut unique_groups: Vec<String> = Vec::new();
40        for g in &group_keys {
41            if !unique_groups.contains(g) {
42                unique_groups.push(g.clone());
43            }
44        }
45
46        let n_groups = unique_groups.len() as f64;
47        if n_groups <= 1.0 {
48            return;
49        }
50
51        let width = params.width;
52        // Shrink each element to leave padding between them
53        let group_width = width / n_groups;
54        let element_width = group_width * (1.0 - self.padding);
55
56        let mut new_x = x_col.clone();
57        let has_xmin = data.has_column("xmin");
58
59        let xmin_col = data.column("xmin").map(|c| c.to_vec());
60        let xmax_col = data.column("xmax").map(|c| c.to_vec());
61
62        let mut new_xmin = xmin_col.clone();
63        let mut new_xmax = xmax_col.clone();
64
65        for (i, (x, group)) in x_col.iter().zip(group_keys.iter()).enumerate() {
66            let group_idx = unique_groups.iter().position(|g| g == group).unwrap() as f64;
67            let offset = (group_idx - (n_groups - 1.0) / 2.0) * group_width;
68
69            if let Some(x_val) = x.as_f64() {
70                new_x[i] = Value::Float(x_val + offset);
71
72                // Also adjust xmin/xmax if present (for bars)
73                if has_xmin {
74                    if let Some(ref mut xmin) = new_xmin {
75                        let center = x_val + offset;
76                        xmin[i] = Value::Float(center - element_width / 2.0);
77                    }
78                    if let Some(ref mut xmax) = new_xmax {
79                        let center = x_val + offset;
80                        xmax[i] = Value::Float(center + element_width / 2.0);
81                    }
82                }
83            }
84        }
85
86        if let Some(col) = data.column_mut("x") {
87            *col = new_x;
88        }
89        if let Some(xmin) = new_xmin {
90            if let Some(col) = data.column_mut("xmin") {
91                *col = xmin;
92            }
93        }
94        if let Some(xmax) = new_xmax {
95            if let Some(col) = data.column_mut("xmax") {
96                *col = xmax;
97            }
98        }
99    }
100
101    fn name(&self) -> &str {
102        "dodge2"
103    }
104}