Skip to main content

dear_implot/plots/
shaded.rs

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