Skip to main content

dear_implot/plots/
digital.rs

1//! Digital 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::{DigitalFlags, ItemFlags, sys};
8
9/// Builder for digital plots with extensive customization options
10///
11/// Digital plots are used to display digital signals (0/1, high/low, etc.)
12/// They do not respond to y drag or zoom, and are always referenced to the bottom of the plot.
13pub struct DigitalPlot<'a> {
14    label: &'a str,
15    x_data: &'a [f64],
16    y_data: &'a [f64],
17    style: PlotItemStyle,
18    flags: DigitalFlags,
19    item_flags: ItemFlags,
20    layout: PlotDataLayout,
21}
22
23impl<'a> super::PlotItemStyled for DigitalPlot<'a> {
24    fn style_mut(&mut self) -> &mut PlotItemStyle {
25        &mut self.style
26    }
27}
28
29impl<'a> DigitalPlot<'a> {
30    /// Create a new digital plot with the given label and data
31    pub fn new(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
32        Self {
33            label,
34            x_data,
35            y_data,
36            style: PlotItemStyle::default(),
37            flags: DigitalFlags::NONE,
38            item_flags: ItemFlags::NONE,
39            layout: PlotDataLayout::DEFAULT,
40        }
41    }
42
43    /// Set digital flags for customization
44    pub fn with_flags(mut self, flags: DigitalFlags) -> Self {
45        self.flags = flags;
46        self
47    }
48
49    /// Set ImPlotSpec-backed style overrides for this digital plot.
50    pub fn with_style(mut self, style: PlotItemStyle) -> Self {
51        self.style = style;
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 both coordinate allocations 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 both coordinate
83    /// allocations 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        // Digital plots should have binary-like data (0/1, but we allow any values)
94        // The validation is mainly for data length consistency
95        Ok(())
96    }
97
98    /// Plot the digital signal
99    pub fn plot(self, plot_ui: &crate::PlotUi<'_>) {
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_PlotDigital_doublePtr(
111                    label_ptr,
112                    self.x_data.as_ptr(),
113                    self.y_data.as_ptr(),
114                    count,
115                    spec,
116                );
117            })
118        })
119    }
120}
121
122impl<'a> PlotData for DigitalPlot<'a> {
123    fn label(&self) -> &str {
124        self.label
125    }
126
127    fn data_len(&self) -> usize {
128        self.x_data.len().min(self.y_data.len())
129    }
130}
131
132/// Digital plot for f32 data
133pub struct DigitalPlotF32<'a> {
134    label: &'a str,
135    x_data: &'a [f32],
136    y_data: &'a [f32],
137    style: PlotItemStyle,
138    flags: DigitalFlags,
139    item_flags: ItemFlags,
140}
141
142impl<'a> super::PlotItemStyled for DigitalPlotF32<'a> {
143    fn style_mut(&mut self) -> &mut PlotItemStyle {
144        &mut self.style
145    }
146}
147
148impl<'a> DigitalPlotF32<'a> {
149    /// Create a new digital plot with f32 data
150    pub fn new(label: &'a str, x_data: &'a [f32], y_data: &'a [f32]) -> Self {
151        Self {
152            label,
153            x_data,
154            y_data,
155            style: PlotItemStyle::default(),
156            flags: DigitalFlags::NONE,
157            item_flags: ItemFlags::NONE,
158        }
159    }
160
161    /// Set ImPlotSpec-backed style overrides for this digital plot.
162    pub fn with_style(mut self, style: PlotItemStyle) -> Self {
163        self.style = style;
164        self
165    }
166
167    /// Set digital flags for customization
168    pub fn with_flags(mut self, flags: DigitalFlags) -> Self {
169        self.flags = flags;
170        self
171    }
172
173    /// Set common item flags for this plot item (applies to all plot types)
174    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
175        self.item_flags = flags;
176        self
177    }
178
179    /// Validate the plot data
180    pub fn validate(&self) -> Result<(), PlotError> {
181        if self.x_data.len() != self.y_data.len() {
182            return Err(PlotError::DataLengthMismatch {
183                x_len: self.x_data.len(),
184                y_len: self.y_data.len(),
185            });
186        }
187        if self.x_data.is_empty() {
188            return Err(PlotError::EmptyData);
189        }
190        Ok(())
191    }
192
193    /// Plot the digital signal
194    pub fn plot(self, plot_ui: &crate::PlotUi<'_>) {
195        let Ok(count) = i32::try_from(self.x_data.len()) else {
196            return;
197        };
198        plot_ui.with_bound_context(|| {
199            with_plot_str_or_empty(self.label, |label_ptr| unsafe {
200                let spec = plot_spec_with_style(
201                    self.style,
202                    self.flags.bits() | self.item_flags.bits(),
203                    PlotDataLayout::DEFAULT,
204                );
205                sys::ImPlot_PlotDigital_FloatPtr(
206                    label_ptr,
207                    self.x_data.as_ptr(),
208                    self.y_data.as_ptr(),
209                    count,
210                    spec,
211                );
212            })
213        })
214    }
215}
216
217impl<'a> PlotData for DigitalPlotF32<'a> {
218    fn label(&self) -> &str {
219        self.label
220    }
221
222    fn data_len(&self) -> usize {
223        self.x_data.len().min(self.y_data.len())
224    }
225}
226
227/// Simple digital plot for single array data (y values only, x is auto-generated)
228pub struct SimpleDigitalPlot<'a> {
229    label: &'a str,
230    y_data: &'a [f64],
231    style: PlotItemStyle,
232    flags: DigitalFlags,
233    item_flags: ItemFlags,
234    x_scale: f64,
235    x_start: f64,
236}
237
238impl<'a> super::PlotItemStyled for SimpleDigitalPlot<'a> {
239    fn style_mut(&mut self) -> &mut PlotItemStyle {
240        &mut self.style
241    }
242}
243
244impl<'a> SimpleDigitalPlot<'a> {
245    /// Create a new simple digital plot with only y data
246    pub fn new(label: &'a str, y_data: &'a [f64]) -> Self {
247        Self {
248            label,
249            y_data,
250            style: PlotItemStyle::default(),
251            flags: DigitalFlags::NONE,
252            item_flags: ItemFlags::NONE,
253            x_scale: 1.0,
254            x_start: 0.0,
255        }
256    }
257
258    /// Set the x scale (spacing between points)
259    pub fn with_x_scale(mut self, x_scale: f64) -> Self {
260        self.x_scale = x_scale;
261        self
262    }
263
264    /// Set the x start value
265    pub fn with_x_start(mut self, x_start: f64) -> Self {
266        self.x_start = x_start;
267        self
268    }
269
270    /// Set digital flags
271    pub fn with_flags(mut self, flags: DigitalFlags) -> Self {
272        self.flags = flags;
273        self
274    }
275
276    /// Set ImPlotSpec-backed style overrides for this digital plot.
277    pub fn with_style(mut self, style: PlotItemStyle) -> Self {
278        self.style = style;
279        self
280    }
281
282    /// Set common item flags for this plot item (applies to all plot types)
283    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
284        self.item_flags = flags;
285        self
286    }
287
288    /// Validate the plot data
289    pub fn validate(&self) -> Result<(), PlotError> {
290        if self.y_data.is_empty() {
291            return Err(PlotError::EmptyData);
292        }
293        Ok(())
294    }
295
296    /// Plot the digital signal
297    pub fn plot(self, plot_ui: &crate::PlotUi<'_>) {
298        let Ok(count) = i32::try_from(self.y_data.len()) else {
299            return;
300        };
301        // Generate x data
302        let x_data: Vec<f64> = (0..self.y_data.len())
303            .map(|i| self.x_start + i as f64 * self.x_scale)
304            .collect();
305
306        plot_ui.with_bound_context(|| {
307            with_plot_str_or_empty(self.label, |label_ptr| unsafe {
308                let spec = plot_spec_with_style(
309                    self.style,
310                    self.flags.bits() | self.item_flags.bits(),
311                    PlotDataLayout::DEFAULT,
312                );
313                sys::ImPlot_PlotDigital_doublePtr(
314                    label_ptr,
315                    x_data.as_ptr(),
316                    self.y_data.as_ptr(),
317                    count,
318                    spec,
319                );
320            })
321        })
322    }
323}
324
325impl<'a> PlotData for SimpleDigitalPlot<'a> {
326    fn label(&self) -> &str {
327        self.label
328    }
329
330    fn data_len(&self) -> usize {
331        self.y_data.len()
332    }
333}
334
335/// Digital plot for boolean data (true/false converted to 1.0/0.0)
336pub struct BooleanDigitalPlot<'a> {
337    label: &'a str,
338    x_data: &'a [f64],
339    y_data: &'a [bool],
340    style: PlotItemStyle,
341    flags: DigitalFlags,
342    item_flags: ItemFlags,
343}
344
345impl<'a> super::PlotItemStyled for BooleanDigitalPlot<'a> {
346    fn style_mut(&mut self) -> &mut PlotItemStyle {
347        &mut self.style
348    }
349}
350
351impl<'a> BooleanDigitalPlot<'a> {
352    /// Create a new digital plot with boolean data
353    pub fn new(label: &'a str, x_data: &'a [f64], y_data: &'a [bool]) -> Self {
354        Self {
355            label,
356            x_data,
357            y_data,
358            style: PlotItemStyle::default(),
359            flags: DigitalFlags::NONE,
360            item_flags: ItemFlags::NONE,
361        }
362    }
363
364    /// Set ImPlotSpec-backed style overrides for this digital plot.
365    pub fn with_style(mut self, style: PlotItemStyle) -> Self {
366        self.style = style;
367        self
368    }
369
370    /// Set digital flags for customization
371    pub fn with_flags(mut self, flags: DigitalFlags) -> Self {
372        self.flags = flags;
373        self
374    }
375
376    /// Set common item flags for this plot item (applies to all plot types)
377    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
378        self.item_flags = flags;
379        self
380    }
381
382    /// Validate the plot data
383    pub fn validate(&self) -> Result<(), PlotError> {
384        if self.x_data.len() != self.y_data.len() {
385            return Err(PlotError::DataLengthMismatch {
386                x_len: self.x_data.len(),
387                y_len: self.y_data.len(),
388            });
389        }
390        if self.x_data.is_empty() {
391            return Err(PlotError::EmptyData);
392        }
393        Ok(())
394    }
395
396    /// Plot the digital signal
397    pub fn plot(self, plot_ui: &crate::PlotUi<'_>) {
398        let Ok(count) = i32::try_from(self.x_data.len()) else {
399            return;
400        };
401        // Convert boolean data to f64
402        let y_data_f64: Vec<f64> = self
403            .y_data
404            .iter()
405            .map(|&b| if b { 1.0 } else { 0.0 })
406            .collect();
407
408        plot_ui.with_bound_context(|| {
409            with_plot_str_or_empty(self.label, |label_ptr| unsafe {
410                let spec = plot_spec_with_style(
411                    self.style,
412                    self.flags.bits() | self.item_flags.bits(),
413                    PlotDataLayout::DEFAULT,
414                );
415                sys::ImPlot_PlotDigital_doublePtr(
416                    label_ptr,
417                    self.x_data.as_ptr(),
418                    y_data_f64.as_ptr(),
419                    count,
420                    spec,
421                );
422            })
423        })
424    }
425}
426
427impl<'a> PlotData for BooleanDigitalPlot<'a> {
428    fn label(&self) -> &str {
429        self.label
430    }
431
432    fn data_len(&self) -> usize {
433        self.x_data.len().min(self.y_data.len())
434    }
435}