1use super::{
4 Plot, PlotDataLayout, PlotDataOffset, PlotDataStride, PlotError, PlotItemStyle,
5 plot_spec_with_style, validate_data_lengths, with_plot_str_or_empty,
6};
7use crate::{ErrorBarsFlags, ItemFlags, sys};
8
9pub struct ErrorBarsPlot<'a> {
11 label: &'a str,
12 x_data: &'a [f64],
13 y_data: &'a [f64],
14 err_data: &'a [f64],
15 style: PlotItemStyle,
16 flags: ErrorBarsFlags,
17 item_flags: ItemFlags,
18 layout: PlotDataLayout,
19}
20
21impl<'a> super::PlotItemStyled for ErrorBarsPlot<'a> {
22 fn style_mut(&mut self) -> &mut PlotItemStyle {
23 &mut self.style
24 }
25}
26
27impl<'a> ErrorBarsPlot<'a> {
28 pub fn new(label: &'a str, x_data: &'a [f64], y_data: &'a [f64], err_data: &'a [f64]) -> Self {
36 Self {
37 label,
38 x_data,
39 y_data,
40 err_data,
41 style: PlotItemStyle::default(),
42 flags: ErrorBarsFlags::NONE,
43 item_flags: ItemFlags::NONE,
44 layout: PlotDataLayout::DEFAULT,
45 }
46 }
47
48 pub fn with_flags(mut self, flags: ErrorBarsFlags) -> Self {
50 self.flags = flags;
51 self
52 }
53
54 pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
56 self.item_flags = flags;
57 self
58 }
59
60 pub unsafe fn with_data_layout(mut self, layout: PlotDataLayout) -> Self {
67 self.layout = layout;
68 self
69 }
70
71 pub fn with_offset(mut self, offset: PlotDataOffset) -> Self {
73 self.layout = self.layout.with_offset(offset);
74 self
75 }
76
77 pub unsafe fn with_stride(mut self, stride: PlotDataStride) -> Self {
84 self.layout = self.layout.with_stride(stride);
85 self
86 }
87
88 pub fn horizontal(mut self) -> Self {
90 self.flags |= ErrorBarsFlags::HORIZONTAL;
91 self
92 }
93
94 pub fn validate(&self) -> Result<(), PlotError> {
96 validate_data_lengths(self.x_data, self.y_data)?;
97 validate_data_lengths(self.x_data, self.err_data)?;
98
99 if self.err_data.iter().any(|&err| err < 0.0) {
101 return Err(PlotError::InvalidData(
102 "Error values cannot be negative".to_string(),
103 ));
104 }
105
106 Ok(())
107 }
108}
109
110impl<'a> Plot for ErrorBarsPlot<'a> {
111 fn plot(&self, plot_ui: &crate::PlotUi<'_>) {
112 if self.validate().is_err() {
113 return;
114 }
115 let Ok(count) = i32::try_from(self.x_data.len()) else {
116 return;
117 };
118 plot_ui.with_bound_context(|| {
119 with_plot_str_or_empty(self.label, |label_ptr| unsafe {
120 let spec = plot_spec_with_style(
121 self.style,
122 self.flags.bits() | self.item_flags.bits(),
123 self.layout,
124 );
125 sys::ImPlot_PlotErrorBars_doublePtrdoublePtrdoublePtrInt(
126 label_ptr,
127 self.x_data.as_ptr(),
128 self.y_data.as_ptr(),
129 self.err_data.as_ptr(),
130 count,
131 spec,
132 );
133 })
134 })
135 }
136
137 fn label(&self) -> &str {
138 self.label
139 }
140}
141
142pub struct AsymmetricErrorBarsPlot<'a> {
144 label: &'a str,
145 x_data: &'a [f64],
146 y_data: &'a [f64],
147 err_neg: &'a [f64],
148 err_pos: &'a [f64],
149 style: PlotItemStyle,
150 flags: ErrorBarsFlags,
151 item_flags: ItemFlags,
152 layout: PlotDataLayout,
153}
154
155impl<'a> super::PlotItemStyled for AsymmetricErrorBarsPlot<'a> {
156 fn style_mut(&mut self) -> &mut PlotItemStyle {
157 &mut self.style
158 }
159}
160
161impl<'a> AsymmetricErrorBarsPlot<'a> {
162 pub fn new(
171 label: &'a str,
172 x_data: &'a [f64],
173 y_data: &'a [f64],
174 err_neg: &'a [f64],
175 err_pos: &'a [f64],
176 ) -> Self {
177 Self {
178 label,
179 x_data,
180 y_data,
181 err_neg,
182 err_pos,
183 style: PlotItemStyle::default(),
184 flags: ErrorBarsFlags::NONE,
185 item_flags: ItemFlags::NONE,
186 layout: PlotDataLayout::DEFAULT,
187 }
188 }
189
190 pub fn with_flags(mut self, flags: ErrorBarsFlags) -> Self {
192 self.flags = flags;
193 self
194 }
195
196 pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
198 self.item_flags = flags;
199 self
200 }
201
202 pub unsafe fn with_data_layout(mut self, layout: PlotDataLayout) -> Self {
209 self.layout = layout;
210 self
211 }
212
213 pub fn with_offset(mut self, offset: PlotDataOffset) -> Self {
215 self.layout = self.layout.with_offset(offset);
216 self
217 }
218
219 pub unsafe fn with_stride(mut self, stride: PlotDataStride) -> Self {
226 self.layout = self.layout.with_stride(stride);
227 self
228 }
229
230 pub fn horizontal(mut self) -> Self {
232 self.flags |= ErrorBarsFlags::HORIZONTAL;
233 self
234 }
235
236 pub fn validate(&self) -> Result<(), PlotError> {
238 validate_data_lengths(self.x_data, self.y_data)?;
239 validate_data_lengths(self.x_data, self.err_neg)?;
240 validate_data_lengths(self.x_data, self.err_pos)?;
241
242 if self.err_neg.iter().any(|&err| err < 0.0) || self.err_pos.iter().any(|&err| err < 0.0) {
244 return Err(PlotError::InvalidData(
245 "Error values cannot be negative".to_string(),
246 ));
247 }
248
249 Ok(())
250 }
251}
252
253impl<'a> Plot for AsymmetricErrorBarsPlot<'a> {
254 fn plot(&self, plot_ui: &crate::PlotUi<'_>) {
255 if self.validate().is_err() {
256 return;
257 }
258 let Ok(count) = i32::try_from(self.x_data.len()) else {
259 return;
260 };
261 plot_ui.with_bound_context(|| {
262 with_plot_str_or_empty(self.label, |label_ptr| unsafe {
263 let spec = plot_spec_with_style(
264 self.style,
265 self.flags.bits() | self.item_flags.bits(),
266 self.layout,
267 );
268 sys::ImPlot_PlotErrorBars_doublePtrdoublePtrdoublePtrdoublePtr(
269 label_ptr,
270 self.x_data.as_ptr(),
271 self.y_data.as_ptr(),
272 self.err_neg.as_ptr(),
273 self.err_pos.as_ptr(),
274 count,
275 spec,
276 );
277 })
278 })
279 }
280
281 fn label(&self) -> &str {
282 self.label
283 }
284}
285
286pub struct SimpleErrorBarsPlot<'a> {
288 label: &'a str,
289 values: &'a [f64],
290 errors: &'a [f64],
291 style: PlotItemStyle,
292 flags: ErrorBarsFlags,
293 item_flags: ItemFlags,
294 x_scale: f64,
295 x_start: f64,
296}
297
298impl<'a> super::PlotItemStyled for SimpleErrorBarsPlot<'a> {
299 fn style_mut(&mut self) -> &mut PlotItemStyle {
300 &mut self.style
301 }
302}
303
304impl<'a> SimpleErrorBarsPlot<'a> {
305 pub fn new(label: &'a str, values: &'a [f64], errors: &'a [f64]) -> Self {
307 Self {
308 label,
309 values,
310 errors,
311 style: PlotItemStyle::default(),
312 flags: ErrorBarsFlags::NONE,
313 item_flags: ItemFlags::NONE,
314 x_scale: 1.0,
315 x_start: 0.0,
316 }
317 }
318
319 pub fn with_flags(mut self, flags: ErrorBarsFlags) -> Self {
321 self.flags = flags;
322 self
323 }
324
325 pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
327 self.item_flags = flags;
328 self
329 }
330
331 pub fn horizontal(mut self) -> Self {
333 self.flags |= ErrorBarsFlags::HORIZONTAL;
334 self
335 }
336
337 pub fn with_x_scale(mut self, scale: f64) -> Self {
339 self.x_scale = scale;
340 self
341 }
342
343 pub fn with_x_start(mut self, start: f64) -> Self {
345 self.x_start = start;
346 self
347 }
348
349 pub fn validate(&self) -> Result<(), PlotError> {
351 validate_data_lengths(self.values, self.errors)?;
352
353 if self.errors.iter().any(|&err| err < 0.0) {
354 return Err(PlotError::InvalidData(
355 "Error values cannot be negative".to_string(),
356 ));
357 }
358
359 Ok(())
360 }
361}
362
363impl<'a> Plot for SimpleErrorBarsPlot<'a> {
364 fn plot(&self, plot_ui: &crate::PlotUi<'_>) {
365 if self.validate().is_err() {
366 return;
367 }
368 let Ok(count) = i32::try_from(self.values.len()) else {
369 return;
370 };
371
372 let x_data: Vec<f64> = (0..self.values.len())
374 .map(|i| self.x_start + i as f64 * self.x_scale)
375 .collect();
376
377 plot_ui.with_bound_context(|| {
378 with_plot_str_or_empty(self.label, |label_ptr| unsafe {
379 let spec = plot_spec_with_style(
380 self.style,
381 self.flags.bits() | self.item_flags.bits(),
382 PlotDataLayout::DEFAULT,
383 );
384 sys::ImPlot_PlotErrorBars_doublePtrdoublePtrdoublePtrInt(
385 label_ptr,
386 x_data.as_ptr(),
387 self.values.as_ptr(),
388 self.errors.as_ptr(),
389 count,
390 spec,
391 );
392 })
393 })
394 }
395
396 fn label(&self) -> &str {
397 self.label
398 }
399}
400
401impl<'ui> crate::PlotUi<'ui> {
403 pub fn error_bars_plot(
405 &self,
406 label: &str,
407 x_data: &[f64],
408 y_data: &[f64],
409 err_data: &[f64],
410 ) -> Result<(), PlotError> {
411 let plot = ErrorBarsPlot::new(label, x_data, y_data, err_data);
412 plot.validate()?;
413 plot.plot(self);
414 Ok(())
415 }
416
417 pub fn asymmetric_error_bars_plot(
419 &self,
420 label: &str,
421 x_data: &[f64],
422 y_data: &[f64],
423 err_neg: &[f64],
424 err_pos: &[f64],
425 ) -> Result<(), PlotError> {
426 let plot = AsymmetricErrorBarsPlot::new(label, x_data, y_data, err_neg, err_pos);
427 plot.validate()?;
428 plot.plot(self);
429 Ok(())
430 }
431
432 pub fn simple_error_bars_plot(
434 &self,
435 label: &str,
436 values: &[f64],
437 errors: &[f64],
438 ) -> Result<(), PlotError> {
439 let plot = SimpleErrorBarsPlot::new(label, values, errors);
440 plot.validate()?;
441 plot.plot(self);
442 Ok(())
443 }
444}
445
446#[cfg(test)]
447mod tests {
448 use super::*;
449
450 #[test]
451 fn test_simple_error_bars_plot_flags() {
452 let values = [1.0, 2.0, 3.0, 4.0];
453 let errors = [0.1, 0.2, 0.3, 0.4];
454 let plot = SimpleErrorBarsPlot::new("test", &values, &errors)
455 .horizontal()
456 .with_item_flags(ItemFlags::NO_LEGEND);
457 assert_eq!(plot.label(), "test");
458 assert_eq!(plot.flags.bits(), ErrorBarsFlags::HORIZONTAL.bits());
459 assert_eq!(plot.item_flags, ItemFlags::NO_LEGEND);
460 }
461}