dear_implot/plots/
shaded.rs

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