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    pub fn with_data_layout(mut self, layout: PlotDataLayout) -> Self {
62        self.layout = layout;
63        self
64    }
65
66    /// Set the sample-index offset used to read values.
67    pub fn with_offset(mut self, offset: PlotDataOffset) -> Self {
68        self.layout = self.layout.with_offset(offset);
69        self
70    }
71
72    /// Set the byte stride used to read values.
73    pub fn with_stride(mut self, stride: PlotDataStride) -> Self {
74        self.layout = self.layout.with_stride(stride);
75        self
76    }
77
78    /// Make error bars horizontal instead of vertical
79    pub fn horizontal(mut self) -> Self {
80        self.flags |= ErrorBarsFlags::HORIZONTAL;
81        self
82    }
83
84    /// Validate the plot data
85    pub fn validate(&self) -> Result<(), PlotError> {
86        validate_data_lengths(self.x_data, self.y_data)?;
87        validate_data_lengths(self.x_data, self.err_data)?;
88
89        // Check for negative error values
90        if self.err_data.iter().any(|&err| err < 0.0) {
91            return Err(PlotError::InvalidData(
92                "Error values cannot be negative".to_string(),
93            ));
94        }
95
96        Ok(())
97    }
98}
99
100impl<'a> Plot for ErrorBarsPlot<'a> {
101    fn plot(&self, plot_ui: &crate::PlotUi<'_>) {
102        if self.validate().is_err() {
103            return;
104        }
105        let Ok(count) = i32::try_from(self.x_data.len()) else {
106            return;
107        };
108        let _guard = plot_ui.bind();
109        with_plot_str_or_empty(self.label, |label_ptr| unsafe {
110            let spec = plot_spec_with_style(
111                self.style,
112                self.flags.bits() | self.item_flags.bits(),
113                self.layout,
114            );
115            sys::ImPlot_PlotErrorBars_doublePtrdoublePtrdoublePtrInt(
116                label_ptr,
117                self.x_data.as_ptr(),
118                self.y_data.as_ptr(),
119                self.err_data.as_ptr(),
120                count,
121                spec,
122            );
123        })
124    }
125
126    fn label(&self) -> &str {
127        self.label
128    }
129}
130
131/// Builder for asymmetric error bars plots
132pub struct AsymmetricErrorBarsPlot<'a> {
133    label: &'a str,
134    x_data: &'a [f64],
135    y_data: &'a [f64],
136    err_neg: &'a [f64],
137    err_pos: &'a [f64],
138    style: PlotItemStyle,
139    flags: ErrorBarsFlags,
140    item_flags: ItemFlags,
141    layout: PlotDataLayout,
142}
143
144impl<'a> super::PlotItemStyled for AsymmetricErrorBarsPlot<'a> {
145    fn style_mut(&mut self) -> &mut PlotItemStyle {
146        &mut self.style
147    }
148}
149
150impl<'a> AsymmetricErrorBarsPlot<'a> {
151    /// Create a new asymmetric error bars plot
152    ///
153    /// # Arguments
154    /// * `label` - The label for the error bars
155    /// * `x_data` - X coordinates of the data points
156    /// * `y_data` - Y coordinates of the data points
157    /// * `err_neg` - Negative error values (downward/leftward)
158    /// * `err_pos` - Positive error values (upward/rightward)
159    pub fn new(
160        label: &'a str,
161        x_data: &'a [f64],
162        y_data: &'a [f64],
163        err_neg: &'a [f64],
164        err_pos: &'a [f64],
165    ) -> Self {
166        Self {
167            label,
168            x_data,
169            y_data,
170            err_neg,
171            err_pos,
172            style: PlotItemStyle::default(),
173            flags: ErrorBarsFlags::NONE,
174            item_flags: ItemFlags::NONE,
175            layout: PlotDataLayout::DEFAULT,
176        }
177    }
178
179    /// Set error bar flags for customization
180    pub fn with_flags(mut self, flags: ErrorBarsFlags) -> Self {
181        self.flags = flags;
182        self
183    }
184
185    /// Set common item flags for this plot item (applies to all plot types)
186    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
187        self.item_flags = flags;
188        self
189    }
190
191    /// Set the data layout used to read values.
192    pub fn with_data_layout(mut self, layout: PlotDataLayout) -> Self {
193        self.layout = layout;
194        self
195    }
196
197    /// Set the sample-index offset used to read values.
198    pub fn with_offset(mut self, offset: PlotDataOffset) -> Self {
199        self.layout = self.layout.with_offset(offset);
200        self
201    }
202
203    /// Set the byte stride used to read values.
204    pub fn with_stride(mut self, stride: PlotDataStride) -> Self {
205        self.layout = self.layout.with_stride(stride);
206        self
207    }
208
209    /// Make error bars horizontal instead of vertical
210    pub fn horizontal(mut self) -> Self {
211        self.flags |= ErrorBarsFlags::HORIZONTAL;
212        self
213    }
214
215    /// Validate the plot data
216    pub fn validate(&self) -> Result<(), PlotError> {
217        validate_data_lengths(self.x_data, self.y_data)?;
218        validate_data_lengths(self.x_data, self.err_neg)?;
219        validate_data_lengths(self.x_data, self.err_pos)?;
220
221        // Check for negative error values
222        if self.err_neg.iter().any(|&err| err < 0.0) || self.err_pos.iter().any(|&err| err < 0.0) {
223            return Err(PlotError::InvalidData(
224                "Error values cannot be negative".to_string(),
225            ));
226        }
227
228        Ok(())
229    }
230}
231
232impl<'a> Plot for AsymmetricErrorBarsPlot<'a> {
233    fn plot(&self, plot_ui: &crate::PlotUi<'_>) {
234        if self.validate().is_err() {
235            return;
236        }
237        let Ok(count) = i32::try_from(self.x_data.len()) else {
238            return;
239        };
240        let _guard = plot_ui.bind();
241        with_plot_str_or_empty(self.label, |label_ptr| unsafe {
242            let spec = plot_spec_with_style(
243                self.style,
244                self.flags.bits() | self.item_flags.bits(),
245                self.layout,
246            );
247            sys::ImPlot_PlotErrorBars_doublePtrdoublePtrdoublePtrdoublePtr(
248                label_ptr,
249                self.x_data.as_ptr(),
250                self.y_data.as_ptr(),
251                self.err_neg.as_ptr(),
252                self.err_pos.as_ptr(),
253                count,
254                spec,
255            );
256        })
257    }
258
259    fn label(&self) -> &str {
260        self.label
261    }
262}
263
264/// Simple error bars plot for quick plotting
265pub struct SimpleErrorBarsPlot<'a> {
266    label: &'a str,
267    values: &'a [f64],
268    errors: &'a [f64],
269    style: PlotItemStyle,
270    flags: ErrorBarsFlags,
271    item_flags: ItemFlags,
272    x_scale: f64,
273    x_start: f64,
274}
275
276impl<'a> super::PlotItemStyled for SimpleErrorBarsPlot<'a> {
277    fn style_mut(&mut self) -> &mut PlotItemStyle {
278        &mut self.style
279    }
280}
281
282impl<'a> SimpleErrorBarsPlot<'a> {
283    /// Create a simple error bars plot with Y values only (X will be indices)
284    pub fn new(label: &'a str, values: &'a [f64], errors: &'a [f64]) -> Self {
285        Self {
286            label,
287            values,
288            errors,
289            style: PlotItemStyle::default(),
290            flags: ErrorBarsFlags::NONE,
291            item_flags: ItemFlags::NONE,
292            x_scale: 1.0,
293            x_start: 0.0,
294        }
295    }
296
297    /// Set error bar flags for customization
298    pub fn with_flags(mut self, flags: ErrorBarsFlags) -> Self {
299        self.flags = flags;
300        self
301    }
302
303    /// Set common item flags for this plot item (applies to all plot types)
304    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
305        self.item_flags = flags;
306        self
307    }
308
309    /// Make error bars horizontal instead of vertical
310    pub fn horizontal(mut self) -> Self {
311        self.flags |= ErrorBarsFlags::HORIZONTAL;
312        self
313    }
314
315    /// Set X scale factor
316    pub fn with_x_scale(mut self, scale: f64) -> Self {
317        self.x_scale = scale;
318        self
319    }
320
321    /// Set X start value
322    pub fn with_x_start(mut self, start: f64) -> Self {
323        self.x_start = start;
324        self
325    }
326
327    /// Validate the plot data
328    pub fn validate(&self) -> Result<(), PlotError> {
329        validate_data_lengths(self.values, self.errors)?;
330
331        if self.errors.iter().any(|&err| err < 0.0) {
332            return Err(PlotError::InvalidData(
333                "Error values cannot be negative".to_string(),
334            ));
335        }
336
337        Ok(())
338    }
339}
340
341impl<'a> Plot for SimpleErrorBarsPlot<'a> {
342    fn plot(&self, plot_ui: &crate::PlotUi<'_>) {
343        if self.validate().is_err() {
344            return;
345        }
346        let Ok(count) = i32::try_from(self.values.len()) else {
347            return;
348        };
349
350        // Create temporary X data
351        let x_data: Vec<f64> = (0..self.values.len())
352            .map(|i| self.x_start + i as f64 * self.x_scale)
353            .collect();
354
355        let _guard = plot_ui.bind();
356        with_plot_str_or_empty(self.label, |label_ptr| unsafe {
357            let spec = plot_spec_with_style(
358                self.style,
359                self.flags.bits() | self.item_flags.bits(),
360                PlotDataLayout::DEFAULT,
361            );
362            sys::ImPlot_PlotErrorBars_doublePtrdoublePtrdoublePtrInt(
363                label_ptr,
364                x_data.as_ptr(),
365                self.values.as_ptr(),
366                self.errors.as_ptr(),
367                count,
368                spec,
369            );
370        })
371    }
372
373    fn label(&self) -> &str {
374        self.label
375    }
376}
377
378/// Convenience functions for quick error bars plotting
379impl<'ui> crate::PlotUi<'ui> {
380    /// Plot error bars with symmetric errors
381    pub fn error_bars_plot(
382        &self,
383        label: &str,
384        x_data: &[f64],
385        y_data: &[f64],
386        err_data: &[f64],
387    ) -> Result<(), PlotError> {
388        let plot = ErrorBarsPlot::new(label, x_data, y_data, err_data);
389        plot.validate()?;
390        plot.plot(self);
391        Ok(())
392    }
393
394    /// Plot error bars with asymmetric errors
395    pub fn asymmetric_error_bars_plot(
396        &self,
397        label: &str,
398        x_data: &[f64],
399        y_data: &[f64],
400        err_neg: &[f64],
401        err_pos: &[f64],
402    ) -> Result<(), PlotError> {
403        let plot = AsymmetricErrorBarsPlot::new(label, x_data, y_data, err_neg, err_pos);
404        plot.validate()?;
405        plot.plot(self);
406        Ok(())
407    }
408
409    /// Plot simple error bars with Y values only (X will be indices)
410    pub fn simple_error_bars_plot(
411        &self,
412        label: &str,
413        values: &[f64],
414        errors: &[f64],
415    ) -> Result<(), PlotError> {
416        let plot = SimpleErrorBarsPlot::new(label, values, errors);
417        plot.validate()?;
418        plot.plot(self);
419        Ok(())
420    }
421}
422
423#[cfg(test)]
424mod tests {
425    use super::*;
426
427    #[test]
428    fn test_simple_error_bars_plot_flags() {
429        let values = [1.0, 2.0, 3.0, 4.0];
430        let errors = [0.1, 0.2, 0.3, 0.4];
431        let plot = SimpleErrorBarsPlot::new("test", &values, &errors)
432            .horizontal()
433            .with_item_flags(ItemFlags::NO_LEGEND);
434        assert_eq!(plot.label(), "test");
435        assert_eq!(plot.flags.bits(), ErrorBarsFlags::HORIZONTAL.bits());
436        assert_eq!(plot.item_flags, ItemFlags::NO_LEGEND);
437    }
438}