Deprecated: The each() function is deprecated. This message will be suppressed on further calls in /home/zhenxiangba/zhenxiangba.com/public_html/phproxy-improved-master/index.php on line 456
lib.rs - source
[go: Go Back, main page]

criterion_plot/
lib.rs

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//! ![Plot](curve.svg)
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//! ![Plot](error_bar.svg)
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//! ![Plot](candlesticks.svg)
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//! ![Plot](multiaxis.svg)
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//! ![Plot](filled_curve.svg)
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(clippy::doc_markdown, 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#![allow(clippy::new_without_default)]
370#![allow(clippy::many_single_char_names)]
371
372use std::borrow::Cow;
373use std::fmt;
374use std::fs::File;
375use std::io;
376use std::num::ParseIntError;
377use std::path::Path;
378use std::process::{Child, Command};
379use std::str;
380
381use crate::data::Matrix;
382use crate::traits::{Configure, Set};
383
384mod data;
385mod display;
386mod map;
387
388pub mod axis;
389pub mod candlestick;
390pub mod curve;
391pub mod errorbar;
392pub mod filledcurve;
393pub mod grid;
394pub mod key;
395pub mod prelude;
396pub mod proxy;
397pub mod traits;
398
399/// Plot container
400#[derive(Clone)]
401pub struct Figure {
402    alpha: Option<f64>,
403    axes: map::axis::Map<axis::Properties>,
404    box_width: Option<f64>,
405    font: Option<Cow<'static, str>>,
406    font_size: Option<f64>,
407    key: Option<key::Properties>,
408    output: Cow<'static, Path>,
409    plots: Vec<Plot>,
410    size: Option<(usize, usize)>,
411    terminal: Terminal,
412    tics: map::axis::Map<String>,
413    title: Option<Cow<'static, str>>,
414}
415
416impl Figure {
417    /// Creates an empty figure
418    pub fn new() -> Figure {
419        Figure {
420            alpha: None,
421            axes: map::axis::Map::new(),
422            box_width: None,
423            font: None,
424            font_size: None,
425            key: None,
426            output: Cow::Borrowed(Path::new("output.plot")),
427            plots: Vec::new(),
428            size: None,
429            terminal: Terminal::Svg,
430            tics: map::axis::Map::new(),
431            title: None,
432        }
433    }
434
435    fn script(&self) -> Vec<u8> {
436        let mut s = String::new();
437
438        s.push_str(&format!(
439            "set output '{}'\n",
440            self.output.display().to_string().replace('\'', "''")
441        ));
442
443        if let Some(width) = self.box_width {
444            s.push_str(&format!("set boxwidth {}\n", width))
445        }
446
447        if let Some(ref title) = self.title {
448            s.push_str(&format!("set title '{}'\n", title))
449        }
450
451        for axis in self.axes.iter() {
452            s.push_str(&axis.script());
453        }
454
455        for (_, script) in self.tics.iter() {
456            s.push_str(script);
457        }
458
459        if let Some(ref key) = self.key {
460            s.push_str(&key.script())
461        }
462
463        if let Some(alpha) = self.alpha {
464            s.push_str(&format!("set style fill transparent solid {}\n", alpha))
465        }
466
467        s.push_str(&format!("set terminal {} dashed", self.terminal.display()));
468
469        if let Some((width, height)) = self.size {
470            s.push_str(&format!(" size {}, {}", width, height))
471        }
472
473        if let Some(ref name) = self.font {
474            if let Some(size) = self.font_size {
475                s.push_str(&format!(" font '{},{}'", name, size))
476            } else {
477                s.push_str(&format!(" font '{}'", name))
478            }
479        }
480
481        // TODO This removes the crossbars from the ends of error bars, but should be configurable
482        s.push_str("\nunset bars\n");
483
484        let mut is_first_plot = true;
485        for plot in &self.plots {
486            let data = plot.data();
487
488            if data.bytes().is_empty() {
489                continue;
490            }
491
492            if is_first_plot {
493                s.push_str("plot ");
494                is_first_plot = false;
495            } else {
496                s.push_str(", ");
497            }
498
499            s.push_str(&format!(
500                "'-' binary endian=little record={} format='%float64' using ",
501                data.nrows()
502            ));
503
504            let mut is_first_col = true;
505            for col in 0..data.ncols() {
506                if is_first_col {
507                    is_first_col = false;
508                } else {
509                    s.push(':');
510                }
511                s.push_str(&(col + 1).to_string());
512            }
513            s.push(' ');
514
515            s.push_str(plot.script());
516        }
517
518        let mut buffer = s.into_bytes();
519        let mut is_first = true;
520        for plot in &self.plots {
521            if is_first {
522                is_first = false;
523                buffer.push(b'\n');
524            }
525            buffer.extend_from_slice(plot.data().bytes());
526        }
527
528        buffer
529    }
530
531    /// Spawns a drawing child process
532    ///
533    /// NOTE: stderr, stdin, and stdout are piped
534    pub fn draw(&mut self) -> io::Result<Child> {
535        use std::process::Stdio;
536
537        let mut gnuplot = Command::new("gnuplot")
538            .stderr(Stdio::piped())
539            .stdin(Stdio::piped())
540            .stdout(Stdio::piped())
541            .spawn()?;
542        self.dump(gnuplot.stdin.as_mut().unwrap())?;
543        Ok(gnuplot)
544    }
545
546    /// Dumps the script required to produce the figure into `sink`
547    pub fn dump<W>(&mut self, sink: &mut W) -> io::Result<&mut Figure>
548    where
549        W: io::Write,
550    {
551        sink.write_all(&self.script())?;
552        Ok(self)
553    }
554
555    /// Saves the script required to produce the figure to `path`
556    pub fn save(&self, path: &Path) -> io::Result<&Figure> {
557        use std::io::Write;
558
559        File::create(path)?.write_all(&self.script())?;
560        Ok(self)
561    }
562}
563
564impl Configure<Axis> for Figure {
565    type Properties = axis::Properties;
566
567    /// Configures an axis
568    fn configure<F>(&mut self, axis: Axis, configure: F) -> &mut Figure
569    where
570        F: FnOnce(&mut axis::Properties) -> &mut axis::Properties,
571    {
572        if self.axes.contains_key(axis) {
573            configure(self.axes.get_mut(axis).unwrap());
574        } else {
575            let mut properties = Default::default();
576            configure(&mut properties);
577            self.axes.insert(axis, properties);
578        }
579        self
580    }
581}
582
583impl Configure<Key> for Figure {
584    type Properties = key::Properties;
585
586    /// Configures the key (legend)
587    fn configure<F>(&mut self, _: Key, configure: F) -> &mut Figure
588    where
589        F: FnOnce(&mut key::Properties) -> &mut key::Properties,
590    {
591        if self.key.is_some() {
592            configure(self.key.as_mut().unwrap());
593        } else {
594            let mut key = Default::default();
595            configure(&mut key);
596            self.key = Some(key);
597        }
598        self
599    }
600}
601
602impl Set<BoxWidth> for Figure {
603    /// Changes the box width of all the box related plots (bars, candlesticks, etc)
604    ///
605    /// **Note** The default value is 0
606    ///
607    /// # Panics
608    ///
609    /// Panics if `width` is a negative value
610    fn set(&mut self, width: BoxWidth) -> &mut Figure {
611        let width = width.0;
612
613        assert!(width >= 0.);
614
615        self.box_width = Some(width);
616        self
617    }
618}
619
620impl Set<Font> for Figure {
621    /// Changes the font
622    fn set(&mut self, font: Font) -> &mut Figure {
623        self.font = Some(font.0);
624        self
625    }
626}
627
628impl Set<FontSize> for Figure {
629    /// Changes the size of the font
630    ///
631    /// # Panics
632    ///
633    /// Panics if `size` is a non-positive value
634    fn set(&mut self, size: FontSize) -> &mut Figure {
635        let size = size.0;
636
637        assert!(size >= 0.);
638
639        self.font_size = Some(size);
640        self
641    }
642}
643
644impl Set<Output> for Figure {
645    /// Changes the output file
646    ///
647    /// **Note** The default output file is `output.plot`
648    fn set(&mut self, output: Output) -> &mut Figure {
649        self.output = output.0;
650        self
651    }
652}
653
654impl Set<Size> for Figure {
655    /// Changes the figure size
656    fn set(&mut self, size: Size) -> &mut Figure {
657        self.size = Some((size.0, size.1));
658        self
659    }
660}
661
662impl Set<Terminal> for Figure {
663    /// Changes the output terminal
664    ///
665    /// **Note** By default, the terminal is set to `Svg`
666    fn set(&mut self, terminal: Terminal) -> &mut Figure {
667        self.terminal = terminal;
668        self
669    }
670}
671
672impl Set<Title> for Figure {
673    /// Sets the title
674    fn set(&mut self, title: Title) -> &mut Figure {
675        self.title = Some(title.0);
676        self
677    }
678}
679
680impl Default for Figure {
681    fn default() -> Self {
682        Self::new()
683    }
684}
685
686/// Box width for box-related plots: bars, candlesticks, etc
687#[derive(Clone, Copy)]
688pub struct BoxWidth(pub f64);
689
690/// A font name
691pub struct Font(Cow<'static, str>);
692
693/// The size of a font
694#[derive(Clone, Copy)]
695pub struct FontSize(pub f64);
696
697/// The key or legend
698#[derive(Clone, Copy)]
699pub struct Key;
700
701/// Plot label
702pub struct Label(Cow<'static, str>);
703
704/// Width of the lines
705#[derive(Clone, Copy)]
706pub struct LineWidth(pub f64);
707
708/// Fill color opacity
709#[derive(Clone, Copy)]
710pub struct Opacity(pub f64);
711
712/// Output file path
713pub struct Output(Cow<'static, Path>);
714
715/// Size of the points
716#[derive(Clone, Copy)]
717pub struct PointSize(pub f64);
718
719/// Axis range
720#[derive(Clone, Copy)]
721pub enum Range {
722    /// Autoscale the axis
723    Auto,
724    /// Set the limits of the axis
725    Limits(f64, f64),
726}
727
728/// Figure size
729#[derive(Clone, Copy)]
730pub struct Size(pub usize, pub usize);
731
732/// Labels attached to the tics of an axis
733pub struct TicLabels<P, L> {
734    /// Labels to attach to the tics
735    pub labels: L,
736    /// Position of the tics on the axis
737    pub positions: P,
738}
739
740/// Figure title
741pub struct Title(Cow<'static, str>);
742
743/// A pair of axes that define a coordinate system
744#[allow(missing_docs)]
745#[derive(Clone, Copy)]
746pub enum Axes {
747    BottomXLeftY,
748    BottomXRightY,
749    TopXLeftY,
750    TopXRightY,
751}
752
753/// A coordinate axis
754#[derive(Clone, Copy)]
755pub enum Axis {
756    /// X axis on the bottom side of the figure
757    BottomX,
758    /// Y axis on the left side of the figure
759    LeftY,
760    /// Y axis on the right side of the figure
761    RightY,
762    /// X axis on the top side of the figure
763    TopX,
764}
765
766impl Axis {
767    fn next(self) -> Option<Axis> {
768        use crate::Axis::*;
769
770        match self {
771            BottomX => Some(LeftY),
772            LeftY => Some(RightY),
773            RightY => Some(TopX),
774            TopX => None,
775        }
776    }
777}
778
779/// Color
780#[allow(missing_docs)]
781#[derive(Clone, Copy)]
782pub enum Color {
783    Black,
784    Blue,
785    Cyan,
786    DarkViolet,
787    ForestGreen,
788    Gold,
789    Gray,
790    Green,
791    Magenta,
792    Red,
793    /// Custom RGB color
794    Rgb(u8, u8, u8),
795    White,
796    Yellow,
797}
798
799/// Grid line
800#[derive(Clone, Copy)]
801pub enum Grid {
802    /// Major gridlines
803    Major,
804    /// Minor gridlines
805    Minor,
806}
807
808impl Grid {
809    fn next(self) -> Option<Grid> {
810        use crate::Grid::*;
811
812        match self {
813            Major => Some(Minor),
814            Minor => None,
815        }
816    }
817}
818
819/// Line type
820#[allow(missing_docs)]
821#[derive(Clone, Copy)]
822pub enum LineType {
823    Dash,
824    Dot,
825    DotDash,
826    DotDotDash,
827    /// Line made of minimally sized dots
828    SmallDot,
829    Solid,
830}
831
832/// Point type
833#[allow(missing_docs)]
834#[derive(Clone, Copy)]
835pub enum PointType {
836    Circle,
837    FilledCircle,
838    FilledSquare,
839    FilledTriangle,
840    Plus,
841    Square,
842    Star,
843    Triangle,
844    X,
845}
846
847/// Axis scale
848#[allow(missing_docs)]
849#[derive(Clone, Copy)]
850pub enum Scale {
851    Linear,
852    Logarithmic,
853}
854
855/// Axis scale factor
856#[allow(missing_docs)]
857#[derive(Clone, Copy)]
858pub struct ScaleFactor(pub f64);
859
860/// Output terminal
861#[allow(missing_docs)]
862#[derive(Clone, Copy)]
863pub enum Terminal {
864    Svg,
865}
866
867/// Not public version of `std::default::Default`, used to not leak default constructors into the
868/// public API
869trait Default {
870    /// Creates `Properties` with default configuration
871    fn default() -> Self;
872}
873
874/// Enums that can produce gnuplot code
875trait Display<S> {
876    /// Translates the enum in gnuplot code
877    fn display(&self) -> S;
878}
879
880/// Curve variant of Default
881trait CurveDefault<S> {
882    /// Creates `curve::Properties` with default configuration
883    fn default(s: S) -> Self;
884}
885
886/// Error bar variant of Default
887trait ErrorBarDefault<S> {
888    /// Creates `errorbar::Properties` with default configuration
889    fn default(s: S) -> Self;
890}
891
892/// Structs that can produce gnuplot code
893trait Script {
894    /// Translates some configuration struct into gnuplot code
895    fn script(&self) -> String;
896}
897
898#[derive(Clone)]
899struct Plot {
900    data: Matrix,
901    script: String,
902}
903
904impl Plot {
905    fn new<S>(data: Matrix, script: &S) -> Plot
906    where
907        S: Script,
908    {
909        Plot {
910            data,
911            script: script.script(),
912        }
913    }
914
915    fn data(&self) -> &Matrix {
916        &self.data
917    }
918
919    fn script(&self) -> &str {
920        &self.script
921    }
922}
923
924/// Possible errors when parsing gnuplot's version string
925#[derive(Debug)]
926pub enum VersionError {
927    /// The `gnuplot` command couldn't be executed
928    Exec(io::Error),
929    /// The `gnuplot` command returned an error message
930    Error(String),
931    /// The `gnuplot` command returned invalid utf-8
932    OutputError,
933    /// The `gnuplot` command returned an unparsable string
934    ParseError(String),
935}
936impl fmt::Display for VersionError {
937    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
938        match self {
939            VersionError::Exec(err) => write!(f, "`gnuplot --version` failed: {}", err),
940            VersionError::Error(msg) => {
941                write!(f, "`gnuplot --version` failed with error message:\n{}", msg)
942            }
943            VersionError::OutputError => write!(f, "`gnuplot --version` returned invalid utf-8"),
944            VersionError::ParseError(msg) => write!(
945                f,
946                "`gnuplot --version` returned an unparsable version string: {}",
947                msg
948            ),
949        }
950    }
951}
952impl ::std::error::Error for VersionError {
953    fn description(&self) -> &str {
954        match self {
955            VersionError::Exec(_) => "Execution Error",
956            VersionError::Error(_) => "Other Error",
957            VersionError::OutputError => "Output Error",
958            VersionError::ParseError(_) => "Parse Error",
959        }
960    }
961
962    fn cause(&self) -> Option<&dyn ::std::error::Error> {
963        match self {
964            VersionError::Exec(err) => Some(err),
965            _ => None,
966        }
967    }
968}
969
970/// Structure representing a gnuplot version number.
971pub struct Version {
972    /// The major version number
973    pub major: usize,
974    /// The minor version number
975    pub minor: usize,
976    /// The patch level
977    pub patch: String,
978}
979
980/// Returns `gnuplot` version
981pub fn version() -> Result<Version, VersionError> {
982    let command_output = Command::new("gnuplot")
983        .arg("--version")
984        .output()
985        .map_err(VersionError::Exec)?;
986    if !command_output.status.success() {
987        let error =
988            String::from_utf8(command_output.stderr).map_err(|_| VersionError::OutputError)?;
989        return Err(VersionError::Error(error));
990    }
991
992    parse_version_utf8(&command_output.stdout).or_else(|utf8_err| {
993        // gnuplot can emit UTF-16 on some systems/configurations (e.g. some Windows machines).
994        // If we failed to parse as UTF-8, try again as UTF-16 to account for this.
995        // If UTF-16 parsing also fails, return the original error we got for UTF-8 to avoid confusing matters more.
996        parse_version_utf16(&command_output.stdout).map_err(|_| utf8_err)
997    })
998}
999
1000fn parse_version_utf8(output_bytes: &[u8]) -> Result<Version, VersionError> {
1001    let output = str::from_utf8(output_bytes).map_err(|_| VersionError::OutputError)?;
1002    parse_version(output).map_err(|_| VersionError::ParseError(output.to_owned()))
1003}
1004
1005fn parse_version_utf16(output_bytes: &[u8]) -> Result<Version, VersionError> {
1006    if output_bytes.len() % 2 != 0 {
1007        // Not an even number of bytes, so cannot be UTF-16.
1008        return Err(VersionError::OutputError);
1009    }
1010
1011    let output_as_u16: Vec<u16> = output_bytes
1012        .chunks_exact(2)
1013        .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
1014        .collect();
1015
1016    let output = String::from_utf16(&output_as_u16).map_err(|_| VersionError::OutputError)?;
1017    parse_version(&output).map_err(|_| VersionError::ParseError(output.to_owned()))
1018}
1019
1020fn parse_version(version_str: &str) -> Result<Version, Option<ParseIntError>> {
1021    let mut words = version_str.split_whitespace().skip(1);
1022    let mut version = words.next().ok_or(None)?.split('.');
1023    let major = version.next().ok_or(None)?.parse()?;
1024    let minor = version.next().ok_or(None)?.parse()?;
1025    let patchlevel = words.nth(1).ok_or(None)?.to_owned();
1026
1027    Ok(Version {
1028        major,
1029        minor,
1030        patch: patchlevel,
1031    })
1032}
1033
1034fn scale_factor(map: &map::axis::Map<axis::Properties>, axes: Axes) -> (f64, f64) {
1035    use crate::Axes::*;
1036    use crate::Axis::*;
1037
1038    match axes {
1039        BottomXLeftY => (
1040            map.get(BottomX).map_or(1., ScaleFactorTrait::scale_factor),
1041            map.get(LeftY).map_or(1., ScaleFactorTrait::scale_factor),
1042        ),
1043        BottomXRightY => (
1044            map.get(BottomX).map_or(1., ScaleFactorTrait::scale_factor),
1045            map.get(RightY).map_or(1., ScaleFactorTrait::scale_factor),
1046        ),
1047        TopXLeftY => (
1048            map.get(TopX).map_or(1., ScaleFactorTrait::scale_factor),
1049            map.get(LeftY).map_or(1., ScaleFactorTrait::scale_factor),
1050        ),
1051        TopXRightY => (
1052            map.get(TopX).map_or(1., ScaleFactorTrait::scale_factor),
1053            map.get(RightY).map_or(1., ScaleFactorTrait::scale_factor),
1054        ),
1055    }
1056}
1057
1058// XXX :-1: to intra-crate privacy rules
1059/// Private
1060trait ScaleFactorTrait {
1061    /// Private
1062    fn scale_factor(&self) -> f64;
1063}
1064
1065#[cfg(test)]
1066mod test {
1067    #[test]
1068    fn version() {
1069        if let Ok(version) = super::version() {
1070            assert!(version.major >= 4);
1071        } else {
1072            println!("Gnuplot not installed.");
1073        }
1074    }
1075
1076    #[test]
1077    fn test_parse_version_on_valid_string() {
1078        let string = "gnuplot 5.0 patchlevel 7";
1079        let version = super::parse_version(string).unwrap();
1080        assert_eq!(5, version.major);
1081        assert_eq!(0, version.minor);
1082        assert_eq!("7", &version.patch);
1083    }
1084
1085    #[test]
1086    fn test_parse_gentoo_version() {
1087        let string = "gnuplot 5.2 patchlevel 5a (Gentoo revision r0)";
1088        let version = super::parse_version(string).unwrap();
1089        assert_eq!(5, version.major);
1090        assert_eq!(2, version.minor);
1091        assert_eq!("5a", &version.patch);
1092    }
1093
1094    #[test]
1095    fn test_parse_version_returns_error_on_invalid_strings() {
1096        let strings = [
1097            "",
1098            "foobar",
1099            "gnuplot 50 patchlevel 7",
1100            "gnuplot 5.0 patchlevel",
1101            "gnuplot foo.bar patchlevel 7",
1102        ];
1103        for string in &strings {
1104            assert!(super::parse_version(string).is_err());
1105        }
1106    }
1107}