Skip to main content

dear_implot/plots/
scatter.rs

1//! Scatter plot implementation
2
3use super::{
4    Plot, PlotError, PlotItemStyle, plot_spec_with_style, validate_data_lengths,
5    with_plot_str_or_empty,
6};
7use crate::{ItemFlags, Marker, ScatterFlags, sys};
8
9/// Builder for scatter plots with customization options
10pub struct ScatterPlot<'a> {
11    label: &'a str,
12    x_data: &'a [f64],
13    y_data: &'a [f64],
14    style: PlotItemStyle,
15    flags: ScatterFlags,
16    item_flags: ItemFlags,
17    offset: i32,
18    stride: i32,
19}
20
21impl<'a> super::PlotItemStyled for ScatterPlot<'a> {
22    fn style_mut(&mut self) -> &mut PlotItemStyle {
23        &mut self.style
24    }
25}
26
27impl<'a> ScatterPlot<'a> {
28    /// Create a new scatter plot with the given label and data
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            flags: ScatterFlags::NONE,
36            item_flags: ItemFlags::NONE,
37            offset: 0,
38            stride: std::mem::size_of::<f64>() as i32,
39        }
40    }
41
42    /// Replace the entire item style override for this scatter plot.
43    pub fn with_style(mut self, style: PlotItemStyle) -> Self {
44        self.style = style;
45        self
46    }
47
48    /// Set the scatter line color. Use the alpha channel to control transparency.
49    pub fn with_line_color(mut self, color: [f32; 4]) -> Self {
50        self.style = self.style.with_line_color(color);
51        self
52    }
53
54    /// Set the outline width in pixels.
55    pub fn with_line_weight(mut self, weight: f32) -> Self {
56        self.style = self.style.with_line_weight(weight);
57        self
58    }
59
60    /// Set the marker type for the scatter plot.
61    pub fn with_marker(mut self, marker: Marker) -> Self {
62        self.style = self.style.with_marker(marker);
63        self
64    }
65
66    /// Set the marker size in pixels.
67    pub fn with_marker_size(mut self, size: f32) -> Self {
68        self.style = self.style.with_marker_size(size);
69        self
70    }
71
72    /// Set the marker outline color.
73    pub fn with_marker_line_color(mut self, color: [f32; 4]) -> Self {
74        self.style = self.style.with_marker_line_color(color);
75        self
76    }
77
78    /// Set the marker fill color.
79    pub fn with_marker_fill_color(mut self, color: [f32; 4]) -> Self {
80        self.style = self.style.with_marker_fill_color(color);
81        self
82    }
83
84    /// Set scatter flags for customization
85    pub fn with_flags(mut self, flags: ScatterFlags) -> Self {
86        self.flags = flags;
87        self
88    }
89
90    /// Set common item flags for this plot item (applies to all plot types)
91    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
92        self.item_flags = flags;
93        self
94    }
95
96    /// Set data offset for partial plotting
97    pub fn with_offset(mut self, offset: i32) -> Self {
98        self.offset = offset;
99        self
100    }
101
102    /// Set data stride for non-contiguous data
103    pub fn with_stride(mut self, stride: i32) -> Self {
104        self.stride = stride;
105        self
106    }
107
108    /// Validate the plot data
109    pub fn validate(&self) -> Result<(), PlotError> {
110        validate_data_lengths(self.x_data, self.y_data)
111    }
112}
113
114impl<'a> Plot for ScatterPlot<'a> {
115    fn plot(&self) {
116        if self.validate().is_err() {
117            return; // Skip plotting if data is invalid
118        }
119        let Ok(count) = i32::try_from(self.x_data.len()) else {
120            return;
121        };
122
123        with_plot_str_or_empty(self.label, |label_ptr| unsafe {
124            let spec = plot_spec_with_style(
125                self.style,
126                self.flags.bits() | self.item_flags.bits(),
127                self.offset,
128                self.stride,
129            );
130            sys::ImPlot_PlotScatter_doublePtrdoublePtr(
131                label_ptr,
132                self.x_data.as_ptr(),
133                self.y_data.as_ptr(),
134                count,
135                spec,
136            );
137        })
138    }
139
140    fn label(&self) -> &str {
141        self.label
142    }
143}
144
145/// Simple scatter plot for quick plotting without builder pattern
146pub struct SimpleScatterPlot<'a> {
147    label: &'a str,
148    values: &'a [f64],
149    style: PlotItemStyle,
150    flags: ScatterFlags,
151    item_flags: ItemFlags,
152    x_scale: f64,
153    x_start: f64,
154}
155
156impl<'a> super::PlotItemStyled for SimpleScatterPlot<'a> {
157    fn style_mut(&mut self) -> &mut PlotItemStyle {
158        &mut self.style
159    }
160}
161
162impl<'a> SimpleScatterPlot<'a> {
163    /// Create a simple scatter plot with Y values only (X will be indices)
164    pub fn new(label: &'a str, values: &'a [f64]) -> Self {
165        Self {
166            label,
167            values,
168            style: PlotItemStyle::default(),
169            flags: ScatterFlags::NONE,
170            item_flags: ItemFlags::NONE,
171            x_scale: 1.0,
172            x_start: 0.0,
173        }
174    }
175
176    /// Replace the entire item style override for this scatter plot.
177    pub fn with_style(mut self, style: PlotItemStyle) -> Self {
178        self.style = style;
179        self
180    }
181
182    /// Set the scatter line color. Use the alpha channel to control transparency.
183    pub fn with_line_color(mut self, color: [f32; 4]) -> Self {
184        self.style = self.style.with_line_color(color);
185        self
186    }
187
188    /// Set the outline width in pixels.
189    pub fn with_line_weight(mut self, weight: f32) -> Self {
190        self.style = self.style.with_line_weight(weight);
191        self
192    }
193
194    /// Set the marker type for the scatter plot.
195    pub fn with_marker(mut self, marker: Marker) -> Self {
196        self.style = self.style.with_marker(marker);
197        self
198    }
199
200    /// Set the marker size in pixels.
201    pub fn with_marker_size(mut self, size: f32) -> Self {
202        self.style = self.style.with_marker_size(size);
203        self
204    }
205
206    /// Set the marker outline color.
207    pub fn with_marker_line_color(mut self, color: [f32; 4]) -> Self {
208        self.style = self.style.with_marker_line_color(color);
209        self
210    }
211
212    /// Set the marker fill color.
213    pub fn with_marker_fill_color(mut self, color: [f32; 4]) -> Self {
214        self.style = self.style.with_marker_fill_color(color);
215        self
216    }
217
218    /// Set scatter flags for customization
219    pub fn with_flags(mut self, flags: ScatterFlags) -> Self {
220        self.flags = flags;
221        self
222    }
223
224    /// Set common item flags for this plot item (applies to all plot types)
225    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
226        self.item_flags = flags;
227        self
228    }
229
230    /// Set X scale factor
231    pub fn with_x_scale(mut self, scale: f64) -> Self {
232        self.x_scale = scale;
233        self
234    }
235
236    /// Set X start value
237    pub fn with_x_start(mut self, start: f64) -> Self {
238        self.x_start = start;
239        self
240    }
241}
242
243impl<'a> Plot for SimpleScatterPlot<'a> {
244    fn plot(&self) {
245        if self.values.is_empty() {
246            return;
247        }
248        let Ok(count) = i32::try_from(self.values.len()) else {
249            return;
250        };
251
252        with_plot_str_or_empty(self.label, |label_ptr| unsafe {
253            let spec = plot_spec_with_style(
254                self.style,
255                self.flags.bits() | self.item_flags.bits(),
256                0,
257                std::mem::size_of::<f64>() as i32,
258            );
259            sys::ImPlot_PlotScatter_doublePtrInt(
260                label_ptr,
261                self.values.as_ptr(),
262                count,
263                self.x_scale,
264                self.x_start,
265                spec,
266            );
267        })
268    }
269
270    fn label(&self) -> &str {
271        self.label
272    }
273}
274
275/// Convenience functions for quick scatter plotting
276impl<'ui> crate::PlotUi<'ui> {
277    /// Plot a scatter plot with X and Y data
278    pub fn scatter_plot(
279        &self,
280        label: &str,
281        x_data: &[f64],
282        y_data: &[f64],
283    ) -> Result<(), PlotError> {
284        let plot = ScatterPlot::new(label, x_data, y_data);
285        plot.validate()?;
286        plot.plot();
287        Ok(())
288    }
289
290    /// Plot a simple scatter plot with Y values only (X will be indices)
291    pub fn simple_scatter_plot(&self, label: &str, values: &[f64]) -> Result<(), PlotError> {
292        if values.is_empty() {
293            return Err(PlotError::EmptyData);
294        }
295        let plot = SimpleScatterPlot::new(label, values);
296        plot.plot();
297        Ok(())
298    }
299}
300
301#[cfg(test)]
302mod tests {
303    use super::*;
304
305    #[test]
306    fn test_scatter_plot_creation() {
307        let x_data = [1.0, 2.0, 3.0, 4.0];
308        let y_data = [1.0, 4.0, 2.0, 3.0];
309
310        let plot = ScatterPlot::new("test", &x_data, &y_data);
311        assert_eq!(plot.label(), "test");
312        assert!(plot.validate().is_ok());
313    }
314
315    #[test]
316    fn test_scatter_plot_validation() {
317        let x_data = [1.0, 2.0, 3.0];
318        let y_data = [1.0, 4.0]; // Different length
319
320        let plot = ScatterPlot::new("test", &x_data, &y_data);
321        assert!(plot.validate().is_err());
322    }
323
324    #[test]
325    fn test_simple_scatter_plot() {
326        let values = [1.0, 2.0, 3.0, 4.0];
327        let plot = SimpleScatterPlot::new("test", &values)
328            .with_flags(ScatterFlags::NO_CLIP)
329            .with_item_flags(ItemFlags::NO_FIT);
330        assert_eq!(plot.label(), "test");
331        assert_eq!(plot.flags.bits(), ScatterFlags::NO_CLIP.bits());
332        assert_eq!(plot.item_flags, ItemFlags::NO_FIT);
333    }
334
335    #[test]
336    fn test_scatter_plot_style_builders() {
337        let x_data = [1.0, 2.0, 3.0, 4.0];
338        let y_data = [1.0, 4.0, 2.0, 3.0];
339
340        let plot = ScatterPlot::new("styled", &x_data, &y_data)
341            .with_line_color([0.1, 0.2, 0.3, 0.4])
342            .with_line_weight(1.5)
343            .with_marker(Marker::Square)
344            .with_marker_size(8.0)
345            .with_marker_line_color([0.9, 0.8, 0.7, 0.6])
346            .with_marker_fill_color([0.6, 0.7, 0.8, 0.9]);
347
348        assert_eq!(
349            plot.style.line_color,
350            Some(sys::ImVec4_c {
351                x: 0.1,
352                y: 0.2,
353                z: 0.3,
354                w: 0.4,
355            })
356        );
357        assert_eq!(plot.style.line_weight, Some(1.5));
358        assert_eq!(plot.style.marker, Some(Marker::Square as sys::ImPlotMarker));
359        assert_eq!(plot.style.marker_size, Some(8.0));
360        assert_eq!(
361            plot.style.marker_line_color,
362            Some(sys::ImVec4_c {
363                x: 0.9,
364                y: 0.8,
365                z: 0.7,
366                w: 0.6,
367            })
368        );
369        assert_eq!(
370            plot.style.marker_fill_color,
371            Some(sys::ImVec4_c {
372                x: 0.6,
373                y: 0.7,
374                z: 0.8,
375                w: 0.9,
376            })
377        );
378    }
379}