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