dear_implot/plots/
error_bars.rs

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