1use plotters::element::{Drawable, PointCollection};
5use plotters::style::{RGBColor, ShapeStyle};
6use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
7
8use crate::series::box_plot::BoxStyle;
9use crate::stats::{kde_curve, quartiles, StatsError};
10use crate::style::{stroke_style, translucent_fill};
11
12const DEFAULT_VIOLIN_FILL: RGBColor = RGBColor(0, 158, 115); const DEFAULT_VIOLIN_WIDTH: u32 = 60;
14const DEFAULT_KDE_POINTS: usize = 200;
15const EMBEDDED_BOX_WIDTH: u32 = 10;
16const N_BOX_POINTS: usize = 5;
17
18#[derive(Debug, Clone)]
20pub struct ViolinStyle {
21 pub fill: ShapeStyle,
23 pub outline: ShapeStyle,
25 pub show_box: bool,
27}
28
29impl Default for ViolinStyle {
30 fn default() -> Self {
31 Self {
32 fill: translucent_fill(&DEFAULT_VIOLIN_FILL, 0.4),
33 outline: stroke_style(&DEFAULT_VIOLIN_FILL, 2),
34 show_box: false,
35 }
36 }
37}
38
39#[derive(Debug, Clone)]
46pub struct ViolinPlot<C> {
47 points: Vec<C>,
50 density: Vec<f64>,
51 max_density: f64,
52 n_kde: usize,
53 width: u32,
54 horizontal: bool,
55 style: ViolinStyle,
56 box_style: BoxStyle,
57}
58
59impl<X: Clone> ViolinPlot<(X, f64)> {
60 pub fn vertical(x: X, data: &[f64]) -> Result<Self, StatsError> {
64 Self::vertical_with_bandwidth(x, data, None)
65 }
66
67 pub fn vertical_with_bandwidth(
69 x: X,
70 data: &[f64],
71 bandwidth: Option<f64>,
72 ) -> Result<Self, StatsError> {
73 let curve = kde_curve(data, bandwidth, DEFAULT_KDE_POINTS, 3.0)?;
74 let q = quartiles(data)?;
75 let mut points: Vec<(X, f64)> = curve.xs.iter().map(|&y| (x.clone(), y)).collect();
76 points.push((x.clone(), q.upper_whisker));
77 points.push((x.clone(), q.q3));
78 points.push((x.clone(), q.median));
79 points.push((x.clone(), q.q1));
80 points.push((x.clone(), q.lower_whisker));
81 Ok(Self {
82 n_kde: curve.xs.len(),
83 max_density: curve.max_density(),
84 density: curve.density,
85 points,
86 width: DEFAULT_VIOLIN_WIDTH,
87 horizontal: false,
88 style: ViolinStyle::default(),
89 box_style: BoxStyle::default(),
90 })
91 }
92}
93
94impl<Y: Clone> ViolinPlot<(f64, Y)> {
95 pub fn horizontal(y: Y, data: &[f64]) -> Result<Self, StatsError> {
97 Self::horizontal_with_bandwidth(y, data, None)
98 }
99
100 pub fn horizontal_with_bandwidth(
102 y: Y,
103 data: &[f64],
104 bandwidth: Option<f64>,
105 ) -> Result<Self, StatsError> {
106 let curve = kde_curve(data, bandwidth, DEFAULT_KDE_POINTS, 3.0)?;
107 let q = quartiles(data)?;
108 let mut points: Vec<(f64, Y)> = curve.xs.iter().map(|&x| (x, y.clone())).collect();
109 points.push((q.upper_whisker, y.clone()));
110 points.push((q.q3, y.clone()));
111 points.push((q.median, y.clone()));
112 points.push((q.q1, y.clone()));
113 points.push((q.lower_whisker, y.clone()));
114 Ok(Self {
115 n_kde: curve.xs.len(),
116 max_density: curve.max_density(),
117 density: curve.density,
118 points,
119 width: DEFAULT_VIOLIN_WIDTH,
120 horizontal: true,
121 style: ViolinStyle::default(),
122 box_style: BoxStyle::default(),
123 })
124 }
125}
126
127impl<C> ViolinPlot<C> {
128 pub fn width(mut self, width: u32) -> Self {
130 self.width = width;
131 self
132 }
133
134 pub fn show_box(mut self, show: bool) -> Self {
136 self.style.show_box = show;
137 self
138 }
139
140 pub fn style(mut self, style: ViolinStyle) -> Self {
142 self.style = style;
143 self
144 }
145
146 pub fn box_style(mut self, style: BoxStyle) -> Self {
148 self.box_style = style;
149 self
150 }
151}
152
153impl<'a, C: 'a> PointCollection<'a, C> for &'a ViolinPlot<C> {
154 type Point = &'a C;
155 type IntoIter = &'a [C];
156 fn point_iter(self) -> &'a [C] {
157 &self.points
158 }
159}
160
161impl<C, DB: DrawingBackend> Drawable<DB> for ViolinPlot<C> {
162 fn draw<I: Iterator<Item = BackendCoord>>(
163 &self,
164 points: I,
165 backend: &mut DB,
166 _parent_dim: (u32, u32),
167 ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
168 let pix: Vec<BackendCoord> = points.collect();
169 if pix.len() < self.n_kde + N_BOX_POINTS || self.max_density <= 0.0 {
170 return Ok(());
171 }
172 let half = self.width as f64 / 2.0;
173 let kde = &pix[..self.n_kde];
174
175 let offset =
178 |i: usize| -> i32 { ((self.density[i] / self.max_density) * half).round() as i32 };
179
180 let mut outline: Vec<BackendCoord> = Vec::with_capacity(self.n_kde * 2 + 1);
181 if self.horizontal {
182 let py = |i: usize| kde[i].1;
183 let px = |i: usize| kde[i].0;
184 for i in 0..self.n_kde {
185 outline.push((px(i), py(i) - offset(i)));
186 }
187 for i in (0..self.n_kde).rev() {
188 outline.push((px(i), py(i) + offset(i)));
189 }
190 } else {
191 let px = |i: usize| kde[i].0;
192 let py = |i: usize| kde[i].1;
193 for i in 0..self.n_kde {
194 outline.push((px(i) - offset(i), py(i)));
195 }
196 for i in (0..self.n_kde).rev() {
197 outline.push((px(i) + offset(i), py(i)));
198 }
199 }
200
201 backend.fill_polygon(outline.iter().copied(), &self.style.fill)?;
202 if let Some(&first) = outline.first() {
204 outline.push(first);
205 }
206 backend.draw_path(outline, &self.style.outline)?;
207
208 if self.style.show_box {
210 let b = &pix[self.n_kde..self.n_kde + N_BOX_POINTS];
211 draw_embedded_box(backend, b, self.horizontal, &self.box_style)?;
212 }
213 Ok(())
214 }
215}
216
217fn draw_embedded_box<DB: DrawingBackend>(
221 backend: &mut DB,
222 b: &[BackendCoord],
223 horizontal: bool,
224 style: &BoxStyle,
225) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
226 let (upper_w, q3, median, q1, lower_w) = (b[0], b[1], b[2], b[3], b[4]);
227 let half = (EMBEDDED_BOX_WIDTH / 2) as i32;
228 if horizontal {
229 let cy = q1.1;
230 let (bx1, bx2) = (q1.0.min(q3.0), q1.0.max(q3.0));
231 backend.draw_line((lower_w.0, cy), (upper_w.0, cy), &style.whisker)?;
232 backend.draw_rect((bx1, cy - half), (bx2, cy + half), &style.box_fill, true)?;
233 backend.draw_rect((bx1, cy - half), (bx2, cy + half), &style.box_border, false)?;
234 backend.draw_line((median.0, cy - half), (median.0, cy + half), &style.median)?;
235 } else {
236 let cx = q1.0;
237 let (by1, by2) = (q3.1.min(q1.1), q3.1.max(q1.1));
238 backend.draw_line((cx, lower_w.1), (cx, upper_w.1), &style.whisker)?;
239 backend.draw_rect((cx - half, by1), (cx + half, by2), &style.box_fill, true)?;
240 backend.draw_rect((cx - half, by1), (cx + half, by2), &style.box_border, false)?;
241 backend.draw_line((cx - half, median.1), (cx + half, median.1), &style.median)?;
242 }
243 Ok(())
244}
245
246#[derive(Debug, Clone)]
248pub struct ViolinPlotSeries<C> {
249 violins: Vec<ViolinPlot<C>>,
250}
251
252impl<X: Clone> ViolinPlotSeries<(X, f64)> {
253 pub fn from_samples<I, S>(groups: I) -> Result<Self, StatsError>
255 where
256 I: IntoIterator<Item = (X, S)>,
257 S: AsRef<[f64]>,
258 {
259 let violins = groups
260 .into_iter()
261 .map(|(x, s)| ViolinPlot::vertical(x, s.as_ref()))
262 .collect::<Result<Vec<_>, _>>()?;
263 Ok(Self { violins })
264 }
265}
266
267impl<C> ViolinPlotSeries<C> {
268 pub fn width(mut self, width: u32) -> Self {
270 self.violins = self.violins.into_iter().map(|v| v.width(width)).collect();
271 self
272 }
273
274 pub fn show_box(mut self, show: bool) -> Self {
276 self.violins = self.violins.into_iter().map(|v| v.show_box(show)).collect();
277 self
278 }
279
280 pub fn style(mut self, style: ViolinStyle) -> Self {
282 self.violins = self
283 .violins
284 .into_iter()
285 .map(|v| v.style(style.clone()))
286 .collect();
287 self
288 }
289}
290
291impl<C> IntoIterator for ViolinPlotSeries<C> {
292 type Item = ViolinPlot<C>;
293 type IntoIter = std::vec::IntoIter<ViolinPlot<C>>;
294 fn into_iter(self) -> Self::IntoIter {
295 self.violins.into_iter()
296 }
297}