Skip to main content

dear_implot/plots/
error_bars.rs

1//! Error bars plot implementation
2
3use 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
9/// Builder for error bars plots
10pub 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    /// Create a new error bars plot with symmetric errors
29    ///
30    /// # Arguments
31    /// * `label` - The label for the error bars
32    /// * `x_data` - X coordinates of the data points
33    /// * `y_data` - Y coordinates of the data points
34    /// * `err_data` - Error values (symmetric, ±err)
35    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    /// Set error bar flags for customization
49    pub fn with_flags(mut self, flags: ErrorBarsFlags) -> Self {
50        self.flags = flags;
51        self
52    }
53
54    /// Set common item flags for this plot item (applies to all plot types)
55    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
56        self.item_flags = flags;
57        self
58    }
59
60    /// Set the data layout used to read values.
61    ///
62    /// # Safety
63    ///
64    /// Every sample address computed from `layout` must refer to an initialized, properly aligned
65    /// `f64` within every data allocation retained by this builder.
66    pub unsafe fn with_data_layout(mut self, layout: PlotDataLayout) -> Self {
67        self.layout = layout;
68        self
69    }
70
71    /// Set the sample-index offset used to read values.
72    pub fn with_offset(mut self, offset: PlotDataOffset) -> Self {
73        self.layout = self.layout.with_offset(offset);
74        self
75    }
76
77    /// Set the byte stride used to read values.
78    ///
79    /// # Safety
80    ///
81    /// Every strided sample read must remain initialized, aligned, and within every data allocation
82    /// retained by this builder.
83    pub unsafe fn with_stride(mut self, stride: PlotDataStride) -> Self {
84        self.layout = self.layout.with_stride(stride);
85        self
86    }
87
88    /// Make error bars horizontal instead of vertical
89    pub fn horizontal(mut self) -> Self {
90        self.flags |= ErrorBarsFlags::HORIZONTAL;
91        self
92    }
93
94    /// Validate the plot data
95    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        // Check for negative error values
100        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
142/// Builder for asymmetric error bars plots
143pub 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    /// Create a new asymmetric error bars plot
163    ///
164    /// # Arguments
165    /// * `label` - The label for the error bars
166    /// * `x_data` - X coordinates of the data points
167    /// * `y_data` - Y coordinates of the data points
168    /// * `err_neg` - Negative error values (downward/leftward)
169    /// * `err_pos` - Positive error values (upward/rightward)
170    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    /// Set error bar flags for customization
191    pub fn with_flags(mut self, flags: ErrorBarsFlags) -> Self {
192        self.flags = flags;
193        self
194    }
195
196    /// Set common item flags for this plot item (applies to all plot types)
197    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
198        self.item_flags = flags;
199        self
200    }
201
202    /// Set the data layout used to read values.
203    ///
204    /// # Safety
205    ///
206    /// Every sample address computed from `layout` must refer to an initialized, properly aligned
207    /// `f64` within every data allocation retained by this builder.
208    pub unsafe fn with_data_layout(mut self, layout: PlotDataLayout) -> Self {
209        self.layout = layout;
210        self
211    }
212
213    /// Set the sample-index offset used to read values.
214    pub fn with_offset(mut self, offset: PlotDataOffset) -> Self {
215        self.layout = self.layout.with_offset(offset);
216        self
217    }
218
219    /// Set the byte stride used to read values.
220    ///
221    /// # Safety
222    ///
223    /// Every strided sample read must remain initialized, aligned, and within every data allocation
224    /// retained by this builder.
225    pub unsafe fn with_stride(mut self, stride: PlotDataStride) -> Self {
226        self.layout = self.layout.with_stride(stride);
227        self
228    }
229
230    /// Make error bars horizontal instead of vertical
231    pub fn horizontal(mut self) -> Self {
232        self.flags |= ErrorBarsFlags::HORIZONTAL;
233        self
234    }
235
236    /// Validate the plot data
237    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        // Check for negative error values
243        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
286/// Simple error bars plot for quick plotting
287pub 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    /// Create a simple error bars plot with Y values only (X will be indices)
306    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    /// Set error bar flags for customization
320    pub fn with_flags(mut self, flags: ErrorBarsFlags) -> Self {
321        self.flags = flags;
322        self
323    }
324
325    /// Set common item flags for this plot item (applies to all plot types)
326    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
327        self.item_flags = flags;
328        self
329    }
330
331    /// Make error bars horizontal instead of vertical
332    pub fn horizontal(mut self) -> Self {
333        self.flags |= ErrorBarsFlags::HORIZONTAL;
334        self
335    }
336
337    /// Set X scale factor
338    pub fn with_x_scale(mut self, scale: f64) -> Self {
339        self.x_scale = scale;
340        self
341    }
342
343    /// Set X start value
344    pub fn with_x_start(mut self, start: f64) -> Self {
345        self.x_start = start;
346        self
347    }
348
349    /// Validate the plot data
350    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        // Create temporary X data
373        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
401/// Convenience functions for quick error bars plotting
402impl<'ui> crate::PlotUi<'ui> {
403    /// Plot error bars with symmetric errors
404    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    /// Plot error bars with asymmetric errors
418    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    /// Plot simple error bars with Y values only (X will be indices)
433    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}