Skip to main content

dear_implot/plots/
shaded.rs

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