1use {
2 super::*,
3 crate::AxisScale,
4 itertools::Itertools,
5 plotters::coord::{
6 ranged1d::{AsRangedCoord, ValueFormatter as PlottersValueFormatter},
7 Shift,
8 },
9 std::{cmp::Ordering, path::Path},
10};
11
12const NUM_COLORS: usize = 8;
13static COMPARISON_COLORS: [RGBColor; NUM_COLORS] = [
14 RGBColor(178, 34, 34),
15 RGBColor(46, 139, 87),
16 RGBColor(0, 139, 139),
17 RGBColor(255, 215, 0),
18 RGBColor(0, 0, 139),
19 RGBColor(220, 20, 60),
20 RGBColor(139, 0, 139),
21 RGBColor(0, 255, 127),
22];
23
24pub(crate) fn line_comparison(
25 line_cfg: LinePlotConfig,
26 formatter: &dyn ValueFormatter,
27 title: &str,
28 all_curves: &[&(&BenchmarkId, Vec<f64>)],
29 path: &Path,
30 value_type: ValueType,
31 axis_scale: AxisScale,
32) {
33 let (unit, series_data) = line_comparison_series_data(line_cfg, formatter, all_curves);
34
35 let x_range =
36 plotters::data::fitting_range(series_data.iter().flat_map(|(_, xs, _)| xs.iter()));
37 let y_range =
38 plotters::data::fitting_range(series_data.iter().flat_map(|(_, _, ys)| ys.iter()));
39 let root_area = SVGBackend::new(&path, SIZE)
40 .into_drawing_area()
41 .titled(&format!("{}: Comparison", title), (DEFAULT_FONT, 20))
42 .unwrap();
43
44 match axis_scale {
45 AxisScale::Linear => draw_line_comparison_figure(
46 line_cfg,
47 root_area,
48 unit,
49 x_range,
50 y_range,
51 value_type,
52 series_data,
53 ),
54 AxisScale::Logarithmic => draw_line_comparison_figure(
55 line_cfg,
56 root_area,
57 unit,
58 x_range.log_scale(),
59 y_range.log_scale(),
60 value_type,
61 series_data,
62 ),
63 }
64}
65
66fn draw_line_comparison_figure<XR: AsRangedCoord<Value = f64>, YR: AsRangedCoord<Value = f64>>(
67 line_cfg: LinePlotConfig,
68 root_area: DrawingArea<SVGBackend, Shift>,
69 y_unit: &str,
70 x_range: XR,
71 y_range: YR,
72 value_type: ValueType,
73 data: Vec<(Option<&String>, Vec<f64>, Vec<f64>)>,
74) where
75 XR::CoordDescType: PlottersValueFormatter<f64>,
76 YR::CoordDescType: PlottersValueFormatter<f64>,
77{
78 let input_suffix = match value_type {
79 ValueType::Bytes => " Size (Bytes)",
80 ValueType::Elements => " Size (Elements)",
81 ValueType::Bits => " Size (Bits)",
82 ValueType::Value => "",
83 };
84
85 let mut chart = ChartBuilder::on(&root_area)
86 .margin((5).percent())
87 .set_label_area_size(LabelAreaPosition::Left, (5).percent_width().min(60))
88 .set_label_area_size(LabelAreaPosition::Bottom, (5).percent_height().min(40))
89 .build_cartesian_2d(x_range, y_range)
90 .unwrap();
91
92 chart
93 .configure_mesh()
94 .disable_mesh()
95 .x_desc(format!("Input{}", input_suffix))
96 .y_desc(format!("Average {} ({})", line_cfg.label, y_unit))
97 .draw()
98 .unwrap();
99
100 for (id, (name, xs, ys)) in (0..).zip(data) {
101 let series = chart
102 .draw_series(
103 LineSeries::new(
104 xs.into_iter().zip(ys),
105 COMPARISON_COLORS[id % NUM_COLORS].filled(),
106 )
107 .point_size(POINT_SIZE),
108 )
109 .unwrap();
110 if let Some(name) = name {
111 series.label(name).legend(move |(x, y)| {
112 Rectangle::new(
113 [(x, y - 5), (x + 20, y + 5)],
114 COMPARISON_COLORS[id % NUM_COLORS].filled(),
115 )
116 });
117 }
118 }
119
120 chart
121 .configure_series_labels()
122 .position(SeriesLabelPosition::UpperLeft)
123 .draw()
124 .unwrap();
125}
126
127#[allow(clippy::type_complexity)]
128fn line_comparison_series_data<'a>(
129 line_cfg: LinePlotConfig,
130 formatter: &dyn ValueFormatter,
131 all_curves: &[&(&'a BenchmarkId, Vec<f64>)],
132) -> (&'static str, Vec<(Option<&'a String>, Vec<f64>, Vec<f64>)>) {
133 let (max_id, max) = all_curves
134 .iter()
135 .map(|&(id, data)| (*id, Sample::new(data).mean()))
136 .fold(None, |prev: Option<(&BenchmarkId, f64)>, next| match prev {
137 Some(prev) if prev.1 >= next.1 => Some(prev),
138 _ => Some(next),
139 })
140 .unwrap();
141
142 let mut max_formatted = [max];
143 let unit = (line_cfg.scale)(formatter, max_id, max, max_id, &mut max_formatted);
144
145 let mut series_data = vec![];
146
147 for (key, group) in &all_curves.iter().chunk_by(|&&&(id, _)| &id.function_id) {
151 let mut tuples: Vec<_> = group
152 .map(|&&(id, ref sample)| {
153 let x = id.as_number().unwrap();
156 let mut y = [Sample::new(sample).mean()];
157
158 (line_cfg.scale)(formatter, max_id, max, id, &mut y);
159
160 (x, y[0])
161 })
162 .collect();
163 tuples.sort_by(|&(ax, _), &(bx, _)| ax.partial_cmp(&bx).unwrap_or(Ordering::Less));
164 let function_name = key.as_ref();
165 let (xs, ys): (Vec<_>, Vec<_>) = tuples.into_iter().unzip();
166 series_data.push((function_name, xs, ys));
167 }
168 (unit, series_data)
169}
170
171pub fn violin(
172 formatter: &dyn ValueFormatter,
173 title: &str,
174 all_curves: &[&(&BenchmarkId, Vec<f64>)],
175 path: &Path,
176 axis_scale: AxisScale,
177) {
178 let all_curves_vec = all_curves.iter().rev().cloned().collect::<Vec<_>>();
179 let all_curves: &[&(&BenchmarkId, Vec<f64>)] = &all_curves_vec;
180
181 let mut kdes = all_curves
182 .iter()
183 .map(|&&(id, ref sample)| {
184 let (x, mut y) = kde::sweep(Sample::new(sample), KDE_POINTS, None);
185 let y_max = Sample::new(&y).max();
186 for y in y.iter_mut() {
187 *y /= y_max;
188 }
189
190 (id.as_title(), x, y)
191 })
192 .collect::<Vec<_>>();
193
194 let mut xs = kdes
195 .iter()
196 .flat_map(|(_, x, _)| x.iter())
197 .filter(|&&x| x > 0.);
198 let (mut min, mut max) = {
199 let &first = xs.next().unwrap();
200 (first, first)
201 };
202 for &e in xs {
203 if e < min {
204 min = e;
205 } else if e > max {
206 max = e;
207 }
208 }
209 let mut dummy = [1.0];
210 let unit = formatter.scale_values(max, &mut dummy);
211 kdes.iter_mut().for_each(|&mut (_, ref mut xs, _)| {
212 formatter.scale_values(max, xs);
213 });
214
215 let mut x_range = plotters::data::fitting_range(kdes.iter().flat_map(|(_, xs, _)| xs.iter()));
216 x_range.start = 0.0;
217 let y_range = -0.5..all_curves.len() as f64 - 0.5;
218
219 let size = (960, 150 + (18 * all_curves.len() as u32));
220
221 let root_area = SVGBackend::new(&path, size)
222 .into_drawing_area()
223 .titled(&format!("{}: Violin plot", title), (DEFAULT_FONT, 20))
224 .unwrap();
225
226 match axis_scale {
227 AxisScale::Linear => draw_violin_figure(root_area, unit, x_range, y_range, kdes),
228 AxisScale::Logarithmic => {
229 draw_violin_figure(root_area, unit, x_range.log_scale(), y_range, kdes);
230 }
231 }
232}
233
234#[allow(clippy::type_complexity)]
235fn draw_violin_figure<XR: AsRangedCoord<Value = f64>, YR: AsRangedCoord<Value = f64>>(
236 root_area: DrawingArea<SVGBackend, Shift>,
237 unit: &'static str,
238 x_range: XR,
239 y_range: YR,
240 data: Vec<(&str, Box<[f64]>, Box<[f64]>)>,
241) where
242 XR::CoordDescType: PlottersValueFormatter<f64>,
243 YR::CoordDescType: PlottersValueFormatter<f64>,
244{
245 let mut chart = ChartBuilder::on(&root_area)
246 .margin((5).percent())
247 .set_label_area_size(LabelAreaPosition::Left, (10).percent_width().min(60))
248 .set_label_area_size(LabelAreaPosition::Bottom, (5).percent_width().min(40))
249 .build_cartesian_2d(x_range, y_range)
250 .unwrap();
251
252 chart
253 .configure_mesh()
254 .disable_mesh()
255 .y_desc("Input")
256 .x_desc(format!("Average time ({})", unit))
257 .y_label_style((DEFAULT_FONT, 10))
258 .y_label_formatter(&|v: &f64| data[v.round() as usize].0.to_string())
259 .y_labels(data.len())
260 .draw()
261 .unwrap();
262
263 for (i, (_, x, y)) in data.into_iter().enumerate() {
264 let base = i as f64;
265
266 chart
267 .draw_series(AreaSeries::new(
268 x.iter().zip(y.iter()).map(|(x, y)| (*x, base + *y / 2.0)),
269 base,
270 DARK_BLUE,
271 ))
272 .unwrap();
273
274 chart
275 .draw_series(AreaSeries::new(
276 x.iter().zip(y.iter()).map(|(x, y)| (*x, base - *y / 2.0)),
277 base,
278 DARK_BLUE,
279 ))
280 .unwrap();
281 }
282}