Skip to main content

dear_implot/plots/
line.rs

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