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