Skip to main content

dear_implot/plots/
shaded.rs

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