ggplot_rs/position/
jitterdodge.rs1use crate::data::{DataFrame, Value};
2
3use super::{Position, PositionParams};
4
5pub struct PositionJitterDodge {
8 pub jitter_width: f64,
9 pub jitter_height: f64,
10 pub dodge_width: f64,
11 seed: u64,
12}
13
14impl PositionJitterDodge {
15 pub fn new(jitter_width: f64, jitter_height: f64) -> Self {
16 PositionJitterDodge {
17 jitter_width,
18 jitter_height,
19 dodge_width: 0.9,
20 seed: 42,
21 }
22 }
23
24 pub fn with_dodge_width(mut self, width: f64) -> Self {
25 self.dodge_width = width;
26 self
27 }
28
29 pub fn with_seed(mut self, seed: u64) -> Self {
30 self.seed = seed;
31 self
32 }
33
34 fn pseudo_random(seed: u64, i: usize) -> f64 {
35 let h = seed
37 .wrapping_mul(6364136223846793005)
38 .wrapping_add(i as u64)
39 .wrapping_mul(1442695040888963407);
40 let h = h ^ (h >> 33);
41 let h = h.wrapping_mul(0xff51afd7ed558ccd);
42 (h as f64 / u64::MAX as f64) - 0.5
43 }
44}
45
46impl Default for PositionJitterDodge {
47 fn default() -> Self {
48 PositionJitterDodge::new(0.4, 0.0)
49 }
50}
51
52impl Position for PositionJitterDodge {
53 fn compute(&self, data: &mut DataFrame, _params: &PositionParams) {
54 let x_col = match data.column("x") {
55 Some(c) => c.to_vec(),
56 None => return,
57 };
58
59 let group_col = data
60 .column("fill")
61 .or_else(|| data.column("color"))
62 .or_else(|| data.column("group"));
63
64 let group_keys: Vec<String> = match group_col {
65 Some(col) => col.iter().map(|v| v.to_group_key()).collect(),
66 None => {
67 let mut new_x = x_col;
69 for (i, v) in new_x.iter_mut().enumerate() {
70 if let Some(f) = v.as_f64() {
71 *v =
72 Value::Float(f + Self::pseudo_random(self.seed, i) * self.jitter_width);
73 }
74 }
75 if let Some(col) = data.column_mut("x") {
76 *col = new_x;
77 }
78 return;
79 }
80 };
81
82 let mut unique_groups: Vec<String> = Vec::new();
83 for g in &group_keys {
84 if !unique_groups.contains(g) {
85 unique_groups.push(g.clone());
86 }
87 }
88
89 let n_groups = unique_groups.len() as f64;
90 let group_width = if n_groups > 1.0 {
91 self.dodge_width / n_groups
92 } else {
93 0.0
94 };
95
96 let mut new_x = x_col.clone();
97 for (i, (x, group)) in x_col.iter().zip(group_keys.iter()).enumerate() {
98 if let Some(x_val) = x.as_f64() {
99 let dodge_offset = if n_groups > 1.0 {
101 let group_idx = unique_groups.iter().position(|g| g == group).unwrap() as f64;
102 (group_idx - (n_groups - 1.0) / 2.0) * group_width
103 } else {
104 0.0
105 };
106
107 let jitter_x = Self::pseudo_random(self.seed, i) * self.jitter_width;
109
110 new_x[i] = Value::Float(x_val + dodge_offset + jitter_x);
111 }
112 }
113
114 if let Some(col) = data.column_mut("x") {
115 *col = new_x;
116 }
117
118 if self.jitter_height.abs() > f64::EPSILON {
120 if let Some(col) = data.column_mut("y") {
121 for (i, v) in col.iter_mut().enumerate() {
122 if let Some(f) = v.as_f64() {
123 *v = Value::Float(
124 f + Self::pseudo_random(self.seed.wrapping_add(1), i)
125 * self.jitter_height,
126 );
127 }
128 }
129 }
130 }
131 }
132
133 fn name(&self) -> &str {
134 "jitterdodge"
135 }
136}