/rust/registry/src/index.crates.io-1949cf8c6b5b557f/criterion-plot-0.5.0/src/lib.rs
Line | Count | Source |
1 | | //! [Criterion]'s plotting library. |
2 | | //! |
3 | | //! [Criterion]: https://github.com/bheisler/criterion.rs |
4 | | //! |
5 | | //! **WARNING** This library is criterion's implementation detail and there no plans to stabilize |
6 | | //! it. In other words, the API may break at any time without notice. |
7 | | //! |
8 | | //! # Examples |
9 | | //! |
10 | | //! - Simple "curves" (based on [`simple.dem`](http://gnuplot.sourceforge.net/demo/simple.html)) |
11 | | //! |
12 | | //!  |
13 | | //! |
14 | | //! ``` |
15 | | //! # use std::fs; |
16 | | //! # use std::path::Path; |
17 | | //! use itertools_num::linspace; |
18 | | //! use criterion_plot::prelude::*; |
19 | | //! |
20 | | //! # if let Err(_) = criterion_plot::version() { |
21 | | //! # return; |
22 | | //! # } |
23 | | //! let ref xs = linspace::<f64>(-10., 10., 51).collect::<Vec<_>>(); |
24 | | //! |
25 | | //! # fs::create_dir_all(Path::new("target/doc/criterion_plot")).unwrap(); |
26 | | //! # assert_eq!(Some(String::new()), |
27 | | //! Figure::new() |
28 | | //! # .set(Font("Helvetica")) |
29 | | //! # .set(FontSize(12.)) |
30 | | //! # .set(Output(Path::new("target/doc/criterion_plot/curve.svg"))) |
31 | | //! # .set(Size(1280, 720)) |
32 | | //! .configure(Key, |k| { |
33 | | //! k.set(Boxed::Yes) |
34 | | //! .set(Position::Inside(Vertical::Top, Horizontal::Left)) |
35 | | //! }) |
36 | | //! .plot(LinesPoints { |
37 | | //! x: xs, |
38 | | //! y: xs.iter().map(|x| x.sin()), |
39 | | //! }, |
40 | | //! |lp| { |
41 | | //! lp.set(Color::DarkViolet) |
42 | | //! .set(Label("sin(x)")) |
43 | | //! .set(LineType::Dash) |
44 | | //! .set(PointSize(1.5)) |
45 | | //! .set(PointType::Circle) |
46 | | //! }) |
47 | | //! .plot(Steps { |
48 | | //! x: xs, |
49 | | //! y: xs.iter().map(|x| x.atan()), |
50 | | //! }, |
51 | | //! |s| { |
52 | | //! s.set(Color::Rgb(0, 158, 115)) |
53 | | //! .set(Label("atan(x)")) |
54 | | //! .set(LineWidth(2.)) |
55 | | //! }) |
56 | | //! .plot(Impulses { |
57 | | //! x: xs, |
58 | | //! y: xs.iter().map(|x| x.atan().cos()), |
59 | | //! }, |
60 | | //! |i| { |
61 | | //! i.set(Color::Rgb(86, 180, 233)) |
62 | | //! .set(Label("cos(atan(x))")) |
63 | | //! }) |
64 | | //! .draw() // (rest of the chain has been omitted) |
65 | | //! # .ok() |
66 | | //! # .and_then(|gnuplot| { |
67 | | //! # gnuplot.wait_with_output().ok().and_then(|p| String::from_utf8(p.stderr).ok()) |
68 | | //! # })); |
69 | | //! ``` |
70 | | //! |
71 | | //! - error bars (based on |
72 | | //! [Julia plotting tutorial](https://plot.ly/julia/error-bars/#Colored-and-Styled-Error-Bars)) |
73 | | //! |
74 | | //!  |
75 | | //! |
76 | | //! ``` |
77 | | //! # use std::fs; |
78 | | //! # use std::path::Path; |
79 | | //! use std::f64::consts::PI; |
80 | | //! |
81 | | //! use itertools_num::linspace; |
82 | | //! use rand::Rng; |
83 | | //! use criterion_plot::prelude::*; |
84 | | //! |
85 | | //! fn sinc(mut x: f64) -> f64 { |
86 | | //! if x == 0. { |
87 | | //! 1. |
88 | | //! } else { |
89 | | //! x *= PI; |
90 | | //! x.sin() / x |
91 | | //! } |
92 | | //! } |
93 | | //! |
94 | | //! # if let Err(_) = criterion_plot::version() { |
95 | | //! # return; |
96 | | //! # } |
97 | | //! let ref xs_ = linspace::<f64>(-4., 4., 101).collect::<Vec<_>>(); |
98 | | //! |
99 | | //! // Fake some data |
100 | | //! let ref mut rng = rand::thread_rng(); |
101 | | //! let xs = linspace::<f64>(-4., 4., 13).skip(1).take(11); |
102 | | //! let ys = xs.map(|x| sinc(x) + 0.05 * rng.gen::<f64>() - 0.025).collect::<Vec<_>>(); |
103 | | //! let y_low = ys.iter().map(|&y| y - 0.025 - 0.075 * rng.gen::<f64>()).collect::<Vec<_>>(); |
104 | | //! let y_high = ys.iter().map(|&y| y + 0.025 + 0.075 * rng.gen::<f64>()).collect::<Vec<_>>(); |
105 | | //! let xs = linspace::<f64>(-4., 4., 13).skip(1).take(11); |
106 | | //! let xs = xs.map(|x| x + 0.2 * rng.gen::<f64>() - 0.1); |
107 | | //! |
108 | | //! # fs::create_dir_all(Path::new("target/doc/criterion_plot")).unwrap(); |
109 | | //! # assert_eq!(Some(String::new()), |
110 | | //! Figure::new() |
111 | | //! # .set(Font("Helvetica")) |
112 | | //! # .set(FontSize(12.)) |
113 | | //! # .set(Output(Path::new("target/doc/criterion_plot/error_bar.svg"))) |
114 | | //! # .set(Size(1280, 720)) |
115 | | //! .configure(Axis::BottomX, |a| { |
116 | | //! a.set(TicLabels { |
117 | | //! labels: &["-π", "0", "π"], |
118 | | //! positions: &[-PI, 0., PI], |
119 | | //! }) |
120 | | //! }) |
121 | | //! .configure(Key, |
122 | | //! |k| k.set(Position::Outside(Vertical::Top, Horizontal::Right))) |
123 | | //! .plot(Lines { |
124 | | //! x: xs_, |
125 | | //! y: xs_.iter().cloned().map(sinc), |
126 | | //! }, |
127 | | //! |l| { |
128 | | //! l.set(Color::Rgb(0, 158, 115)) |
129 | | //! .set(Label("sinc(x)")) |
130 | | //! .set(LineWidth(2.)) |
131 | | //! }) |
132 | | //! .plot(YErrorBars { |
133 | | //! x: xs, |
134 | | //! y: &ys, |
135 | | //! y_low: &y_low, |
136 | | //! y_high: &y_high, |
137 | | //! }, |
138 | | //! |eb| { |
139 | | //! eb.set(Color::DarkViolet) |
140 | | //! .set(LineWidth(2.)) |
141 | | //! .set(PointType::FilledCircle) |
142 | | //! .set(Label("measured")) |
143 | | //! }) |
144 | | //! .draw() // (rest of the chain has been omitted) |
145 | | //! # .ok() |
146 | | //! # .and_then(|gnuplot| { |
147 | | //! # gnuplot.wait_with_output().ok().and_then(|p| String::from_utf8(p.stderr).ok()) |
148 | | //! # })); |
149 | | //! ``` |
150 | | //! |
151 | | //! - Candlesticks (based on |
152 | | //! [`candlesticks.dem`](http://gnuplot.sourceforge.net/demo/candlesticks.html)) |
153 | | //! |
154 | | //!  |
155 | | //! |
156 | | //! ``` |
157 | | //! # use std::fs; |
158 | | //! # use std::path::Path; |
159 | | //! use criterion_plot::prelude::*; |
160 | | //! use rand::Rng; |
161 | | //! |
162 | | //! # if let Err(_) = criterion_plot::version() { |
163 | | //! # return; |
164 | | //! # } |
165 | | //! let xs = 1..11; |
166 | | //! |
167 | | //! // Fake some data |
168 | | //! let mut rng = rand::thread_rng(); |
169 | | //! let bh = xs.clone().map(|_| 5f64 + 2.5 * rng.gen::<f64>()).collect::<Vec<_>>(); |
170 | | //! let bm = xs.clone().map(|_| 2.5f64 + 2.5 * rng.gen::<f64>()).collect::<Vec<_>>(); |
171 | | //! let wh = bh.iter().map(|&y| y + (10. - y) * rng.gen::<f64>()).collect::<Vec<_>>(); |
172 | | //! let wm = bm.iter().map(|&y| y * rng.gen::<f64>()).collect::<Vec<_>>(); |
173 | | //! let m = bm.iter().zip(bh.iter()).map(|(&l, &h)| (h - l) * rng.gen::<f64>() + l) |
174 | | //! .collect::<Vec<_>>(); |
175 | | //! |
176 | | //! # fs::create_dir_all(Path::new("target/doc/criterion_plot")).unwrap(); |
177 | | //! # assert_eq!(Some(String::new()), |
178 | | //! Figure::new() |
179 | | //! # .set(Font("Helvetica")) |
180 | | //! # .set(FontSize(12.)) |
181 | | //! # .set(Output(Path::new("target/doc/criterion_plot/candlesticks.svg"))) |
182 | | //! # .set(Size(1280, 720)) |
183 | | //! .set(BoxWidth(0.2)) |
184 | | //! .configure(Axis::BottomX, |a| a.set(Range::Limits(0., 11.))) |
185 | | //! .plot(Candlesticks { |
186 | | //! x: xs.clone(), |
187 | | //! whisker_min: &wm, |
188 | | //! box_min: &bm, |
189 | | //! box_high: &bh, |
190 | | //! whisker_high: &wh, |
191 | | //! }, |
192 | | //! |cs| { |
193 | | //! cs.set(Color::Rgb(86, 180, 233)) |
194 | | //! .set(Label("Quartiles")) |
195 | | //! .set(LineWidth(2.)) |
196 | | //! }) |
197 | | //! // trick to plot the median |
198 | | //! .plot(Candlesticks { |
199 | | //! x: xs, |
200 | | //! whisker_min: &m, |
201 | | //! box_min: &m, |
202 | | //! box_high: &m, |
203 | | //! whisker_high: &m, |
204 | | //! }, |
205 | | //! |cs| { |
206 | | //! cs.set(Color::Black) |
207 | | //! .set(LineWidth(2.)) |
208 | | //! }) |
209 | | //! .draw() // (rest of the chain has been omitted) |
210 | | //! # .ok() |
211 | | //! # .and_then(|gnuplot| { |
212 | | //! # gnuplot.wait_with_output().ok().and_then(|p| String::from_utf8(p.stderr).ok()) |
213 | | //! # })); |
214 | | //! ``` |
215 | | //! |
216 | | //! - Multiaxis (based on [`multiaxis.dem`](http://gnuplot.sourceforge.net/demo/multiaxis.html)) |
217 | | //! |
218 | | //!  |
219 | | //! |
220 | | //! ``` |
221 | | //! # use std::fs; |
222 | | //! # use std::path::Path; |
223 | | //! use std::f64::consts::PI; |
224 | | //! |
225 | | //! use itertools_num::linspace; |
226 | | //! use num_complex::Complex; |
227 | | //! use criterion_plot::prelude::*; |
228 | | //! |
229 | | //! fn tf(x: f64) -> Complex<f64> { |
230 | | //! Complex::new(0., x) / Complex::new(10., x) / Complex::new(1., x / 10_000.) |
231 | | //! } |
232 | | //! |
233 | | //! # if let Err(_) = criterion_plot::version() { |
234 | | //! # return; |
235 | | //! # } |
236 | | //! let (start, end): (f64, f64) = (1.1, 90_000.); |
237 | | //! let ref xs = linspace(start.ln(), end.ln(), 101).map(|x| x.exp()).collect::<Vec<_>>(); |
238 | | //! let phase = xs.iter().map(|&x| tf(x).arg() * 180. / PI); |
239 | | //! let magnitude = xs.iter().map(|&x| tf(x).norm()); |
240 | | //! |
241 | | //! # fs::create_dir_all(Path::new("target/doc/criterion_plot")).unwrap(); |
242 | | //! # assert_eq!(Some(String::new()), |
243 | | //! Figure::new(). |
244 | | //! # set(Font("Helvetica")). |
245 | | //! # set(FontSize(12.)). |
246 | | //! # set(Output(Path::new("target/doc/criterion_plot/multiaxis.svg"))). |
247 | | //! # set(Size(1280, 720)). |
248 | | //! set(Title("Frequency response")). |
249 | | //! configure(Axis::BottomX, |a| a. |
250 | | //! configure(Grid::Major, |g| g. |
251 | | //! show()). |
252 | | //! set(Label("Angular frequency (rad/s)")). |
253 | | //! set(Range::Limits(start, end)). |
254 | | //! set(Scale::Logarithmic)). |
255 | | //! configure(Axis::LeftY, |a| a. |
256 | | //! set(Label("Gain")). |
257 | | //! set(Scale::Logarithmic)). |
258 | | //! configure(Axis::RightY, |a| a. |
259 | | //! configure(Grid::Major, |g| g. |
260 | | //! show()). |
261 | | //! set(Label("Phase shift (°)"))). |
262 | | //! configure(Key, |k| k. |
263 | | //! set(Position::Inside(Vertical::Top, Horizontal::Center)). |
264 | | //! set(Title(" "))). |
265 | | //! plot(Lines { |
266 | | //! x: xs, |
267 | | //! y: magnitude, |
268 | | //! }, |l| l. |
269 | | //! set(Color::DarkViolet). |
270 | | //! set(Label("Magnitude")). |
271 | | //! set(LineWidth(2.))). |
272 | | //! plot(Lines { |
273 | | //! x: xs, |
274 | | //! y: phase, |
275 | | //! }, |l| l. |
276 | | //! set(Axes::BottomXRightY). |
277 | | //! set(Color::Rgb(0, 158, 115)). |
278 | | //! set(Label("Phase")). |
279 | | //! set(LineWidth(2.))). |
280 | | //! draw(). // (rest of the chain has been omitted) |
281 | | //! # ok().and_then(|gnuplot| { |
282 | | //! # gnuplot.wait_with_output().ok().and_then(|p| { |
283 | | //! # String::from_utf8(p.stderr).ok() |
284 | | //! # }) |
285 | | //! # })); |
286 | | //! ``` |
287 | | //! - Filled curves (based on |
288 | | //! [`transparent.dem`](http://gnuplot.sourceforge.net/demo/transparent.html)) |
289 | | //! |
290 | | //!  |
291 | | //! |
292 | | //! ``` |
293 | | //! # use std::fs; |
294 | | //! # use std::path::Path; |
295 | | //! use std::f64::consts::PI; |
296 | | //! use std::iter; |
297 | | //! |
298 | | //! use itertools_num::linspace; |
299 | | //! use criterion_plot::prelude::*; |
300 | | //! |
301 | | //! # if let Err(_) = criterion_plot::version() { |
302 | | //! # return; |
303 | | //! # } |
304 | | //! let (start, end) = (-5., 5.); |
305 | | //! let ref xs = linspace(start, end, 101).collect::<Vec<_>>(); |
306 | | //! let zeros = iter::repeat(0); |
307 | | //! |
308 | | //! fn gaussian(x: f64, mu: f64, sigma: f64) -> f64 { |
309 | | //! 1. / (((x - mu).powi(2) / 2. / sigma.powi(2)).exp() * sigma * (2. * PI).sqrt()) |
310 | | //! } |
311 | | //! |
312 | | //! # fs::create_dir_all(Path::new("target/doc/criterion_plot")).unwrap(); |
313 | | //! # assert_eq!(Some(String::new()), |
314 | | //! Figure::new() |
315 | | //! # .set(Font("Helvetica")) |
316 | | //! # .set(FontSize(12.)) |
317 | | //! # .set(Output(Path::new("target/doc/criterion_plot/filled_curve.svg"))) |
318 | | //! # .set(Size(1280, 720)) |
319 | | //! .set(Title("Transparent filled curve")) |
320 | | //! .configure(Axis::BottomX, |a| a.set(Range::Limits(start, end))) |
321 | | //! .configure(Axis::LeftY, |a| a.set(Range::Limits(0., 1.))) |
322 | | //! .configure(Key, |k| { |
323 | | //! k.set(Justification::Left) |
324 | | //! .set(Order::SampleText) |
325 | | //! .set(Position::Inside(Vertical::Top, Horizontal::Left)) |
326 | | //! .set(Title("Gaussian Distribution")) |
327 | | //! }) |
328 | | //! .plot(FilledCurve { |
329 | | //! x: xs, |
330 | | //! y1: xs.iter().map(|&x| gaussian(x, 0.5, 0.5)), |
331 | | //! y2: zeros.clone(), |
332 | | //! }, |
333 | | //! |fc| { |
334 | | //! fc.set(Color::ForestGreen) |
335 | | //! .set(Label("μ = 0.5 σ = 0.5")) |
336 | | //! }) |
337 | | //! .plot(FilledCurve { |
338 | | //! x: xs, |
339 | | //! y1: xs.iter().map(|&x| gaussian(x, 2.0, 1.0)), |
340 | | //! y2: zeros.clone(), |
341 | | //! }, |
342 | | //! |fc| { |
343 | | //! fc.set(Color::Gold) |
344 | | //! .set(Label("μ = 2.0 σ = 1.0")) |
345 | | //! .set(Opacity(0.5)) |
346 | | //! }) |
347 | | //! .plot(FilledCurve { |
348 | | //! x: xs, |
349 | | //! y1: xs.iter().map(|&x| gaussian(x, -1.0, 2.0)), |
350 | | //! y2: zeros, |
351 | | //! }, |
352 | | //! |fc| { |
353 | | //! fc.set(Color::Red) |
354 | | //! .set(Label("μ = -1.0 σ = 2.0")) |
355 | | //! .set(Opacity(0.5)) |
356 | | //! }) |
357 | | //! .draw() |
358 | | //! .ok() |
359 | | //! .and_then(|gnuplot| { |
360 | | //! gnuplot.wait_with_output().ok().and_then(|p| String::from_utf8(p.stderr).ok()) |
361 | | //! })); |
362 | | //! ``` |
363 | | |
364 | | #![deny(missing_docs)] |
365 | | #![deny(warnings)] |
366 | | #![deny(bare_trait_objects)] |
367 | | // This lint has lots of false positives ATM, see |
368 | | // https://github.com/Manishearth/rust-clippy/issues/761 |
369 | | #![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))] |
370 | | // False positives with images |
371 | | #![cfg_attr(feature = "cargo-clippy", allow(clippy::doc_markdown))] |
372 | | #![cfg_attr(feature = "cargo-clippy", allow(clippy::many_single_char_names))] |
373 | | |
374 | | extern crate cast; |
375 | | #[macro_use] |
376 | | extern crate itertools; |
377 | | |
378 | | use std::borrow::Cow; |
379 | | use std::fmt; |
380 | | use std::fs::File; |
381 | | use std::io; |
382 | | use std::num::ParseIntError; |
383 | | use std::path::Path; |
384 | | use std::process::{Child, Command}; |
385 | | use std::str; |
386 | | |
387 | | use crate::data::Matrix; |
388 | | use crate::traits::{Configure, Set}; |
389 | | |
390 | | mod data; |
391 | | mod display; |
392 | | mod map; |
393 | | |
394 | | pub mod axis; |
395 | | pub mod candlestick; |
396 | | pub mod curve; |
397 | | pub mod errorbar; |
398 | | pub mod filledcurve; |
399 | | pub mod grid; |
400 | | pub mod key; |
401 | | pub mod prelude; |
402 | | pub mod proxy; |
403 | | pub mod traits; |
404 | | |
405 | | /// Plot container |
406 | | #[derive(Clone)] |
407 | | pub struct Figure { |
408 | | alpha: Option<f64>, |
409 | | axes: map::axis::Map<axis::Properties>, |
410 | | box_width: Option<f64>, |
411 | | font: Option<Cow<'static, str>>, |
412 | | font_size: Option<f64>, |
413 | | key: Option<key::Properties>, |
414 | | output: Cow<'static, Path>, |
415 | | plots: Vec<Plot>, |
416 | | size: Option<(usize, usize)>, |
417 | | terminal: Terminal, |
418 | | tics: map::axis::Map<String>, |
419 | | title: Option<Cow<'static, str>>, |
420 | | } |
421 | | |
422 | | impl Figure { |
423 | | /// Creates an empty figure |
424 | 0 | pub fn new() -> Figure { |
425 | 0 | Figure { |
426 | 0 | alpha: None, |
427 | 0 | axes: map::axis::Map::new(), |
428 | 0 | box_width: None, |
429 | 0 | font: None, |
430 | 0 | font_size: None, |
431 | 0 | key: None, |
432 | 0 | output: Cow::Borrowed(Path::new("output.plot")), |
433 | 0 | plots: Vec::new(), |
434 | 0 | size: None, |
435 | 0 | terminal: Terminal::Svg, |
436 | 0 | tics: map::axis::Map::new(), |
437 | 0 | title: None, |
438 | 0 | } |
439 | 0 | } |
440 | | |
441 | | // Allow clippy::format_push_string even with older versions of rust (<1.62) which |
442 | | // don't have it defined. |
443 | | #[allow(clippy::all)] |
444 | 0 | fn script(&self) -> Vec<u8> { |
445 | 0 | let mut s = String::new(); |
446 | | |
447 | 0 | s.push_str(&format!( |
448 | 0 | "set output '{}'\n", |
449 | 0 | self.output.display().to_string().replace('\'', "''") |
450 | 0 | )); |
451 | | |
452 | 0 | if let Some(width) = self.box_width { |
453 | 0 | s.push_str(&format!("set boxwidth {}\n", width)) |
454 | 0 | } |
455 | | |
456 | 0 | if let Some(ref title) = self.title { |
457 | 0 | s.push_str(&format!("set title '{}'\n", title)) |
458 | 0 | } |
459 | | |
460 | 0 | for axis in self.axes.iter() { |
461 | 0 | s.push_str(&axis.script()); |
462 | 0 | } |
463 | | |
464 | 0 | for (_, script) in self.tics.iter() { |
465 | 0 | s.push_str(script); |
466 | 0 | } |
467 | | |
468 | 0 | if let Some(ref key) = self.key { |
469 | 0 | s.push_str(&key.script()) |
470 | 0 | } |
471 | | |
472 | 0 | if let Some(alpha) = self.alpha { |
473 | 0 | s.push_str(&format!("set style fill transparent solid {}\n", alpha)) |
474 | 0 | } |
475 | | |
476 | 0 | s.push_str(&format!("set terminal {} dashed", self.terminal.display())); |
477 | | |
478 | 0 | if let Some((width, height)) = self.size { |
479 | 0 | s.push_str(&format!(" size {}, {}", width, height)) |
480 | 0 | } |
481 | | |
482 | 0 | if let Some(ref name) = self.font { |
483 | 0 | if let Some(size) = self.font_size { |
484 | 0 | s.push_str(&format!(" font '{},{}'", name, size)) |
485 | | } else { |
486 | 0 | s.push_str(&format!(" font '{}'", name)) |
487 | | } |
488 | 0 | } |
489 | | |
490 | | // TODO This removes the crossbars from the ends of error bars, but should be configurable |
491 | 0 | s.push_str("\nunset bars\n"); |
492 | | |
493 | 0 | let mut is_first_plot = true; |
494 | 0 | for plot in &self.plots { |
495 | 0 | let data = plot.data(); |
496 | | |
497 | 0 | if data.bytes().is_empty() { |
498 | 0 | continue; |
499 | 0 | } |
500 | | |
501 | 0 | if is_first_plot { |
502 | 0 | s.push_str("plot "); |
503 | 0 | is_first_plot = false; |
504 | 0 | } else { |
505 | 0 | s.push_str(", "); |
506 | 0 | } |
507 | | |
508 | 0 | s.push_str(&format!( |
509 | 0 | "'-' binary endian=little record={} format='%float64' using ", |
510 | 0 | data.nrows() |
511 | 0 | )); |
512 | | |
513 | 0 | let mut is_first_col = true; |
514 | 0 | for col in 0..data.ncols() { |
515 | 0 | if is_first_col { |
516 | 0 | is_first_col = false; |
517 | 0 | } else { |
518 | 0 | s.push(':'); |
519 | 0 | } |
520 | 0 | s.push_str(&(col + 1).to_string()); |
521 | | } |
522 | 0 | s.push(' '); |
523 | | |
524 | 0 | s.push_str(plot.script()); |
525 | | } |
526 | | |
527 | 0 | let mut buffer = s.into_bytes(); |
528 | 0 | let mut is_first = true; |
529 | 0 | for plot in &self.plots { |
530 | 0 | if is_first { |
531 | 0 | is_first = false; |
532 | 0 | buffer.push(b'\n'); |
533 | 0 | } |
534 | 0 | buffer.extend_from_slice(plot.data().bytes()); |
535 | | } |
536 | | |
537 | 0 | buffer |
538 | 0 | } |
539 | | |
540 | | /// Spawns a drawing child process |
541 | | /// |
542 | | /// NOTE: stderr, stdin, and stdout are piped |
543 | 0 | pub fn draw(&mut self) -> io::Result<Child> { |
544 | | use std::process::Stdio; |
545 | | |
546 | 0 | let mut gnuplot = Command::new("gnuplot") |
547 | 0 | .stderr(Stdio::piped()) |
548 | 0 | .stdin(Stdio::piped()) |
549 | 0 | .stdout(Stdio::piped()) |
550 | 0 | .spawn()?; |
551 | 0 | self.dump(gnuplot.stdin.as_mut().unwrap())?; |
552 | 0 | Ok(gnuplot) |
553 | 0 | } |
554 | | |
555 | | /// Dumps the script required to produce the figure into `sink` |
556 | 0 | pub fn dump<W>(&mut self, sink: &mut W) -> io::Result<&mut Figure> |
557 | 0 | where |
558 | 0 | W: io::Write, |
559 | | { |
560 | 0 | sink.write_all(&self.script())?; |
561 | 0 | Ok(self) |
562 | 0 | } |
563 | | |
564 | | /// Saves the script required to produce the figure to `path` |
565 | 0 | pub fn save(&self, path: &Path) -> io::Result<&Figure> { |
566 | | use std::io::Write; |
567 | | |
568 | 0 | File::create(path)?.write_all(&self.script())?; |
569 | 0 | Ok(self) |
570 | 0 | } |
571 | | } |
572 | | |
573 | | impl Configure<Axis> for Figure { |
574 | | type Properties = axis::Properties; |
575 | | |
576 | | /// Configures an axis |
577 | 0 | fn configure<F>(&mut self, axis: Axis, configure: F) -> &mut Figure |
578 | 0 | where |
579 | 0 | F: FnOnce(&mut axis::Properties) -> &mut axis::Properties, |
580 | | { |
581 | 0 | if self.axes.contains_key(axis) { |
582 | 0 | configure(self.axes.get_mut(axis).unwrap()); |
583 | 0 | } else { |
584 | 0 | let mut properties = Default::default(); |
585 | 0 | configure(&mut properties); |
586 | 0 | self.axes.insert(axis, properties); |
587 | 0 | } |
588 | 0 | self |
589 | 0 | } Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::regression::regression_figure::{closure#0}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::regression::regression_figure::{closure#1}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::regression::regression_comparison_figure::{closure#0}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::regression::regression_comparison_figure::{closure#1}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::distributions::abs_distribution::{closure#3}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::distributions::abs_distribution::{closure#4}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::distributions::rel_distribution::{closure#3}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::distributions::rel_distribution::{closure#5}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::iteration_times::iteration_times_figure::{closure#2}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::iteration_times::iteration_times_figure::{closure#1}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::iteration_times::iteration_times_comparison_figure::{closure#2}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::iteration_times::iteration_times_comparison_figure::{closure#1}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::pdf::pdf_comparison_figure::{closure#0}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::pdf::pdf_comparison_figure::{closure#2}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::pdf::pdf_comparison_figure::{closure#1}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::pdf::pdf::{closure#2}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::pdf::pdf::{closure#3}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::pdf::pdf::{closure#1}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::pdf::pdf_small::{closure#0}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::pdf::pdf_small::{closure#2}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::pdf::pdf_small::{closure#1}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::t_test::t_test::{closure#0}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::t_test::t_test::{closure#1}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::summary::line_comparison::{closure#3}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::summary::line_comparison::{closure#1}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::summary::violin::{closure#4}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<criterion::plot::gnuplot_backend::summary::violin::{closure#5}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Axis>>::configure::<_> |
590 | | } |
591 | | |
592 | | impl Configure<Key> for Figure { |
593 | | type Properties = key::Properties; |
594 | | |
595 | | /// Configures the key (legend) |
596 | 0 | fn configure<F>(&mut self, _: Key, configure: F) -> &mut Figure |
597 | 0 | where |
598 | 0 | F: FnOnce(&mut key::Properties) -> &mut key::Properties, |
599 | | { |
600 | 0 | if self.key.is_some() { |
601 | 0 | configure(self.key.as_mut().unwrap()); |
602 | 0 | } else { |
603 | 0 | let mut key = Default::default(); |
604 | 0 | configure(&mut key); |
605 | 0 | self.key = Some(key); |
606 | 0 | } |
607 | 0 | self |
608 | 0 | } Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Key>>::configure::<criterion::plot::gnuplot_backend::regression::regression::{closure#0}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Key>>::configure::<criterion::plot::gnuplot_backend::regression::regression_small::{closure#0}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Key>>::configure::<criterion::plot::gnuplot_backend::regression::regression_comparison_small::{closure#0}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Key>>::configure::<criterion::plot::gnuplot_backend::regression::regression_comparison_figure::{closure#2}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Key>>::configure::<criterion::plot::gnuplot_backend::distributions::abs_distribution::{closure#5}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Key>>::configure::<criterion::plot::gnuplot_backend::distributions::rel_distribution::{closure#4}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Key>>::configure::<criterion::plot::gnuplot_backend::iteration_times::iteration_times::{closure#0}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Key>>::configure::<criterion::plot::gnuplot_backend::iteration_times::iteration_times_small::{closure#0}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Key>>::configure::<criterion::plot::gnuplot_backend::iteration_times::iteration_times_comparison_small::{closure#0}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Key>>::configure::<criterion::plot::gnuplot_backend::iteration_times::iteration_times_comparison_figure::{closure#3}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Key>>::configure::<criterion::plot::gnuplot_backend::pdf::pdf_comparison_small::{closure#0}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Key>>::configure::<criterion::plot::gnuplot_backend::pdf::pdf_comparison_figure::{closure#3}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Key>>::configure::<criterion::plot::gnuplot_backend::pdf::pdf::{closure#4}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Key>>::configure::<criterion::plot::gnuplot_backend::pdf::pdf_small::{closure#3}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Key>>::configure::<criterion::plot::gnuplot_backend::t_test::t_test::{closure#2}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Key>>::configure::<criterion::plot::gnuplot_backend::summary::line_comparison::{closure#0}>Unexecuted instantiation: <criterion_plot::Figure as criterion_plot::traits::Configure<criterion_plot::Key>>::configure::<_> |
609 | | } |
610 | | |
611 | | impl Set<BoxWidth> for Figure { |
612 | | /// Changes the box width of all the box related plots (bars, candlesticks, etc) |
613 | | /// |
614 | | /// **Note** The default value is 0 |
615 | | /// |
616 | | /// # Panics |
617 | | /// |
618 | | /// Panics if `width` is a negative value |
619 | 0 | fn set(&mut self, width: BoxWidth) -> &mut Figure { |
620 | 0 | let width = width.0; |
621 | | |
622 | 0 | assert!(width >= 0.); |
623 | | |
624 | 0 | self.box_width = Some(width); |
625 | 0 | self |
626 | 0 | } |
627 | | } |
628 | | |
629 | | impl Set<Font> for Figure { |
630 | | /// Changes the font |
631 | 0 | fn set(&mut self, font: Font) -> &mut Figure { |
632 | 0 | self.font = Some(font.0); |
633 | 0 | self |
634 | 0 | } |
635 | | } |
636 | | |
637 | | impl Set<FontSize> for Figure { |
638 | | /// Changes the size of the font |
639 | | /// |
640 | | /// # Panics |
641 | | /// |
642 | | /// Panics if `size` is a non-positive value |
643 | 0 | fn set(&mut self, size: FontSize) -> &mut Figure { |
644 | 0 | let size = size.0; |
645 | | |
646 | 0 | assert!(size >= 0.); |
647 | | |
648 | 0 | self.font_size = Some(size); |
649 | 0 | self |
650 | 0 | } |
651 | | } |
652 | | |
653 | | impl Set<Output> for Figure { |
654 | | /// Changes the output file |
655 | | /// |
656 | | /// **Note** The default output file is `output.plot` |
657 | 0 | fn set(&mut self, output: Output) -> &mut Figure { |
658 | 0 | self.output = output.0; |
659 | 0 | self |
660 | 0 | } |
661 | | } |
662 | | |
663 | | impl Set<Size> for Figure { |
664 | | /// Changes the figure size |
665 | 0 | fn set(&mut self, size: Size) -> &mut Figure { |
666 | 0 | self.size = Some((size.0, size.1)); |
667 | 0 | self |
668 | 0 | } |
669 | | } |
670 | | |
671 | | impl Set<Terminal> for Figure { |
672 | | /// Changes the output terminal |
673 | | /// |
674 | | /// **Note** By default, the terminal is set to `Svg` |
675 | 0 | fn set(&mut self, terminal: Terminal) -> &mut Figure { |
676 | 0 | self.terminal = terminal; |
677 | 0 | self |
678 | 0 | } |
679 | | } |
680 | | |
681 | | impl Set<Title> for Figure { |
682 | | /// Sets the title |
683 | 0 | fn set(&mut self, title: Title) -> &mut Figure { |
684 | 0 | self.title = Some(title.0); |
685 | 0 | self |
686 | 0 | } |
687 | | } |
688 | | |
689 | | impl Default for Figure { |
690 | 0 | fn default() -> Self { |
691 | 0 | Self::new() |
692 | 0 | } |
693 | | } |
694 | | |
695 | | /// Box width for box-related plots: bars, candlesticks, etc |
696 | | #[derive(Clone, Copy)] |
697 | | pub struct BoxWidth(pub f64); |
698 | | |
699 | | /// A font name |
700 | | pub struct Font(Cow<'static, str>); |
701 | | |
702 | | /// The size of a font |
703 | | #[derive(Clone, Copy)] |
704 | | pub struct FontSize(pub f64); |
705 | | |
706 | | /// The key or legend |
707 | | #[derive(Clone, Copy)] |
708 | | pub struct Key; |
709 | | |
710 | | /// Plot label |
711 | | pub struct Label(Cow<'static, str>); |
712 | | |
713 | | /// Width of the lines |
714 | | #[derive(Clone, Copy)] |
715 | | pub struct LineWidth(pub f64); |
716 | | |
717 | | /// Fill color opacity |
718 | | #[derive(Clone, Copy)] |
719 | | pub struct Opacity(pub f64); |
720 | | |
721 | | /// Output file path |
722 | | pub struct Output(Cow<'static, Path>); |
723 | | |
724 | | /// Size of the points |
725 | | #[derive(Clone, Copy)] |
726 | | pub struct PointSize(pub f64); |
727 | | |
728 | | /// Axis range |
729 | | #[derive(Clone, Copy)] |
730 | | pub enum Range { |
731 | | /// Autoscale the axis |
732 | | Auto, |
733 | | /// Set the limits of the axis |
734 | | Limits(f64, f64), |
735 | | } |
736 | | |
737 | | /// Figure size |
738 | | #[derive(Clone, Copy)] |
739 | | pub struct Size(pub usize, pub usize); |
740 | | |
741 | | /// Labels attached to the tics of an axis |
742 | | pub struct TicLabels<P, L> { |
743 | | /// Labels to attach to the tics |
744 | | pub labels: L, |
745 | | /// Position of the tics on the axis |
746 | | pub positions: P, |
747 | | } |
748 | | |
749 | | /// Figure title |
750 | | pub struct Title(Cow<'static, str>); |
751 | | |
752 | | /// A pair of axes that define a coordinate system |
753 | | #[allow(missing_docs)] |
754 | | #[derive(Clone, Copy)] |
755 | | pub enum Axes { |
756 | | BottomXLeftY, |
757 | | BottomXRightY, |
758 | | TopXLeftY, |
759 | | TopXRightY, |
760 | | } |
761 | | |
762 | | /// A coordinate axis |
763 | | #[derive(Clone, Copy)] |
764 | | pub enum Axis { |
765 | | /// X axis on the bottom side of the figure |
766 | | BottomX, |
767 | | /// Y axis on the left side of the figure |
768 | | LeftY, |
769 | | /// Y axis on the right side of the figure |
770 | | RightY, |
771 | | /// X axis on the top side of the figure |
772 | | TopX, |
773 | | } |
774 | | |
775 | | impl Axis { |
776 | 0 | fn next(self) -> Option<Axis> { |
777 | | use crate::Axis::*; |
778 | | |
779 | 0 | match self { |
780 | 0 | BottomX => Some(LeftY), |
781 | 0 | LeftY => Some(RightY), |
782 | 0 | RightY => Some(TopX), |
783 | 0 | TopX => None, |
784 | | } |
785 | 0 | } |
786 | | } |
787 | | |
788 | | /// Color |
789 | | #[allow(missing_docs)] |
790 | | #[derive(Clone, Copy)] |
791 | | pub enum Color { |
792 | | Black, |
793 | | Blue, |
794 | | Cyan, |
795 | | DarkViolet, |
796 | | ForestGreen, |
797 | | Gold, |
798 | | Gray, |
799 | | Green, |
800 | | Magenta, |
801 | | Red, |
802 | | /// Custom RGB color |
803 | | Rgb(u8, u8, u8), |
804 | | White, |
805 | | Yellow, |
806 | | } |
807 | | |
808 | | /// Grid line |
809 | | #[derive(Clone, Copy)] |
810 | | pub enum Grid { |
811 | | /// Major gridlines |
812 | | Major, |
813 | | /// Minor gridlines |
814 | | Minor, |
815 | | } |
816 | | |
817 | | impl Grid { |
818 | 0 | fn next(self) -> Option<Grid> { |
819 | | use crate::Grid::*; |
820 | | |
821 | 0 | match self { |
822 | 0 | Major => Some(Minor), |
823 | 0 | Minor => None, |
824 | | } |
825 | 0 | } |
826 | | } |
827 | | |
828 | | /// Line type |
829 | | #[allow(missing_docs)] |
830 | | #[derive(Clone, Copy)] |
831 | | pub enum LineType { |
832 | | Dash, |
833 | | Dot, |
834 | | DotDash, |
835 | | DotDotDash, |
836 | | /// Line made of minimally sized dots |
837 | | SmallDot, |
838 | | Solid, |
839 | | } |
840 | | |
841 | | /// Point type |
842 | | #[allow(missing_docs)] |
843 | | #[derive(Clone, Copy)] |
844 | | pub enum PointType { |
845 | | Circle, |
846 | | FilledCircle, |
847 | | FilledSquare, |
848 | | FilledTriangle, |
849 | | Plus, |
850 | | Square, |
851 | | Star, |
852 | | Triangle, |
853 | | X, |
854 | | } |
855 | | |
856 | | /// Axis scale |
857 | | #[allow(missing_docs)] |
858 | | #[derive(Clone, Copy)] |
859 | | pub enum Scale { |
860 | | Linear, |
861 | | Logarithmic, |
862 | | } |
863 | | |
864 | | /// Axis scale factor |
865 | | #[allow(missing_docs)] |
866 | | #[derive(Clone, Copy)] |
867 | | pub struct ScaleFactor(pub f64); |
868 | | |
869 | | /// Output terminal |
870 | | #[allow(missing_docs)] |
871 | | #[derive(Clone, Copy)] |
872 | | pub enum Terminal { |
873 | | Svg, |
874 | | } |
875 | | |
876 | | /// Not public version of `std::default::Default`, used to not leak default constructors into the |
877 | | /// public API |
878 | | trait Default { |
879 | | /// Creates `Properties` with default configuration |
880 | | fn default() -> Self; |
881 | | } |
882 | | |
883 | | /// Enums that can produce gnuplot code |
884 | | trait Display<S> { |
885 | | /// Translates the enum in gnuplot code |
886 | | fn display(&self) -> S; |
887 | | } |
888 | | |
889 | | /// Curve variant of Default |
890 | | trait CurveDefault<S> { |
891 | | /// Creates `curve::Properties` with default configuration |
892 | | fn default(s: S) -> Self; |
893 | | } |
894 | | |
895 | | /// Error bar variant of Default |
896 | | trait ErrorBarDefault<S> { |
897 | | /// Creates `errorbar::Properties` with default configuration |
898 | | fn default(s: S) -> Self; |
899 | | } |
900 | | |
901 | | /// Structs that can produce gnuplot code |
902 | | trait Script { |
903 | | /// Translates some configuration struct into gnuplot code |
904 | | fn script(&self) -> String; |
905 | | } |
906 | | |
907 | | #[derive(Clone)] |
908 | | struct Plot { |
909 | | data: Matrix, |
910 | | script: String, |
911 | | } |
912 | | |
913 | | impl Plot { |
914 | 0 | fn new<S>(data: Matrix, script: &S) -> Plot |
915 | 0 | where |
916 | 0 | S: Script, |
917 | | { |
918 | 0 | Plot { |
919 | 0 | data, |
920 | 0 | script: script.script(), |
921 | 0 | } |
922 | 0 | } Unexecuted instantiation: <criterion_plot::Plot>::new::<criterion_plot::filledcurve::Properties> Unexecuted instantiation: <criterion_plot::Plot>::new::<criterion_plot::curve::Properties> Unexecuted instantiation: <criterion_plot::Plot>::new::<_> |
923 | | |
924 | 0 | fn data(&self) -> &Matrix { |
925 | 0 | &self.data |
926 | 0 | } |
927 | | |
928 | 0 | fn script(&self) -> &str { |
929 | 0 | &self.script |
930 | 0 | } |
931 | | } |
932 | | |
933 | | /// Possible errors when parsing gnuplot's version string |
934 | | #[derive(Debug)] |
935 | | pub enum VersionError { |
936 | | /// The `gnuplot` command couldn't be executed |
937 | | Exec(io::Error), |
938 | | /// The `gnuplot` command returned an error message |
939 | | Error(String), |
940 | | /// The `gnuplot` command returned invalid utf-8 |
941 | | OutputError, |
942 | | /// The `gnuplot` command returned an unparseable string |
943 | | ParseError(String), |
944 | | } |
945 | | impl fmt::Display for VersionError { |
946 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
947 | 0 | match self { |
948 | 0 | VersionError::Exec(err) => write!(f, "`gnuplot --version` failed: {}", err), |
949 | 0 | VersionError::Error(msg) => { |
950 | 0 | write!(f, "`gnuplot --version` failed with error message:\n{}", msg) |
951 | | } |
952 | 0 | VersionError::OutputError => write!(f, "`gnuplot --version` returned invalid utf-8"), |
953 | 0 | VersionError::ParseError(msg) => write!( |
954 | 0 | f, |
955 | 0 | "`gnuplot --version` returned an unparseable version string: {}", |
956 | | msg |
957 | | ), |
958 | | } |
959 | 0 | } |
960 | | } |
961 | | impl ::std::error::Error for VersionError { |
962 | 0 | fn description(&self) -> &str { |
963 | 0 | match self { |
964 | 0 | VersionError::Exec(_) => "Execution Error", |
965 | 0 | VersionError::Error(_) => "Other Error", |
966 | 0 | VersionError::OutputError => "Output Error", |
967 | 0 | VersionError::ParseError(_) => "Parse Error", |
968 | | } |
969 | 0 | } |
970 | | |
971 | 0 | fn cause(&self) -> Option<&dyn ::std::error::Error> { |
972 | 0 | match self { |
973 | 0 | VersionError::Exec(err) => Some(err), |
974 | 0 | _ => None, |
975 | | } |
976 | 0 | } |
977 | | } |
978 | | |
979 | | /// Structure representing a gnuplot version number. |
980 | | pub struct Version { |
981 | | /// The major version number |
982 | | pub major: usize, |
983 | | /// The minor version number |
984 | | pub minor: usize, |
985 | | /// The patch level |
986 | | pub patch: String, |
987 | | } |
988 | | |
989 | | /// Returns `gnuplot` version |
990 | 0 | pub fn version() -> Result<Version, VersionError> { |
991 | 0 | let command_output = Command::new("gnuplot") |
992 | 0 | .arg("--version") |
993 | 0 | .output() |
994 | 0 | .map_err(VersionError::Exec)?; |
995 | 0 | if !command_output.status.success() { |
996 | 0 | let error = |
997 | 0 | String::from_utf8(command_output.stderr).map_err(|_| VersionError::OutputError)?; |
998 | 0 | return Err(VersionError::Error(error)); |
999 | 0 | } |
1000 | | |
1001 | 0 | let output = String::from_utf8(command_output.stdout).map_err(|_| VersionError::OutputError)?; |
1002 | | |
1003 | 0 | parse_version(&output).map_err(|_| VersionError::ParseError(output.clone())) |
1004 | 0 | } |
1005 | | |
1006 | 0 | fn parse_version(version_str: &str) -> Result<Version, Option<ParseIntError>> { |
1007 | 0 | let mut words = version_str.split_whitespace().skip(1); |
1008 | 0 | let mut version = words.next().ok_or(None)?.split('.'); |
1009 | 0 | let major = version.next().ok_or(None)?.parse()?; |
1010 | 0 | let minor = version.next().ok_or(None)?.parse()?; |
1011 | 0 | let patchlevel = words.nth(1).ok_or(None)?.to_owned(); |
1012 | | |
1013 | 0 | Ok(Version { |
1014 | 0 | major, |
1015 | 0 | minor, |
1016 | 0 | patch: patchlevel, |
1017 | 0 | }) |
1018 | 0 | } |
1019 | | |
1020 | 0 | fn scale_factor(map: &map::axis::Map<axis::Properties>, axes: Axes) -> (f64, f64) { |
1021 | | use crate::Axes::*; |
1022 | | use crate::Axis::*; |
1023 | | |
1024 | 0 | match axes { |
1025 | 0 | BottomXLeftY => ( |
1026 | 0 | map.get(BottomX).map_or(1., ScaleFactorTrait::scale_factor), |
1027 | 0 | map.get(LeftY).map_or(1., ScaleFactorTrait::scale_factor), |
1028 | 0 | ), |
1029 | 0 | BottomXRightY => ( |
1030 | 0 | map.get(BottomX).map_or(1., ScaleFactorTrait::scale_factor), |
1031 | 0 | map.get(RightY).map_or(1., ScaleFactorTrait::scale_factor), |
1032 | 0 | ), |
1033 | 0 | TopXLeftY => ( |
1034 | 0 | map.get(TopX).map_or(1., ScaleFactorTrait::scale_factor), |
1035 | 0 | map.get(LeftY).map_or(1., ScaleFactorTrait::scale_factor), |
1036 | 0 | ), |
1037 | 0 | TopXRightY => ( |
1038 | 0 | map.get(TopX).map_or(1., ScaleFactorTrait::scale_factor), |
1039 | 0 | map.get(RightY).map_or(1., ScaleFactorTrait::scale_factor), |
1040 | 0 | ), |
1041 | | } |
1042 | 0 | } |
1043 | | |
1044 | | // XXX :-1: to intra-crate privacy rules |
1045 | | /// Private |
1046 | | trait ScaleFactorTrait { |
1047 | | /// Private |
1048 | | fn scale_factor(&self) -> f64; |
1049 | | } |
1050 | | |
1051 | | #[cfg(test)] |
1052 | | mod test { |
1053 | | #[test] |
1054 | | fn version() { |
1055 | | if let Ok(version) = super::version() { |
1056 | | assert!(version.major >= 4); |
1057 | | } else { |
1058 | | println!("Gnuplot not installed."); |
1059 | | } |
1060 | | } |
1061 | | |
1062 | | #[test] |
1063 | | fn test_parse_version_on_valid_string() { |
1064 | | let string = "gnuplot 5.0 patchlevel 7"; |
1065 | | let version = super::parse_version(&string).unwrap(); |
1066 | | assert_eq!(5, version.major); |
1067 | | assert_eq!(0, version.minor); |
1068 | | assert_eq!("7", &version.patch); |
1069 | | } |
1070 | | |
1071 | | #[test] |
1072 | | fn test_parse_gentoo_version() { |
1073 | | let string = "gnuplot 5.2 patchlevel 5a (Gentoo revision r0)"; |
1074 | | let version = super::parse_version(&string).unwrap(); |
1075 | | assert_eq!(5, version.major); |
1076 | | assert_eq!(2, version.minor); |
1077 | | assert_eq!("5a", &version.patch); |
1078 | | } |
1079 | | |
1080 | | #[test] |
1081 | | fn test_parse_version_returns_error_on_invalid_strings() { |
1082 | | let strings = [ |
1083 | | "", |
1084 | | "foobar", |
1085 | | "gnuplot 50 patchlevel 7", |
1086 | | "gnuplot 5.0 patchlevel", |
1087 | | "gnuplot foo.bar patchlevel 7", |
1088 | | ]; |
1089 | | for string in &strings { |
1090 | | assert!(super::parse_version(string).is_err()); |
1091 | | } |
1092 | | } |
1093 | | } |