use crate::data::{DataFrame, Value};
use super::{Position, PositionParams};
pub struct PositionJitterDodge {
pub jitter_width: f64,
pub jitter_height: f64,
pub dodge_width: f64,
seed: u64,
auto_width: bool,
}
impl PositionJitterDodge {
pub fn new(jitter_width: f64, jitter_height: f64) -> Self {
PositionJitterDodge {
jitter_width,
jitter_height,
dodge_width: 0.9,
seed: 42,
auto_width: false,
}
}
pub fn with_dodge_width(mut self, width: f64) -> Self {
self.dodge_width = width;
self
}
pub fn with_seed(mut self, seed: u64) -> Self {
self.seed = seed;
self
}
fn pseudo_random(seed: u64, i: usize) -> f64 {
let h = seed
.wrapping_mul(6364136223846793005)
.wrapping_add(i as u64)
.wrapping_mul(1442695040888963407);
let h = h ^ (h >> 33);
let h = h.wrapping_mul(0xff51afd7ed558ccd);
(h as f64 / u64::MAX as f64) - 0.5
}
}
impl Default for PositionJitterDodge {
fn default() -> Self {
PositionJitterDodge {
auto_width: true,
..PositionJitterDodge::new(0.4, 0.0)
}
}
}
fn resolution(x: &[Value]) -> f64 {
if super::is_discrete_x(x) {
return 1.0;
}
let mut v: Vec<f64> = x
.iter()
.filter_map(Value::as_f64)
.filter(|f| f.is_finite())
.collect();
v.sort_by(f64::total_cmp);
v.dedup();
v.windows(2)
.map(|w| w[1] - w[0])
.fold(None, |m: Option<f64>, d| Some(m.map_or(d, |m| m.min(d))))
.unwrap_or(1.0)
}
impl Position for PositionJitterDodge {
fn compute(&self, data: &mut DataFrame, params: &PositionParams) {
let x_col = match data.column("x") {
Some(c) => c.to_vec(),
None => return,
};
let keys = super::dodge::group_keys(data);
let mut slots: indexmap::IndexMap<String, indexmap::IndexSet<&str>> =
indexmap::IndexMap::new();
if let Some(keys) = &keys {
for (x, k) in x_col.iter().zip(keys) {
if !x.is_na() {
slots.entry(x.to_group_key()).or_default().insert(k);
}
}
}
let ndodge = slots.values().map(|s| s.len()).max().unwrap_or(1).max(1) as f64;
let width = if self.auto_width {
0.4 * resolution(&x_col)
} else {
self.jitter_width
};
let amount = 2.0 * width / (ndodge + 2.0);
super::dodge::dodge_rows(
data,
self.dodge_width,
None,
false,
false,
params.group_order.as_deref(),
);
let seed = self.seed;
if super::is_discrete_x(&x_col) {
super::add_x_offsets(data, |i| Self::pseudo_random(seed, i) * amount);
} else if let Some(col) = data.column_mut("x") {
for (i, v) in col.iter_mut().enumerate() {
if let Some(f) = v.as_f64() {
*v = Value::Float(f + Self::pseudo_random(seed, i) * amount);
}
}
}
if self.jitter_height.abs() > f64::EPSILON {
if let Some(col) = data.column_mut("y") {
for (i, v) in col.iter_mut().enumerate() {
if let Some(f) = v.as_f64() {
*v = Value::Float(
f + Self::pseudo_random(self.seed.wrapping_add(1), i)
* self.jitter_height,
);
}
}
}
}
}
fn group_order(&self, data: &DataFrame) -> Option<Vec<String>> {
super::dodge::group_keys(data).map(|keys| super::dodge::dedup_order(&keys))
}
fn name(&self) -> &str {
"jitterdodge"
}
}