dear_implot/plots/
scatter.rs

1//! Scatter plot implementation
2
3use super::{Plot, PlotError, validate_data_lengths, with_plot_str_or_empty};
4use crate::{ScatterFlags, sys};
5
6/// Builder for scatter plots with customization options
7pub struct ScatterPlot<'a> {
8    label: &'a str,
9    x_data: &'a [f64],
10    y_data: &'a [f64],
11    flags: ScatterFlags,
12    offset: i32,
13    stride: i32,
14}
15
16impl<'a> ScatterPlot<'a> {
17    /// Create a new scatter plot with the given label and data
18    pub fn new(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
19        Self {
20            label,
21            x_data,
22            y_data,
23            flags: ScatterFlags::NONE,
24            offset: 0,
25            stride: std::mem::size_of::<f64>() as i32,
26        }
27    }
28
29    /// Set scatter flags for customization
30    pub fn with_flags(mut self, flags: ScatterFlags) -> Self {
31        self.flags = flags;
32        self
33    }
34
35    /// Set data offset for partial plotting
36    pub fn with_offset(mut self, offset: i32) -> Self {
37        self.offset = offset;
38        self
39    }
40
41    /// Set data stride for non-contiguous data
42    pub fn with_stride(mut self, stride: i32) -> Self {
43        self.stride = stride;
44        self
45    }
46
47    /// Validate the plot data
48    pub fn validate(&self) -> Result<(), PlotError> {
49        validate_data_lengths(self.x_data, self.y_data)
50    }
51}
52
53impl<'a> Plot for ScatterPlot<'a> {
54    fn plot(&self) {
55        if self.validate().is_err() {
56            return; // Skip plotting if data is invalid
57        }
58        let Ok(count) = i32::try_from(self.x_data.len()) else {
59            return;
60        };
61
62        with_plot_str_or_empty(self.label, |label_ptr| unsafe {
63            sys::ImPlot_PlotScatter_doublePtrdoublePtr(
64                label_ptr,
65                self.x_data.as_ptr(),
66                self.y_data.as_ptr(),
67                count,
68                self.flags.bits() as i32,
69                self.offset,
70                self.stride,
71            );
72        })
73    }
74
75    fn label(&self) -> &str {
76        self.label
77    }
78}
79
80/// Simple scatter plot for quick plotting without builder pattern
81pub struct SimpleScatterPlot<'a> {
82    label: &'a str,
83    values: &'a [f64],
84    x_scale: f64,
85    x_start: f64,
86}
87
88impl<'a> SimpleScatterPlot<'a> {
89    /// Create a simple scatter plot with Y values only (X will be indices)
90    pub fn new(label: &'a str, values: &'a [f64]) -> Self {
91        Self {
92            label,
93            values,
94            x_scale: 1.0,
95            x_start: 0.0,
96        }
97    }
98
99    /// Set X scale factor
100    pub fn with_x_scale(mut self, scale: f64) -> Self {
101        self.x_scale = scale;
102        self
103    }
104
105    /// Set X start value
106    pub fn with_x_start(mut self, start: f64) -> Self {
107        self.x_start = start;
108        self
109    }
110}
111
112impl<'a> Plot for SimpleScatterPlot<'a> {
113    fn plot(&self) {
114        if self.values.is_empty() {
115            return;
116        }
117        let Ok(count) = i32::try_from(self.values.len()) else {
118            return;
119        };
120
121        // Create temporary X data
122        let x_data: Vec<f64> = (0..self.values.len())
123            .map(|i| self.x_start + i as f64 * self.x_scale)
124            .collect();
125
126        with_plot_str_or_empty(self.label, |label_ptr| unsafe {
127            sys::ImPlot_PlotScatter_doublePtrdoublePtr(
128                label_ptr,
129                x_data.as_ptr(),
130                self.values.as_ptr(),
131                count,
132                0,
133                0,
134                std::mem::size_of::<f64>() as i32,
135            );
136        })
137    }
138
139    fn label(&self) -> &str {
140        self.label
141    }
142}
143
144/// Convenience functions for quick scatter plotting
145impl<'ui> crate::PlotUi<'ui> {
146    /// Plot a scatter plot with X and Y data
147    pub fn scatter_plot(
148        &self,
149        label: &str,
150        x_data: &[f64],
151        y_data: &[f64],
152    ) -> Result<(), PlotError> {
153        let plot = ScatterPlot::new(label, x_data, y_data);
154        plot.validate()?;
155        plot.plot();
156        Ok(())
157    }
158
159    /// Plot a simple scatter plot with Y values only (X will be indices)
160    pub fn simple_scatter_plot(&self, label: &str, values: &[f64]) -> Result<(), PlotError> {
161        if values.is_empty() {
162            return Err(PlotError::EmptyData);
163        }
164        let plot = SimpleScatterPlot::new(label, values);
165        plot.plot();
166        Ok(())
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn test_scatter_plot_creation() {
176        let x_data = [1.0, 2.0, 3.0, 4.0];
177        let y_data = [1.0, 4.0, 2.0, 3.0];
178
179        let plot = ScatterPlot::new("test", &x_data, &y_data);
180        assert_eq!(plot.label(), "test");
181        assert!(plot.validate().is_ok());
182    }
183
184    #[test]
185    fn test_scatter_plot_validation() {
186        let x_data = [1.0, 2.0, 3.0];
187        let y_data = [1.0, 4.0]; // Different length
188
189        let plot = ScatterPlot::new("test", &x_data, &y_data);
190        assert!(plot.validate().is_err());
191    }
192
193    #[test]
194    fn test_simple_scatter_plot() {
195        let values = [1.0, 2.0, 3.0, 4.0];
196        let plot = SimpleScatterPlot::new("test", &values);
197        assert_eq!(plot.label(), "test");
198    }
199}