Skip to main content

dear_implot/plots/
scatter.rs

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