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
92            // Right side (top→bottom) then left side reversed to close the polygon.
93            let mut right_side: Vec<(f64, f64)> = Vec::with_capacity(idxs.len());
94            let mut left_side: Vec<(f64, f64)> = Vec::with_capacity(idxs.len());
95            for &i in idxs {
96                let ny = y_scale.map(|s| s.map(&y_col[i])).unwrap_or(0.0);
97                let half = width_col[i].as_f64().unwrap_or(0.0) * max_half_width;
98                right_side.push(coord.transform((nx + half, ny), &plot_area));
99                left_side.push(coord.transform((nx - half, ny), &plot_area));
100            }
101
102            let mut polygon = right_side;
103            left_side.reverse();
104            polygon.extend(left_side);
105
106            if polygon.len() >= 3 {
107                backend.draw_polygon(
108                    &polygon,
109                    &RectStyle {
110                        fill: Some(fill_color),
111                        stroke: Some(self.color),
112                        stroke_width: self.line_width,
113                        alpha: self.alpha,
114                        clip: true,
115                    },
116                )?;
117            }
118        }
119
120        Ok(())
121    }
122
123    fn required_aes(&self) -> Vec<Aesthetic> {
124        vec![Aesthetic::X, Aesthetic::Y]
125    }
126
127    fn default_stat(&self) -> Box<dyn Stat> {
128        Box::new(StatYDensity::default())
129    }
130
131    fn default_position(&self) -> Box<dyn Position> {
132        Box::new(PositionIdentity)
133    }
134
135    fn default_params(&self) -> GeomParams {
136        GeomParams::default()
137    }
138
139    fn name(&self) -> &str {
140        "violin"
141    }
142
143    fn set_series_color(&mut self, color: (u8, u8, u8)) {
144        self.fill = color;
145    }
146}