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 { |
25 | 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)] |
42 | 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 |
77 | 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 |
85 | 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 |
95 | 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 |
107 | 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 |
123 | 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 |
135 | 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 { |
150 | 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> { |
209 | 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 | |
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 | |