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