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