Skip to main content

ggplot_rs/position/
dodge.rs

1use crate::data::{DataFrame, Value};
2
3use super::{Position, PositionParams};
4
5/// Place groups side-by-side.
6pub struct PositionDodge;
7
8impl Position for PositionDodge {
9    fn compute(&self, data: &mut DataFrame, params: &PositionParams) {
10        let x_col = match data.column("x") {
11            Some(c) => c.to_vec(),
12            None => return,
13        };
14
15        // Determine groups from fill or color aesthetic
16        let group_col = data
17            .column("fill")
18            .or_else(|| data.column("color"))
19            .or_else(|| data.column("group"));
20
21        let group_keys: Vec<String> = match group_col {
22            Some(col) => col.iter().map(|v| v.to_group_key()).collect(),
23            None => return, // No grouping, nothing to dodge
24        };
25
26        // Find unique groups
27        let unique_groups: indexmap::IndexSet<&String> = group_keys.iter().collect();
28
29        let n_groups = unique_groups.len() as f64;
30        if n_groups <= 1.0 {
31            return;
32        }
33
34        let width = params.width;
35        let group_width = width / n_groups;
36
37        let mut new_x = x_col.clone();
38        for (i, (x, group)) in x_col.iter().zip(group_keys.iter()).enumerate() {
39            let group_idx = unique_groups.get_index_of(group).unwrap_or(0) as f64;
40            let offset = (group_idx - (n_groups - 1.0) / 2.0) * group_width;
41
42            if let Some(x_val) = x.as_f64() {
43                new_x[i] = Value::Float(x_val + offset);
44            }
45        }
46
47        if let Some(col) = data.column_mut("x") {
48            *col = new_x;
49        }
50    }
51
52    fn name(&self) -> &str {
53        "dodge"
54    }
55}