dear_implot/plots/
stems.rs

1//! Stem plot implementation
2
3use super::{Plot, PlotError, safe_cstring, validate_data_lengths};
4use crate::{StemsFlags, sys};
5
6/// Builder for stem plots (lollipop charts)
7pub struct StemPlot<'a> {
8    label: &'a str,
9    x_data: &'a [f64],
10    y_data: &'a [f64],
11    y_ref: f64,
12    flags: StemsFlags,
13    offset: i32,
14    stride: i32,
15}
16
17impl<'a> StemPlot<'a> {
18    /// Create a new stem plot with the given label and data
19    pub fn new(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
20        Self {
21            label,
22            x_data,
23            y_data,
24            y_ref: 0.0, // Default reference line at Y=0
25            flags: StemsFlags::NONE,
26            offset: 0,
27            stride: std::mem::size_of::<f64>() as i32,
28        }
29    }
30
31    /// Set the reference Y value for stems
32    /// Stems will be drawn from this Y value to the data points
33    pub fn with_y_ref(mut self, y_ref: f64) -> Self {
34        self.y_ref = y_ref;
35        self
36    }
37
38    /// Set stem flags for customization
39    pub fn with_flags(mut self, flags: StemsFlags) -> Self {
40        self.flags = flags;
41        self
42    }
43
44    /// Set data offset for partial plotting
45    pub fn with_offset(mut self, offset: i32) -> Self {
46        self.offset = offset;
47        self
48    }
49
50    /// Set data stride for non-contiguous data
51    pub fn with_stride(mut self, stride: i32) -> Self {
52        self.stride = stride;
53        self
54    }
55
56    /// Validate the plot data
57    pub fn validate(&self) -> Result<(), PlotError> {
58        validate_data_lengths(self.x_data, self.y_data)
59    }
60}
61
62impl<'a> Plot for StemPlot<'a> {
63    fn plot(&self) {
64        if self.validate().is_err() {
65            return;
66        }
67
68        let label_cstr = safe_cstring(self.label);
69
70        unsafe {
71            sys::ImPlot_PlotStems_doublePtrdoublePtr(
72                label_cstr.as_ptr(),
73                self.x_data.as_ptr(),
74                self.y_data.as_ptr(),
75                self.x_data.len() as i32,
76                self.y_ref,
77                self.flags.bits() as i32,
78                self.offset,
79                self.stride,
80            );
81        }
82    }
83
84    fn label(&self) -> &str {
85        self.label
86    }
87}
88
89/// Simple stem plot for quick plotting without builder pattern
90pub struct SimpleStemPlot<'a> {
91    label: &'a str,
92    values: &'a [f64],
93    y_ref: f64,
94    x_scale: f64,
95    x_start: f64,
96}
97
98impl<'a> SimpleStemPlot<'a> {
99    /// Create a simple stem plot with Y values only (X will be indices)
100    pub fn new(label: &'a str, values: &'a [f64]) -> Self {
101        Self {
102            label,
103            values,
104            y_ref: 0.0,
105            x_scale: 1.0,
106            x_start: 0.0,
107        }
108    }
109
110    /// Set the reference Y value for stems
111    pub fn with_y_ref(mut self, y_ref: f64) -> Self {
112        self.y_ref = y_ref;
113        self
114    }
115
116    /// Set X scale factor
117    pub fn with_x_scale(mut self, scale: f64) -> Self {
118        self.x_scale = scale;
119        self
120    }
121
122    /// Set X start value
123    pub fn with_x_start(mut self, start: f64) -> Self {
124        self.x_start = start;
125        self
126    }
127}
128
129impl<'a> Plot for SimpleStemPlot<'a> {
130    fn plot(&self) {
131        if self.values.is_empty() {
132            return;
133        }
134
135        let label_cstr = safe_cstring(self.label);
136
137        // Create temporary X data
138        let x_data: Vec<f64> = (0..self.values.len())
139            .map(|i| self.x_start + i as f64 * self.x_scale)
140            .collect();
141
142        unsafe {
143            sys::ImPlot_PlotStems_doublePtrdoublePtr(
144                label_cstr.as_ptr(),
145                x_data.as_ptr(),
146                self.values.as_ptr(),
147                self.values.len() as i32,
148                self.y_ref,
149                0, // flags
150                0,
151                std::mem::size_of::<f64>() as i32,
152            );
153        }
154    }
155
156    fn label(&self) -> &str {
157        self.label
158    }
159}
160
161/// Convenience functions for quick stem plotting
162impl<'ui> crate::PlotUi<'ui> {
163    /// Plot a stem plot with X and Y data
164    pub fn stem_plot(&self, label: &str, x_data: &[f64], y_data: &[f64]) -> Result<(), PlotError> {
165        let plot = StemPlot::new(label, x_data, y_data);
166        plot.validate()?;
167        plot.plot();
168        Ok(())
169    }
170
171    /// Plot a stem plot with custom reference Y value
172    pub fn stem_plot_with_ref(
173        &self,
174        label: &str,
175        x_data: &[f64],
176        y_data: &[f64],
177        y_ref: f64,
178    ) -> Result<(), PlotError> {
179        let plot = StemPlot::new(label, x_data, y_data).with_y_ref(y_ref);
180        plot.validate()?;
181        plot.plot();
182        Ok(())
183    }
184
185    /// Plot a simple stem plot with Y values only (X will be indices)
186    pub fn simple_stem_plot(&self, label: &str, values: &[f64]) -> Result<(), PlotError> {
187        if values.is_empty() {
188            return Err(PlotError::EmptyData);
189        }
190        let plot = SimpleStemPlot::new(label, values);
191        plot.plot();
192        Ok(())
193    }
194
195    /// Plot a simple stem plot with custom reference Y value
196    pub fn simple_stem_plot_with_ref(
197        &self,
198        label: &str,
199        values: &[f64],
200        y_ref: f64,
201    ) -> Result<(), PlotError> {
202        if values.is_empty() {
203            return Err(PlotError::EmptyData);
204        }
205        let plot = SimpleStemPlot::new(label, values).with_y_ref(y_ref);
206        plot.plot();
207        Ok(())
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214
215    #[test]
216    fn test_stem_plot_creation() {
217        let x_data = [1.0, 2.0, 3.0, 4.0];
218        let y_data = [1.0, 4.0, 2.0, 3.0];
219
220        let plot = StemPlot::new("test", &x_data, &y_data);
221        assert_eq!(plot.label(), "test");
222        assert!(plot.validate().is_ok());
223    }
224
225    #[test]
226    fn test_stem_plot_validation() {
227        let x_data = [1.0, 2.0, 3.0];
228        let y_data = [1.0, 4.0]; // Different length
229
230        let plot = StemPlot::new("test", &x_data, &y_data);
231        assert!(plot.validate().is_err());
232    }
233
234    #[test]
235    fn test_simple_stem_plot() {
236        let values = [1.0, 2.0, 3.0, 4.0];
237        let plot = SimpleStemPlot::new("test", &values);
238        assert_eq!(plot.label(), "test");
239    }
240
241    #[test]
242    fn test_stem_plot_with_ref() {
243        let x_data = [1.0, 2.0, 3.0, 4.0];
244        let y_data = [1.0, 4.0, 2.0, 3.0];
245
246        let plot = StemPlot::new("test", &x_data, &y_data).with_y_ref(1.0);
247        assert_eq!(plot.y_ref, 1.0);
248        assert!(plot.validate().is_ok());
249    }
250}