Skip to main content

ggplot_rs/geom/
violin.rs

1use std::collections::HashMap;
2
3use crate::aes::Aesthetic;
4use crate::coord::Coord;
5use crate::data::DataFrame;
6use crate::position::identity::PositionIdentity;
7use crate::position::Position;
8use crate::render::backend::{DrawBackend, RectStyle};
9use crate::render::RenderError;
10use crate::scale::ScaleSet;
11use crate::stat::ydensity::StatYDensity;
12use crate::stat::Stat;
13use crate::theme::Theme;
14
15use super::{Geom, GeomParams};
16
17/// Violin geometry — mirrored density polygon per group.
18pub struct GeomViolin {
19    pub fill: (u8, u8, u8),
20    pub color: (u8, u8, u8),
21    pub alpha: f64,
22    pub line_width: f64,
23}
24
25impl Default for GeomViolin {
26    fn default() -> Self {
27        GeomViolin {
28            fill: (97, 156, 255),
29            color: (50, 50, 50),
30            alpha: 0.7,
31            line_width: 0.5,
32        }
33    }
34}
35
36impl Geom for GeomViolin {
37    fn draw(
38        &self,
39        data: &DataFrame,
40        coord: &dyn Coord,
41        scales: &ScaleSet,
42        _theme: &Theme,
43        backend: &mut dyn DrawBackend,
44    ) -> Result<(), RenderError> {
45        let x_col = data
46            .column("x")
47            .ok_or(RenderError::MissingAesthetic("x".into()))?;
48        let y_col = data
49            .column("y")
50            .ok_or(RenderError::MissingAesthetic("y".into()))?;
51        let width_col = data
52            .column("violinwidth")
53            .ok_or(RenderError::MissingAesthetic("violinwidth".into()))?;
54        let fill_col = data.column("fill");
55
56        let plot_area = backend.plot_area();
57        let x_scale = scales.get(&Aesthetic::X);
58        let y_scale = scales.get(&Aesthetic::Y);
59
60        // Half-width of a group's slot, in the X scale's mapped space. Mirror the
61        // boxplot: discrete axes share the slot between all groups; continuous
62        // axes use a small fixed fraction.
63        let x_is_discrete = x_scale.map(|s| s.is_discrete()).unwrap_or(false);
64        let max_half_width = if x_is_discrete {
65            let n = x_scale.map(|s| s.breaks().len()).unwrap_or(1);
66            0.75 / (n.max(1) as f64 * 2.5)
67        } else {
68            0.03
69        };
70
71        // The stat vstacks one contiguous block of points per group; partition
72        // rows back into groups (keyed by the x value) and draw a polygon each.
73        let mut order: Vec<String> = Vec::new();
74        let mut groups: HashMap<String, Vec<usize>> = HashMap::new();
75        for (i, xv) in x_col.iter().enumerate() {
76            let key = format!("{xv:?}");
77            if !groups.contains_key(&key) {
78                order.push(key.clone());
79                groups.insert(key.clone(), Vec::new());
80            }
81            groups.get_mut(&key).unwrap().push(i);
82        }
83
84        for key in &order {
85            let idxs = &groups[key];
86            let first = idxs[0];
87            let nx = x_scale.map(|s| s.map(&x_col[first])).unwrap_or(0.5);
88            let fill_color = fill_col
89                .and_then(|fc| scales.map_color(&Aesthetic::Fill, &fc[first]))
90                .unwrap_or(self.fill);
91            let group = Some(super::tip_value(&x_col[first])).filter(|s| !s.is_empty());
92            super::set_mark(
93                backend,
94                group.clone(),
95                group,
96                super::series_key(data, first),
97                None,
98            );
99
100            // Right side (top→bottom) then left side reversed to close the polygon.
101            let mut right_side: Vec<(f64, f64)> = Vec::with_capacity(idxs.len());
102            let mut left_side: Vec<(f64, f64)> = Vec::with_capacity(idxs.len());
103            for &i in idxs {
104                let ny = y_scale.map(|s| s.map(&y_col[i])).unwrap_or(0.0);
105                let half = width_col[i].as_f64().unwrap_or(0.0) * max_half_width;
106                right_side.push(coord.transform((nx + half, ny), &plot_area));
107                left_side.push(coord.transform((nx - half, ny), &plot_area));
108            }
109
110            let mut polygon = right_side;
111            left_side.reverse();
112            polygon.extend(left_side);
113
114            if polygon.len() >= 3 {
115                backend.draw_polygon(
116                    &polygon,
117                    &RectStyle {
118                        fill: Some(fill_color),
119                        stroke: Some(self.color),
120                        stroke_width: self.line_width,
121                        alpha: self.alpha,
122                        clip: true,
123                    },
124                )?;
125            }
126        }
127        super::clear_mark(backend);
128
129        Ok(())
130    }
131
132    fn required_aes(&self) -> Vec<Aesthetic> {
133        vec![Aesthetic::X, Aesthetic::Y]
134    }
135
136    fn default_stat(&self) -> Box<dyn Stat> {
137        Box::new(StatYDensity::default())
138    }
139
140    fn default_position(&self) -> Box<dyn Position> {
141        Box::new(PositionIdentity)
142    }
143
144    fn default_params(&self) -> GeomParams {
145        GeomParams::default()
146    }
147
148    fn name(&self) -> &str {
149        "violin"
150    }
151
152    fn set_series_color(&mut self, color: (u8, u8, u8)) {
153        self.fill = color;
154    }
155}