Skip to main content

dear_implot/plots/
stairs.rs

1//! Stairs plot implementation
2
3use super::{
4    PlotData, PlotDataLayout, PlotDataOffset, PlotDataStride, PlotError, PlotItemStyle,
5    plot_spec_with_style, validate_data_lengths, with_plot_str_or_empty,
6};
7use crate::{ItemFlags, StairsFlags, sys};
8
9/// Builder for stairs plots with extensive customization options
10pub struct StairsPlot<'a> {
11    label: &'a str,
12    x_data: &'a [f64],
13    y_data: &'a [f64],
14    style: PlotItemStyle,
15    flags: StairsFlags,
16    item_flags: ItemFlags,
17    layout: PlotDataLayout,
18}
19
20impl<'a> super::PlotItemStyled for StairsPlot<'a> {
21    fn style_mut(&mut self) -> &mut PlotItemStyle {
22        &mut self.style
23    }
24}
25
26impl<'a> StairsPlot<'a> {
27    /// Create a new stairs plot with the given label and data
28    pub fn new(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
29        Self {
30            label,
31            x_data,
32            y_data,
33            style: PlotItemStyle::default(),
34            flags: StairsFlags::NONE,
35            item_flags: ItemFlags::NONE,
36            layout: PlotDataLayout::DEFAULT,
37        }
38    }
39
40    /// Set stairs flags for customization
41    pub fn with_flags(mut self, flags: StairsFlags) -> Self {
42        self.flags = flags;
43        self
44    }
45
46    /// Set common item flags for this plot item (applies to all plot types)
47    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
48        self.item_flags = flags;
49        self
50    }
51
52    /// Enable pre-step mode (step before the point instead of after)
53    pub fn pre_step(mut self) -> Self {
54        self.flags |= StairsFlags::PRE_STEP;
55        self
56    }
57
58    /// Enable shaded stairs (fill area under stairs)
59    pub fn shaded(mut self) -> Self {
60        self.flags |= StairsFlags::SHADED;
61        self
62    }
63
64    /// Set the data layout used to read X/Y samples.
65    ///
66    /// # Safety
67    ///
68    /// Every sample address computed from `layout` must refer to an initialized, properly aligned
69    /// `f64` within both coordinate allocations retained by this builder.
70    pub unsafe fn with_data_layout(mut self, layout: PlotDataLayout) -> Self {
71        self.layout = layout;
72        self
73    }
74
75    /// Set the sample-index offset used to read X/Y samples.
76    pub fn with_offset(mut self, offset: PlotDataOffset) -> Self {
77        self.layout = self.layout.with_offset(offset);
78        self
79    }
80
81    /// Set the byte stride used to read X/Y samples.
82    ///
83    /// # Safety
84    ///
85    /// Every strided sample read must remain initialized, aligned, and within both coordinate
86    /// allocations retained by this builder.
87    pub unsafe fn with_stride(mut self, stride: PlotDataStride) -> Self {
88        self.layout = self.layout.with_stride(stride);
89        self
90    }
91
92    /// Validate the plot data
93    pub fn validate(&self) -> Result<(), PlotError> {
94        validate_data_lengths(self.x_data, self.y_data)
95    }
96
97    /// Plot the stairs
98    pub fn plot(self, plot_ui: &crate::PlotUi<'_>) {
99        let Ok(count) = i32::try_from(self.x_data.len()) else {
100            return;
101        };
102        plot_ui.with_bound_context(|| {
103            with_plot_str_or_empty(self.label, |label_ptr| unsafe {
104                let spec = plot_spec_with_style(
105                    self.style,
106                    self.flags.bits() | self.item_flags.bits(),
107                    self.layout,
108                );
109                sys::ImPlot_PlotStairs_doublePtrdoublePtr(
110                    label_ptr,
111                    self.x_data.as_ptr(),
112                    self.y_data.as_ptr(),
113                    count,
114                    spec,
115                );
116            })
117        })
118    }
119}
120
121impl<'a> PlotData for StairsPlot<'a> {
122    fn label(&self) -> &str {
123        self.label
124    }
125
126    fn data_len(&self) -> usize {
127        self.x_data.len().min(self.y_data.len())
128    }
129}
130
131/// Simple stairs plot for f32 data
132pub struct StairsPlotF32<'a> {
133    label: &'a str,
134    x_data: &'a [f32],
135    y_data: &'a [f32],
136    style: PlotItemStyle,
137    flags: StairsFlags,
138    item_flags: ItemFlags,
139}
140
141impl<'a> super::PlotItemStyled for StairsPlotF32<'a> {
142    fn style_mut(&mut self) -> &mut PlotItemStyle {
143        &mut self.style
144    }
145}
146
147impl<'a> StairsPlotF32<'a> {
148    /// Create a new stairs plot with f32 data
149    pub fn new(label: &'a str, x_data: &'a [f32], y_data: &'a [f32]) -> Self {
150        Self {
151            label,
152            x_data,
153            y_data,
154            style: PlotItemStyle::default(),
155            flags: StairsFlags::NONE,
156            item_flags: ItemFlags::NONE,
157        }
158    }
159
160    /// Set stairs flags for customization
161    pub fn with_flags(mut self, flags: StairsFlags) -> 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    /// Enable pre-step mode
173    pub fn pre_step(mut self) -> Self {
174        self.flags |= StairsFlags::PRE_STEP;
175        self
176    }
177
178    /// Enable shaded stairs
179    pub fn shaded(mut self) -> Self {
180        self.flags |= StairsFlags::SHADED;
181        self
182    }
183
184    /// Validate the plot data
185    pub fn validate(&self) -> Result<(), PlotError> {
186        if self.x_data.len() != self.y_data.len() {
187            return Err(PlotError::DataLengthMismatch {
188                x_len: self.x_data.len(),
189                y_len: self.y_data.len(),
190            });
191        }
192        if self.x_data.is_empty() {
193            return Err(PlotError::EmptyData);
194        }
195        Ok(())
196    }
197
198    /// Plot the stairs
199    pub fn plot(self, plot_ui: &crate::PlotUi<'_>) {
200        let Ok(count) = i32::try_from(self.x_data.len()) else {
201            return;
202        };
203        plot_ui.with_bound_context(|| {
204            with_plot_str_or_empty(self.label, |label_ptr| unsafe {
205                let spec = plot_spec_with_style(
206                    self.style,
207                    self.flags.bits() | self.item_flags.bits(),
208                    PlotDataLayout::DEFAULT,
209                );
210                sys::ImPlot_PlotStairs_FloatPtrFloatPtr(
211                    label_ptr,
212                    self.x_data.as_ptr(),
213                    self.y_data.as_ptr(),
214                    count,
215                    spec,
216                );
217            })
218        })
219    }
220}
221
222impl<'a> PlotData for StairsPlotF32<'a> {
223    fn label(&self) -> &str {
224        self.label
225    }
226
227    fn data_len(&self) -> usize {
228        self.x_data.len().min(self.y_data.len())
229    }
230}
231
232/// Simple stairs plot for single array data (y values only, x is auto-generated)
233pub struct SimpleStairsPlot<'a> {
234    label: &'a str,
235    y_data: &'a [f64],
236    style: PlotItemStyle,
237    flags: StairsFlags,
238    item_flags: ItemFlags,
239    x_scale: f64,
240    x_start: f64,
241}
242
243impl<'a> super::PlotItemStyled for SimpleStairsPlot<'a> {
244    fn style_mut(&mut self) -> &mut PlotItemStyle {
245        &mut self.style
246    }
247}
248
249impl<'a> SimpleStairsPlot<'a> {
250    /// Create a new simple stairs plot with only y data
251    pub fn new(label: &'a str, y_data: &'a [f64]) -> Self {
252        Self {
253            label,
254            y_data,
255            style: PlotItemStyle::default(),
256            flags: StairsFlags::NONE,
257            item_flags: ItemFlags::NONE,
258            x_scale: 1.0,
259            x_start: 0.0,
260        }
261    }
262
263    /// Set the x scale (spacing between points)
264    pub fn with_x_scale(mut self, x_scale: f64) -> Self {
265        self.x_scale = x_scale;
266        self
267    }
268
269    /// Set the x start value
270    pub fn with_x_start(mut self, x_start: f64) -> Self {
271        self.x_start = x_start;
272        self
273    }
274
275    /// Set stairs flags
276    pub fn with_flags(mut self, flags: StairsFlags) -> Self {
277        self.flags = flags;
278        self
279    }
280
281    /// Set common item flags for this plot item (applies to all plot types)
282    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
283        self.item_flags = flags;
284        self
285    }
286
287    /// Enable pre-step mode
288    pub fn pre_step(mut self) -> Self {
289        self.flags |= StairsFlags::PRE_STEP;
290        self
291    }
292
293    /// Enable shaded stairs
294    pub fn shaded(mut self) -> Self {
295        self.flags |= StairsFlags::SHADED;
296        self
297    }
298
299    /// Validate the plot data
300    pub fn validate(&self) -> Result<(), PlotError> {
301        if self.y_data.is_empty() {
302            return Err(PlotError::EmptyData);
303        }
304        Ok(())
305    }
306
307    /// Plot the stairs
308    pub fn plot(self, plot_ui: &crate::PlotUi<'_>) {
309        let Ok(count) = i32::try_from(self.y_data.len()) else {
310            return;
311        };
312        plot_ui.with_bound_context(|| {
313            with_plot_str_or_empty(self.label, |label_ptr| unsafe {
314                let spec = plot_spec_with_style(
315                    self.style,
316                    self.flags.bits() | self.item_flags.bits(),
317                    PlotDataLayout::DEFAULT,
318                );
319                sys::ImPlot_PlotStairs_doublePtrInt(
320                    label_ptr,
321                    self.y_data.as_ptr(),
322                    count,
323                    self.x_scale,
324                    self.x_start,
325                    spec,
326                );
327            })
328        })
329    }
330}
331
332impl<'a> PlotData for SimpleStairsPlot<'a> {
333    fn label(&self) -> &str {
334        self.label
335    }
336
337    fn data_len(&self) -> usize {
338        self.y_data.len()
339    }
340}