Skip to main content

dear_implot/plots/
error_bars.rs

1//! Error bars plot implementation
2
3use super::{Plot, PlotError, plot_spec_from, validate_data_lengths, with_plot_str_or_empty};
4use crate::{ErrorBarsFlags, ItemFlags, sys};
5
6/// Builder for error bars plots
7pub struct ErrorBarsPlot<'a> {
8    label: &'a str,
9    x_data: &'a [f64],
10    y_data: &'a [f64],
11    err_data: &'a [f64],
12    flags: ErrorBarsFlags,
13    item_flags: ItemFlags,
14    offset: i32,
15    stride: i32,
16}
17
18impl<'a> ErrorBarsPlot<'a> {
19    /// Create a new error bars plot with symmetric errors
20    ///
21    /// # Arguments
22    /// * `label` - The label for the error bars
23    /// * `x_data` - X coordinates of the data points
24    /// * `y_data` - Y coordinates of the data points
25    /// * `err_data` - Error values (symmetric, ±err)
26    pub fn new(label: &'a str, x_data: &'a [f64], y_data: &'a [f64], err_data: &'a [f64]) -> Self {
27        Self {
28            label,
29            x_data,
30            y_data,
31            err_data,
32            flags: ErrorBarsFlags::NONE,
33            item_flags: ItemFlags::NONE,
34            offset: 0,
35            stride: std::mem::size_of::<f64>() as i32,
36        }
37    }
38
39    /// Set error bar flags for customization
40    pub fn with_flags(mut self, flags: ErrorBarsFlags) -> Self {
41        self.flags = flags;
42        self
43    }
44
45    /// Set common item flags for this plot item (applies to all plot types)
46    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
47        self.item_flags = flags;
48        self
49    }
50
51    /// Set data offset for partial plotting
52    pub fn with_offset(mut self, offset: i32) -> Self {
53        self.offset = offset;
54        self
55    }
56
57    /// Set data stride for non-contiguous data
58    pub fn with_stride(mut self, stride: i32) -> Self {
59        self.stride = stride;
60        self
61    }
62
63    /// Make error bars horizontal instead of vertical
64    pub fn horizontal(self) -> Self {
65        // Note: This would require a different flag or function
66        // For now, we'll keep it as a placeholder
67        self
68    }
69
70    /// Validate the plot data
71    pub fn validate(&self) -> Result<(), PlotError> {
72        validate_data_lengths(self.x_data, self.y_data)?;
73        validate_data_lengths(self.x_data, self.err_data)?;
74
75        // Check for negative error values
76        if self.err_data.iter().any(|&err| err < 0.0) {
77            return Err(PlotError::InvalidData(
78                "Error values cannot be negative".to_string(),
79            ));
80        }
81
82        Ok(())
83    }
84}
85
86impl<'a> Plot for ErrorBarsPlot<'a> {
87    fn plot(&self) {
88        if self.validate().is_err() {
89            return;
90        }
91        let Ok(count) = i32::try_from(self.x_data.len()) else {
92            return;
93        };
94        with_plot_str_or_empty(self.label, |label_ptr| unsafe {
95            let spec = plot_spec_from(
96                self.flags.bits() | self.item_flags.bits(),
97                self.offset,
98                self.stride,
99            );
100            sys::ImPlot_PlotErrorBars_doublePtrdoublePtrdoublePtrInt(
101                label_ptr,
102                self.x_data.as_ptr(),
103                self.y_data.as_ptr(),
104                self.err_data.as_ptr(),
105                count,
106                spec,
107            );
108        })
109    }
110
111    fn label(&self) -> &str {
112        self.label
113    }
114}
115
116/// Builder for asymmetric error bars plots
117pub struct AsymmetricErrorBarsPlot<'a> {
118    label: &'a str,
119    x_data: &'a [f64],
120    y_data: &'a [f64],
121    err_neg: &'a [f64],
122    err_pos: &'a [f64],
123    flags: ErrorBarsFlags,
124    item_flags: ItemFlags,
125}
126
127impl<'a> AsymmetricErrorBarsPlot<'a> {
128    /// Create a new asymmetric error bars plot
129    ///
130    /// # Arguments
131    /// * `label` - The label for the error bars
132    /// * `x_data` - X coordinates of the data points
133    /// * `y_data` - Y coordinates of the data points
134    /// * `err_neg` - Negative error values (downward/leftward)
135    /// * `err_pos` - Positive error values (upward/rightward)
136    pub fn new(
137        label: &'a str,
138        x_data: &'a [f64],
139        y_data: &'a [f64],
140        err_neg: &'a [f64],
141        err_pos: &'a [f64],
142    ) -> Self {
143        Self {
144            label,
145            x_data,
146            y_data,
147            err_neg,
148            err_pos,
149            flags: ErrorBarsFlags::NONE,
150            item_flags: ItemFlags::NONE,
151        }
152    }
153
154    /// Set error bar flags for customization
155    pub fn with_flags(mut self, flags: ErrorBarsFlags) -> Self {
156        self.flags = flags;
157        self
158    }
159
160    /// Set common item flags for this plot item (applies to all plot types)
161    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
162        self.item_flags = flags;
163        self
164    }
165
166    /// Validate the plot data
167    pub fn validate(&self) -> Result<(), PlotError> {
168        validate_data_lengths(self.x_data, self.y_data)?;
169        validate_data_lengths(self.x_data, self.err_neg)?;
170        validate_data_lengths(self.x_data, self.err_pos)?;
171
172        // Check for negative error values
173        if self.err_neg.iter().any(|&err| err < 0.0) || self.err_pos.iter().any(|&err| err < 0.0) {
174            return Err(PlotError::InvalidData(
175                "Error values cannot be negative".to_string(),
176            ));
177        }
178
179        Ok(())
180    }
181}
182
183impl<'a> Plot for AsymmetricErrorBarsPlot<'a> {
184    fn plot(&self) {
185        if self.validate().is_err() {
186            return;
187        }
188        let Ok(count) = i32::try_from(self.x_data.len()) else {
189            return;
190        };
191        with_plot_str_or_empty(self.label, |label_ptr| unsafe {
192            let spec = plot_spec_from(
193                self.flags.bits() | self.item_flags.bits(),
194                0,
195                std::mem::size_of::<f64>() as i32,
196            );
197            sys::ImPlot_PlotErrorBars_doublePtrdoublePtrdoublePtrdoublePtr(
198                label_ptr,
199                self.x_data.as_ptr(),
200                self.y_data.as_ptr(),
201                self.err_neg.as_ptr(),
202                self.err_pos.as_ptr(),
203                count,
204                spec,
205            );
206        })
207    }
208
209    fn label(&self) -> &str {
210        self.label
211    }
212}
213
214/// Simple error bars plot for quick plotting
215pub struct SimpleErrorBarsPlot<'a> {
216    label: &'a str,
217    values: &'a [f64],
218    errors: &'a [f64],
219    x_scale: f64,
220    x_start: f64,
221}
222
223impl<'a> SimpleErrorBarsPlot<'a> {
224    /// Create a simple error bars plot with Y values only (X will be indices)
225    pub fn new(label: &'a str, values: &'a [f64], errors: &'a [f64]) -> Self {
226        Self {
227            label,
228            values,
229            errors,
230            x_scale: 1.0,
231            x_start: 0.0,
232        }
233    }
234
235    /// Set X scale factor
236    pub fn with_x_scale(mut self, scale: f64) -> Self {
237        self.x_scale = scale;
238        self
239    }
240
241    /// Set X start value
242    pub fn with_x_start(mut self, start: f64) -> Self {
243        self.x_start = start;
244        self
245    }
246
247    /// Validate the plot data
248    pub fn validate(&self) -> Result<(), PlotError> {
249        validate_data_lengths(self.values, self.errors)?;
250
251        if self.errors.iter().any(|&err| err < 0.0) {
252            return Err(PlotError::InvalidData(
253                "Error values cannot be negative".to_string(),
254            ));
255        }
256
257        Ok(())
258    }
259}
260
261impl<'a> Plot for SimpleErrorBarsPlot<'a> {
262    fn plot(&self) {
263        if self.validate().is_err() {
264            return;
265        }
266        let Ok(count) = i32::try_from(self.values.len()) else {
267            return;
268        };
269
270        // Create temporary X data
271        let x_data: Vec<f64> = (0..self.values.len())
272            .map(|i| self.x_start + i as f64 * self.x_scale)
273            .collect();
274
275        with_plot_str_or_empty(self.label, |label_ptr| unsafe {
276            let spec = plot_spec_from(0, 0, std::mem::size_of::<f64>() as i32);
277            sys::ImPlot_PlotErrorBars_doublePtrdoublePtrdoublePtrInt(
278                label_ptr,
279                x_data.as_ptr(),
280                self.values.as_ptr(),
281                self.errors.as_ptr(),
282                count,
283                spec,
284            );
285        })
286    }
287
288    fn label(&self) -> &str {
289        self.label
290    }
291}
292
293/// Convenience functions for quick error bars plotting
294impl<'ui> crate::PlotUi<'ui> {
295    /// Plot error bars with symmetric errors
296    pub fn error_bars_plot(
297        &self,
298        label: &str,
299        x_data: &[f64],
300        y_data: &[f64],
301        err_data: &[f64],
302    ) -> Result<(), PlotError> {
303        let plot = ErrorBarsPlot::new(label, x_data, y_data, err_data);
304        plot.validate()?;
305        plot.plot();
306        Ok(())
307    }
308
309    /// Plot error bars with asymmetric errors
310    pub fn asymmetric_error_bars_plot(
311        &self,
312        label: &str,
313        x_data: &[f64],
314        y_data: &[f64],
315        err_neg: &[f64],
316        err_pos: &[f64],
317    ) -> Result<(), PlotError> {
318        let plot = AsymmetricErrorBarsPlot::new(label, x_data, y_data, err_neg, err_pos);
319        plot.validate()?;
320        plot.plot();
321        Ok(())
322    }
323
324    /// Plot simple error bars with Y values only (X will be indices)
325    pub fn simple_error_bars_plot(
326        &self,
327        label: &str,
328        values: &[f64],
329        errors: &[f64],
330    ) -> Result<(), PlotError> {
331        let plot = SimpleErrorBarsPlot::new(label, values, errors);
332        plot.validate()?;
333        plot.plot();
334        Ok(())
335    }
336}