1 //! Error bar plots
2 
3 use std::borrow::Cow;
4 use std::iter::IntoIterator;
5 
6 use crate::data::Matrix;
7 use crate::traits::{self, Data, Set};
8 use crate::{
9     Color, Display, ErrorBarDefault, Figure, Label, LineType, LineWidth, Plot, PointSize,
10     PointType, Script,
11 };
12 
13 /// Properties common to error bar plots
14 pub struct Properties {
15     color: Option<Color>,
16     label: Option<Cow<'static, str>>,
17     line_type: LineType,
18     linewidth: Option<f64>,
19     point_size: Option<f64>,
20     point_type: Option<PointType>,
21     style: Style,
22 }
23 
24 impl ErrorBarDefault<Style> for Properties {
default(style: Style) -> Properties25     fn default(style: Style) -> Properties {
26         Properties {
27             color: None,
28             label: None,
29             line_type: LineType::Solid,
30             linewidth: None,
31             point_type: None,
32             point_size: None,
33             style,
34         }
35     }
36 }
37 
38 impl Script for Properties {
39     // Allow clippy::format_push_string even with older versions of rust (<1.62) which
40     // don't have it defined.
41     #[allow(clippy::all)]
script(&self) -> String42     fn script(&self) -> String {
43         let mut script = format!("with {} ", self.style.display());
44 
45         script.push_str(&format!("lt {} ", self.line_type.display()));
46 
47         if let Some(lw) = self.linewidth {
48             script.push_str(&format!("lw {} ", lw))
49         }
50 
51         if let Some(color) = self.color {
52             script.push_str(&format!("lc rgb '{}' ", color.display()))
53         }
54 
55         if let Some(pt) = self.point_type {
56             script.push_str(&format!("pt {} ", pt.display()))
57         }
58 
59         if let Some(ps) = self.point_size {
60             script.push_str(&format!("ps {} ", ps))
61         }
62 
63         if let Some(ref label) = self.label {
64             script.push_str("title '");
65             script.push_str(label);
66             script.push('\'')
67         } else {
68             script.push_str("notitle")
69         }
70 
71         script
72     }
73 }
74 
75 impl Set<Color> for Properties {
76     /// Changes the color of the error bars
set(&mut self, color: Color) -> &mut Properties77     fn set(&mut self, color: Color) -> &mut Properties {
78         self.color = Some(color);
79         self
80     }
81 }
82 
83 impl Set<Label> for Properties {
84     /// Sets the legend label
set(&mut self, label: Label) -> &mut Properties85     fn set(&mut self, label: Label) -> &mut Properties {
86         self.label = Some(label.0);
87         self
88     }
89 }
90 
91 impl Set<LineType> for Properties {
92     /// Change the line type
93     ///
94     /// **Note** By default `Solid` lines are used
set(&mut self, lt: LineType) -> &mut Properties95     fn set(&mut self, lt: LineType) -> &mut Properties {
96         self.line_type = lt;
97         self
98     }
99 }
100 
101 impl Set<LineWidth> for Properties {
102     /// Changes the linewidth
103     ///
104     /// # Panics
105     ///
106     /// Panics if `lw` is a non-positive value
set(&mut self, lw: LineWidth) -> &mut Properties107     fn set(&mut self, lw: LineWidth) -> &mut Properties {
108         let lw = lw.0;
109 
110         assert!(lw > 0.);
111 
112         self.linewidth = Some(lw);
113         self
114     }
115 }
116 
117 impl Set<PointSize> for Properties {
118     /// Changes the size of the points
119     ///
120     /// # Panics
121     ///
122     /// Panics if `size` is a non-positive value
set(&mut self, ps: PointSize) -> &mut Properties123     fn set(&mut self, ps: PointSize) -> &mut Properties {
124         let ps = ps.0;
125 
126         assert!(ps > 0.);
127 
128         self.point_size = Some(ps);
129         self
130     }
131 }
132 
133 impl Set<PointType> for Properties {
134     /// Changes the point type
set(&mut self, pt: PointType) -> &mut Properties135     fn set(&mut self, pt: PointType) -> &mut Properties {
136         self.point_type = Some(pt);
137         self
138     }
139 }
140 
141 #[derive(Clone, Copy)]
142 enum Style {
143     XErrorBars,
144     XErrorLines,
145     YErrorBars,
146     YErrorLines,
147 }
148 
149 impl Display<&'static str> for Style {
display(&self) -> &'static str150     fn display(&self) -> &'static str {
151         match *self {
152             Style::XErrorBars => "xerrorbars",
153             Style::XErrorLines => "xerrorlines",
154             Style::YErrorBars => "yerrorbars",
155             Style::YErrorLines => "yerrorlines",
156         }
157     }
158 }
159 
160 /// Asymmetric error bar plots
161 pub enum ErrorBar<X, Y, L, H> {
162     /// Horizontal error bars
163     XErrorBars {
164         /// X coordinate of the data points
165         x: X,
166         /// Y coordinate of the data points
167         y: Y,
168         /// X coordinate of the left end of the error bar
169         x_low: L,
170         /// Y coordinate of the right end of the error bar
171         x_high: H,
172     },
173     /// Horizontal error bars, where each point is joined by a line
174     XErrorLines {
175         /// X coordinate of the data points
176         x: X,
177         /// Y coordinate of the data points
178         y: Y,
179         /// X coordinate of the left end of the error bar
180         x_low: L,
181         /// Y coordinate of the right end of the error bar
182         x_high: H,
183     },
184     /// Vertical error bars
185     YErrorBars {
186         /// X coordinate of the data points
187         x: X,
188         /// Y coordinate of the data points
189         y: Y,
190         /// Y coordinate of the bottom of the error bar
191         y_low: L,
192         /// Y coordinate of the top of the error bar
193         y_high: H,
194     },
195     /// Vertical error bars, where each point is joined by a line
196     YErrorLines {
197         /// X coordinate of the data points
198         x: X,
199         /// Y coordinate of the data points
200         y: Y,
201         /// Y coordinate of the bottom of the error bar
202         y_low: L,
203         /// Y coordinate of the top of the error bar
204         y_high: H,
205     },
206 }
207 
208 impl<X, Y, L, H> ErrorBar<X, Y, L, H> {
style(&self) -> Style209     fn style(&self) -> Style {
210         match *self {
211             ErrorBar::XErrorBars { .. } => Style::XErrorBars,
212             ErrorBar::XErrorLines { .. } => Style::XErrorLines,
213             ErrorBar::YErrorBars { .. } => Style::YErrorBars,
214             ErrorBar::YErrorLines { .. } => Style::YErrorLines,
215         }
216     }
217 }
218 
219 impl<X, Y, L, H> traits::Plot<ErrorBar<X, Y, L, H>> for Figure
220 where
221     H: IntoIterator,
222     H::Item: Data,
223     L: IntoIterator,
224     L::Item: Data,
225     X: IntoIterator,
226     X::Item: Data,
227     Y: IntoIterator,
228     Y::Item: Data,
229 {
230     type Properties = Properties;
231 
plot<F>(&mut self, e: ErrorBar<X, Y, L, H>, configure: F) -> &mut Figure where F: FnOnce(&mut Properties) -> &mut Properties,232     fn plot<F>(&mut self, e: ErrorBar<X, Y, L, H>, configure: F) -> &mut Figure
233     where
234         F: FnOnce(&mut Properties) -> &mut Properties,
235     {
236         let (x_factor, y_factor) = crate::scale_factor(&self.axes, crate::Axes::BottomXLeftY);
237 
238         let style = e.style();
239         let (x, y, length, height, e_factor) = match e {
240             ErrorBar::XErrorBars {
241                 x,
242                 y,
243                 x_low,
244                 x_high,
245             }
246             | ErrorBar::XErrorLines {
247                 x,
248                 y,
249                 x_low,
250                 x_high,
251             } => (x, y, x_low, x_high, x_factor),
252             ErrorBar::YErrorBars {
253                 x,
254                 y,
255                 y_low,
256                 y_high,
257             }
258             | ErrorBar::YErrorLines {
259                 x,
260                 y,
261                 y_low,
262                 y_high,
263             } => (x, y, y_low, y_high, y_factor),
264         };
265         let data = Matrix::new(
266             izip!(x, y, length, height),
267             (x_factor, y_factor, e_factor, e_factor),
268         );
269         self.plots.push(Plot::new(
270             data,
271             configure(&mut ErrorBarDefault::default(style)),
272         ));
273         self
274     }
275 }
276 
277 // TODO XY error bar
278 // pub struct XyErrorBar<X, Y, XL, XH, YL, YH> {
279 // x: X,
280 // y: Y,
281 // x_low: XL,
282 // x_high: XH,
283 // y_low: YL,
284 // y_high: YH,
285 // }
286 
287 // TODO Symmetric error bars
288 // pub enum SymmetricErrorBar {
289 // XSymmetricErrorBar { x: X, y: Y, x_delta: D },
290 // XSymmetricErrorLines { x: X, y: Y, x_delta: D },
291 // YSymmetricErrorBar { x: X, y: Y, y_delta: D },
292 // YSymmetricErrorLines { x: X, y: Y, y_delta: D },
293 // }
294