dear_implot/plots/
line.rs

1//! Line plot implementation
2
3use super::{Plot, PlotError, safe_cstring, validate_data_lengths};
4use crate::{LineFlags, sys};
5
6/// Builder for line plots with extensive customization options
7pub struct LinePlot<'a> {
8    label: &'a str,
9    x_data: &'a [f64],
10    y_data: &'a [f64],
11    flags: LineFlags,
12    offset: i32,
13    stride: i32,
14}
15
16impl<'a> LinePlot<'a> {
17    /// Create a new line 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: LineFlags::NONE,
24            offset: 0,
25            stride: std::mem::size_of::<f64>() as i32,
26        }
27    }
28
29    /// Set line flags for customization
30    pub fn with_flags(mut self, flags: LineFlags) -> 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 LinePlot<'a> {
54    fn plot(&self) {
55        if self.validate().is_err() {
56            return; // Skip plotting if data is invalid
57        }
58
59        let label_cstr = safe_cstring(self.label);
60
61        unsafe {
62            sys::ImPlot_PlotLine_doublePtrdoublePtr(
63                label_cstr.as_ptr(),
64                self.x_data.as_ptr(),
65                self.y_data.as_ptr(),
66                self.x_data.len() as i32,
67                self.flags.bits() as i32,
68                self.offset,
69                self.stride,
70            );
71        }
72    }
73
74    fn label(&self) -> &str {
75        self.label
76    }
77}
78
79/// Simple line plot for quick plotting without builder pattern
80pub struct SimpleLinePlot<'a> {
81    label: &'a str,
82    values: &'a [f64],
83    x_scale: f64,
84    x_start: f64,
85}
86
87impl<'a> SimpleLinePlot<'a> {
88    /// Create a simple line plot with Y values only (X will be indices)
89    pub fn new(label: &'a str, values: &'a [f64]) -> Self {
90        Self {
91            label,
92            values,
93            x_scale: 1.0,
94            x_start: 0.0,
95        }
96    }
97
98    /// Set X scale factor
99    pub fn with_x_scale(mut self, scale: f64) -> Self {
100        self.x_scale = scale;
101        self
102    }
103
104    /// Set X start value
105    pub fn with_x_start(mut self, start: f64) -> Self {
106        self.x_start = start;
107        self
108    }
109}
110
111impl<'a> Plot for SimpleLinePlot<'a> {
112    fn plot(&self) {
113        if self.values.is_empty() {
114            return;
115        }
116
117        let label_cstr = safe_cstring(self.label);
118
119        // For simple line plots, we use the single-array version
120        // This would require a wrapper function in the sys crate
121        // For now, we'll 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        unsafe {
127            sys::ImPlot_PlotLine_doublePtrdoublePtr(
128                label_cstr.as_ptr(),
129                x_data.as_ptr(),
130                self.values.as_ptr(),
131                self.values.len() as i32,
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 line plotting
145impl<'ui> crate::PlotUi<'ui> {
146    /// Plot a line with X and Y data
147    pub fn line_plot(&self, label: &str, x_data: &[f64], y_data: &[f64]) -> Result<(), PlotError> {
148        let plot = LinePlot::new(label, x_data, y_data);
149        plot.validate()?;
150        plot.plot();
151        Ok(())
152    }
153
154    /// Plot a simple line with Y values only (X will be indices)
155    pub fn simple_line_plot(&self, label: &str, values: &[f64]) -> Result<(), PlotError> {
156        if values.is_empty() {
157            return Err(PlotError::EmptyData);
158        }
159        let plot = SimpleLinePlot::new(label, values);
160        plot.plot();
161        Ok(())
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn test_line_plot_creation() {
171        let x_data = [1.0, 2.0, 3.0, 4.0];
172        let y_data = [1.0, 4.0, 2.0, 3.0];
173
174        let plot = LinePlot::new("test", &x_data, &y_data);
175        assert_eq!(plot.label(), "test");
176        assert!(plot.validate().is_ok());
177    }
178
179    #[test]
180    fn test_line_plot_validation() {
181        let x_data = [1.0, 2.0, 3.0];
182        let y_data = [1.0, 4.0]; // Different length
183
184        let plot = LinePlot::new("test", &x_data, &y_data);
185        assert!(plot.validate().is_err());
186    }
187
188    #[test]
189    fn test_simple_line_plot() {
190        let values = [1.0, 2.0, 3.0, 4.0];
191        let plot = SimpleLinePlot::new("test", &values);
192        assert_eq!(plot.label(), "test");
193    }
194}