Skip to main content

dear_implot/plots/
line.rs

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