1use crate::artist::ErrorBarArtist;
21use crate::primitives::Color;
22
23impl ErrorBarArtist {
24 pub fn color(&mut self, color: Color) -> &mut Self {
35 self.color = color;
36 self
37 }
38
39 pub fn label(&mut self, label: &str) -> &mut Self {
51 self.label = Some(label.to_string());
52 self
53 }
54
55 pub fn cap_size(&mut self, size: f64) -> &mut Self {
67 self.cap_size = size.max(0.0);
68 self
69 }
70
71 pub fn line_width(&mut self, width: f64) -> &mut Self {
82 self.line_width = width.max(0.0);
83 self
84 }
85
86 pub fn yerr_symmetric(mut self, errs: &[f64]) -> Self {
98 self.yerr = Some(crate::artist::ErrorBarData::Symmetric(errs.to_vec()));
99 self
100 }
101
102 pub fn yerr_asymmetric(mut self, low: &[f64], high: &[f64]) -> Self {
114 self.yerr = Some(crate::artist::ErrorBarData::Asymmetric {
115 low: low.to_vec(),
116 high: high.to_vec(),
117 });
118 self
119 }
120
121 pub fn xerr_symmetric(mut self, errs: &[f64]) -> Self {
133 self.xerr = Some(crate::artist::ErrorBarData::Symmetric(errs.to_vec()));
134 self
135 }
136
137 pub fn xerr_asymmetric(mut self, low: &[f64], high: &[f64]) -> Self {
149 self.xerr = Some(crate::artist::ErrorBarData::Asymmetric {
150 low: low.to_vec(),
151 high: high.to_vec(),
152 });
153 self
154 }
155}
156
157#[cfg(test)]
162mod tests {
163 use super::*;
164 use crate::artist::ErrorBarData;
165 use crate::series::Series;
166
167 const TOL: f64 = 1e-12;
169
170 fn approx_eq(a: f64, b: f64) -> bool {
172 (a - b).abs() < TOL
173 }
174
175 fn sample_errorbar() -> ErrorBarArtist {
177 ErrorBarArtist {
178 x: Series::new(vec![1.0, 2.0, 3.0]),
179 y: Series::new(vec![10.0, 20.0, 30.0]),
180 xerr: None,
181 yerr: None,
182 color: Color::TAB_BLUE,
183 label: None,
184 cap_size: 4.0,
185 line_width: 1.0,
186 }
187 }
188
189 #[test]
190 fn builder_color() {
191 let mut a = sample_errorbar();
192 a.color(Color::TAB_RED);
193 assert_eq!(a.color, Color::TAB_RED);
194 }
195
196 #[test]
197 fn builder_label() {
198 let mut a = sample_errorbar();
199 assert!(a.label.is_none());
200 a.label("Measurements");
201 assert_eq!(a.label.as_deref(), Some("Measurements"));
202 }
203
204 #[test]
205 fn builder_label_overwrite() {
206 let mut a = sample_errorbar();
207 a.label("first");
208 a.label("second");
209 assert_eq!(a.label.as_deref(), Some("second"));
210 }
211
212 #[test]
213 fn builder_cap_size() {
214 let mut a = sample_errorbar();
215 a.cap_size(8.0);
216 assert!(approx_eq(a.cap_size, 8.0));
217 }
218
219 #[test]
220 fn builder_cap_size_clamps_negative() {
221 let mut a = sample_errorbar();
222 a.cap_size(-5.0);
223 assert!(approx_eq(a.cap_size, 0.0));
224 }
225
226 #[test]
227 fn builder_line_width() {
228 let mut a = sample_errorbar();
229 a.line_width(2.5);
230 assert!(approx_eq(a.line_width, 2.5));
231 }
232
233 #[test]
234 fn builder_line_width_clamps_negative() {
235 let mut a = sample_errorbar();
236 a.line_width(-1.0);
237 assert!(approx_eq(a.line_width, 0.0));
238 }
239
240 #[test]
241 fn builder_yerr_symmetric() {
242 let a = sample_errorbar().yerr_symmetric(&[0.5, 1.0, 1.5]);
243 match &a.yerr {
244 Some(ErrorBarData::Symmetric(v)) => {
245 assert_eq!(v.len(), 3);
246 assert!(approx_eq(v[0], 0.5));
247 assert!(approx_eq(v[1], 1.0));
248 assert!(approx_eq(v[2], 1.5));
249 }
250 _ => panic!("expected Symmetric yerr"),
251 }
252 }
253
254 #[test]
255 fn builder_yerr_asymmetric() {
256 let a = sample_errorbar().yerr_asymmetric(&[0.3, 0.5, 0.7], &[1.0, 1.2, 1.4]);
257 match &a.yerr {
258 Some(ErrorBarData::Asymmetric { low, high }) => {
259 assert_eq!(low.len(), 3);
260 assert_eq!(high.len(), 3);
261 assert!(approx_eq(low[0], 0.3));
262 assert!(approx_eq(high[2], 1.4));
263 }
264 _ => panic!("expected Asymmetric yerr"),
265 }
266 }
267
268 #[test]
269 fn builder_xerr_symmetric() {
270 let a = sample_errorbar().xerr_symmetric(&[0.1, 0.2, 0.3]);
271 match &a.xerr {
272 Some(ErrorBarData::Symmetric(v)) => {
273 assert_eq!(v.len(), 3);
274 assert!(approx_eq(v[0], 0.1));
275 }
276 _ => panic!("expected Symmetric xerr"),
277 }
278 }
279
280 #[test]
281 fn builder_xerr_asymmetric() {
282 let a = sample_errorbar().xerr_asymmetric(&[0.1, 0.2, 0.3], &[0.4, 0.5, 0.6]);
283 match &a.xerr {
284 Some(ErrorBarData::Asymmetric { low, high }) => {
285 assert_eq!(low.len(), 3);
286 assert_eq!(high.len(), 3);
287 assert!(approx_eq(low[1], 0.2));
288 assert!(approx_eq(high[1], 0.5));
289 }
290 _ => panic!("expected Asymmetric xerr"),
291 }
292 }
293
294 #[test]
295 fn builder_chaining() {
296 let mut a = sample_errorbar();
297 a.color(Color::TAB_GREEN)
298 .label("Test")
299 .cap_size(6.0)
300 .line_width(2.0);
301
302 assert_eq!(a.color, Color::TAB_GREEN);
303 assert_eq!(a.label.as_deref(), Some("Test"));
304 assert!(approx_eq(a.cap_size, 6.0));
305 assert!(approx_eq(a.line_width, 2.0));
306 }
307
308 #[test]
309 fn data_bounds_no_errors() {
310 let a = sample_errorbar();
311 let (xmin, xmax, ymin, ymax) = a.data_bounds();
312 assert!(approx_eq(xmin, 1.0));
313 assert!(approx_eq(xmax, 3.0));
314 assert!(approx_eq(ymin, 10.0));
315 assert!(approx_eq(ymax, 30.0));
316 }
317
318 #[test]
319 fn data_bounds_with_symmetric_yerr() {
320 let a = sample_errorbar().yerr_symmetric(&[2.0, 3.0, 5.0]);
321 let (xmin, xmax, ymin, ymax) = a.data_bounds();
322 assert!(approx_eq(xmin, 1.0));
323 assert!(approx_eq(xmax, 3.0));
324 assert!(approx_eq(ymin, 8.0));
325 assert!(approx_eq(ymax, 35.0));
326 }
327
328 #[test]
329 fn data_bounds_with_asymmetric_yerr() {
330 let a = sample_errorbar().yerr_asymmetric(&[1.0, 2.0, 3.0], &[5.0, 6.0, 7.0]);
331 let (_, _, ymin, ymax) = a.data_bounds();
332 assert!(approx_eq(ymin, 9.0));
333 assert!(approx_eq(ymax, 37.0));
334 }
335
336 #[test]
337 fn data_bounds_with_symmetric_xerr() {
338 let a = sample_errorbar().xerr_symmetric(&[0.5, 0.5, 0.5]);
339 let (xmin, xmax, _, _) = a.data_bounds();
340 assert!(approx_eq(xmin, 0.5));
341 assert!(approx_eq(xmax, 3.5));
342 }
343
344 #[test]
345 fn data_bounds_with_both_errors() {
346 let a = sample_errorbar()
347 .xerr_symmetric(&[0.5, 0.5, 0.5])
348 .yerr_symmetric(&[2.0, 3.0, 5.0]);
349 let (xmin, xmax, ymin, ymax) = a.data_bounds();
350 assert!(approx_eq(xmin, 0.5));
351 assert!(approx_eq(xmax, 3.5));
352 assert!(approx_eq(ymin, 8.0));
353 assert!(approx_eq(ymax, 35.0));
354 }
355
356 #[test]
357 fn data_bounds_empty_series() {
358 let a = ErrorBarArtist {
359 x: Series::new(vec![]),
360 y: Series::new(vec![]),
361 xerr: None,
362 yerr: None,
363 color: Color::BLACK,
364 label: None,
365 cap_size: 4.0,
366 line_width: 1.0,
367 };
368 let (xmin, xmax, ymin, ymax) = a.data_bounds();
369 assert!(approx_eq(xmin, 0.0));
370 assert!(approx_eq(xmax, 1.0));
371 assert!(approx_eq(ymin, 0.0));
372 assert!(approx_eq(ymax, 1.0));
373 }
374}