1use std::fmt::{Debug, Formatter};
19use std::time::Duration;
20
21use plotters::prelude::*;
22
23use crate::measurements::{Measurements, Point};
24
25pub struct PlotConfig<'a> {
28 title: &'a str,
29 caption: &'a str,
30 x_label: &'a str,
31 y_label: &'a str,
32 scale: Scale,
33}
34
35pub enum Scale {
37 Linear,
39 LogLog,
41}
42
43impl<'a> PlotConfig<'a> {
44 pub fn new(
48 title: &'a str,
49 caption: &'a str,
50 x_label: &'a str,
51 y_label: &'a str,
52 scale: Scale,
53 ) -> PlotConfig<'a> {
54 PlotConfig {
55 title,
56 caption,
57 x_label,
58 y_label,
59 scale,
60 }
61 }
62
63 pub fn with_x_label(mut self, x_label: &'a str) -> PlotConfig<'a> {
65 self.x_label = x_label;
66 self
67 }
68
69 pub fn with_y_label(mut self, y_label: &'a str) -> PlotConfig<'a> {
71 self.y_label = y_label;
72 self
73 }
74
75 pub fn with_title(mut self, title: &'a str) -> PlotConfig<'a> {
77 self.title = title;
78 self
79 }
80
81 pub fn with_caption(mut self, caption: &'a str) -> PlotConfig<'a> {
83 self.caption = caption;
84 self
85 }
86
87 pub fn with_scale(mut self, scale: Scale) -> PlotConfig<'a> {
89 self.scale = scale;
90 self
91 }
92}
93
94impl<'a> Default for PlotConfig<'a> {
95 fn default() -> PlotConfig<'a> {
96 PlotConfig::new(
97 "Measurements plot",
98 "Caption",
99 "Size",
100 "Time",
101 Scale::Linear,
102 )
103 }
104}
105
106enum Precision {
107 Nanoseconds,
108 Microseconds,
109 Milliseconds,
110 Seconds,
111}
112
113impl Precision {
114 const MAX_U32: u128 = u32::MAX as u128;
115
116 fn get_precision_u32(duration: Duration) -> Self {
117 if duration.as_nanos() < Self::MAX_U32 {
118 Precision::Nanoseconds
119 } else if duration.as_micros() < Self::MAX_U32 {
120 Precision::Microseconds
121 } else if duration.as_millis() < Self::MAX_U32 {
122 Precision::Milliseconds
123 } else {
124 Precision::Seconds
125 }
126 }
127
128 fn as_u32(&self, duration: Duration) -> u32 {
129 match self {
130 Precision::Nanoseconds => duration.as_nanos() as u32,
131 Precision::Microseconds => duration.as_micros() as u32,
132 Precision::Milliseconds => duration.as_millis() as u32,
133 Precision::Seconds => duration.as_secs() as u32,
134 }
135 }
136}
137
138impl Debug for Precision {
139 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
140 match self {
141 Precision::Nanoseconds => write!(f, "ns"),
142 Precision::Microseconds => write!(f, "μs"),
143 Precision::Milliseconds => write!(f, "ms"),
144 Precision::Seconds => write!(f, "s"),
145 }
146 }
147}
148
149pub fn time_plot(file_name: &str, measurements: Measurements, config: &PlotConfig) {
159 let x_min = measurements.min_length() as u32;
160 let x_max = measurements.max_length() as u32;
161
162 let max_time = measurements.max_time();
163 let y_precision = Precision::get_precision_u32(max_time);
164 let y_min = y_precision.as_u32(measurements.min_time());
165 let y_max = y_precision.as_u32(max_time);
166
167 let mut measurements = measurements.measurements;
168
169 let root = SVGBackend::new(file_name, (1024, 768)).into_drawing_area();
171 root.fill(&WHITE).unwrap();
172
173 let (upper, lower) = root.split_vertically(750);
174
175 lower
176 .titled(
177 config.title,
178 ("sans-serif", 10).into_font().color(&BLACK.mix(0.5)),
179 )
180 .unwrap();
181
182 let caption = config.caption.to_string();
183
184 let mut binding = ChartBuilder::on(&upper);
185
186 let chart_builder = binding
187 .caption(&caption, ("sans-serif", (5).percent_height()))
188 .set_label_area_size(LabelAreaPosition::Left, (8).percent())
189 .set_label_area_size(LabelAreaPosition::Bottom, (4).percent())
190 .margin((1).percent());
191
192 match config.scale {
193 Scale::Linear => {
194 let mut chart = chart_builder
195 .build_cartesian_2d(x_min..x_max, y_min..y_max)
196 .unwrap();
197 chart
198 .configure_mesh()
199 .x_desc(config.x_label)
200 .y_desc(format!("{} ({:?})", config.x_label, y_precision))
201 .draw()
202 .unwrap();
203
204 for (i, measurement) in measurements.iter_mut().enumerate() {
206 measurement.measurement.sort_by_key(|a| a.size);
207
208 let color = Palette99::pick(i).mix(0.9);
209 chart
210 .draw_series(LineSeries::new(
211 measurement
212 .measurement
213 .iter()
214 .map(|&Point { size, time, .. }| {
215 (size as u32, y_precision.as_u32(time))
216 }),
217 color.stroke_width(3),
218 ))
219 .unwrap()
220 .label(&measurement.algorithm_name)
221 .legend(move |(x, y)| {
222 Rectangle::new([(x, y - 5), (x + 10, y + 5)], color.filled())
223 });
224 }
225
226 chart
227 .configure_series_labels()
228 .border_style(BLACK)
229 .draw()
230 .unwrap();
231 }
232 Scale::LogLog => {
233 let mut chart = chart_builder
234 .build_cartesian_2d((x_min..x_max).log_scale(), (y_min..y_max).log_scale())
235 .unwrap();
236 chart
237 .configure_mesh()
238 .x_desc(config.x_label)
239 .y_desc(format!("{} ({:?})", config.x_label, y_precision))
240 .draw()
241 .unwrap();
242
243 for (i, measurement) in measurements.iter_mut().enumerate() {
245 measurement.measurement.sort_by_key(|a| a.size);
246
247 let color = Palette99::pick(i).mix(0.9);
248 chart
249 .draw_series(LineSeries::new(
250 measurement
251 .measurement
252 .iter()
253 .map(|&Point { size, time, .. }| {
254 (size as u32, y_precision.as_u32(time))
255 }),
256 color.stroke_width(3),
257 ))
258 .unwrap()
259 .label(&measurement.algorithm_name)
260 .legend(move |(x, y)| {
261 Rectangle::new([(x, y - 5), (x + 10, y + 5)], color.filled())
262 });
263 }
264
265 chart
266 .configure_series_labels()
267 .border_style(BLACK)
268 .draw()
269 .unwrap();
270 }
271 };
272
273 root.present().expect(
275 "Unable to write result to file, please make sure 'results' dir exists under current dir",
276 );
277 println!("Result has been saved to {file_name}");
278}