Skip to main content

dear_implot/plots/
error_bars.rs

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