dear_implot/plots/
mod.rs

1//! Modular plot types for ImPlot
2//!
3//! This module provides a modular approach to different plot types,
4//! each with their own builder pattern and configuration options.
5
6pub mod bar;
7pub mod bar_groups;
8pub mod digital;
9pub mod dummy;
10pub mod error_bars;
11pub mod heatmap;
12pub mod histogram;
13pub mod image;
14pub mod inf_lines;
15pub mod line;
16pub mod pie;
17pub mod scatter;
18pub mod shaded;
19pub mod stairs;
20pub mod stems;
21pub mod text;
22
23use dear_imgui_rs::{with_scratch_txt, with_scratch_txt_slice, with_scratch_txt_slice_with_opt};
24use std::os::raw::c_char;
25
26// Re-export all plot types for convenience
27pub use bar::*;
28pub use bar_groups::*;
29pub use digital::*;
30pub use dummy::*;
31pub use error_bars::*;
32pub use heatmap::*;
33pub use histogram::*;
34pub use image::*;
35pub use inf_lines::*;
36pub use line::*;
37pub use pie::*;
38pub use scatter::*;
39pub use shaded::*;
40pub use stairs::*;
41pub use stems::*;
42pub use text::*;
43
44/// Common trait for all plot types
45pub trait Plot {
46    /// Plot this element
47    fn plot(&self);
48
49    /// Get the label for this plot
50    fn label(&self) -> &str;
51}
52
53/// Common trait for plot data validation
54pub trait PlotData {
55    /// Get the label for this plot
56    fn label(&self) -> &str;
57
58    /// Get the length of the data
59    fn data_len(&self) -> usize;
60
61    /// Check if the data is empty
62    fn is_empty(&self) -> bool {
63        self.data_len() == 0
64    }
65
66    /// Validate the data for plotting
67    fn validate(&self) -> Result<(), PlotError> {
68        if self.is_empty() {
69            Err(PlotError::EmptyData)
70        } else {
71            Ok(())
72        }
73    }
74}
75
76/// Errors that can occur during plotting
77#[derive(Debug, Clone, PartialEq)]
78pub enum PlotError {
79    /// Data arrays have mismatched lengths
80    DataLengthMismatch { x_len: usize, y_len: usize },
81    /// Data is empty
82    EmptyData,
83    /// Invalid parameter value or data
84    InvalidData(String),
85    /// String conversion error
86    StringConversion(String),
87    /// Plot creation failed
88    PlotCreationFailed(String),
89}
90
91impl std::fmt::Display for PlotError {
92    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93        match self {
94            PlotError::DataLengthMismatch { x_len, y_len } => {
95                write!(
96                    f,
97                    "Data length mismatch: x has {} elements, y has {} elements",
98                    x_len, y_len
99                )
100            }
101            PlotError::EmptyData => write!(f, "Data is empty"),
102            PlotError::InvalidData(msg) => write!(f, "Invalid data: {}", msg),
103            PlotError::StringConversion(msg) => write!(f, "String conversion error: {}", msg),
104            PlotError::PlotCreationFailed(msg) => write!(f, "Plot creation failed: {}", msg),
105        }
106    }
107}
108
109impl std::error::Error for PlotError {}
110
111// Note: PlotData trait is implemented by individual plot types
112// rather than raw data types, since each plot needs its own label
113
114/// Helper function to validate data length consistency for slices
115pub fn validate_data_lengths<T, U>(data1: &[T], data2: &[U]) -> Result<(), PlotError> {
116    if data1.is_empty() || data2.is_empty() {
117        return Err(PlotError::EmptyData);
118    }
119
120    if data1.len() != data2.len() {
121        return Err(PlotError::DataLengthMismatch {
122            x_len: data1.len(),
123            y_len: data2.len(),
124        });
125    }
126
127    Ok(())
128}
129
130pub(crate) fn with_plot_str<R>(s: &str, f: impl FnOnce(*const c_char) -> R) -> Option<R> {
131    if s.contains('\0') {
132        None
133    } else {
134        Some(with_scratch_txt(s, f))
135    }
136}
137
138pub(crate) fn with_plot_str_or_empty<R>(s: &str, f: impl FnOnce(*const c_char) -> R) -> R {
139    let s = if s.contains('\0') { "" } else { s };
140    with_scratch_txt(s, f)
141}
142
143pub(crate) fn with_plot_str_slice<R>(txts: &[&str], f: impl FnOnce(&[*const c_char]) -> R) -> R {
144    let cleaned: Vec<&str> = txts
145        .iter()
146        .map(|&s| if s.contains('\0') { "" } else { s })
147        .collect();
148    with_scratch_txt_slice(&cleaned, f)
149}
150
151pub(crate) fn with_plot_str_slice_with_opt<R>(
152    txts: &[&str],
153    txt_opt: Option<&str>,
154    f: impl FnOnce(&[*const c_char], *const c_char) -> R,
155) -> R {
156    let cleaned: Vec<&str> = txts
157        .iter()
158        .map(|&s| if s.contains('\0') { "" } else { s })
159        .collect();
160    let txt_opt = txt_opt.filter(|s| !s.contains('\0'));
161    with_scratch_txt_slice_with_opt(&cleaned, txt_opt, f)
162}
163
164/// Universal plot builder that can create any plot type
165pub struct PlotBuilder<'a> {
166    plot_type: PlotType<'a>,
167}
168
169/// Enum representing different plot types
170pub enum PlotType<'a> {
171    Line {
172        label: &'a str,
173        x_data: &'a [f64],
174        y_data: &'a [f64],
175    },
176    Scatter {
177        label: &'a str,
178        x_data: &'a [f64],
179        y_data: &'a [f64],
180    },
181    Bar {
182        label: &'a str,
183        values: &'a [f64],
184        width: f64,
185    },
186    Histogram {
187        label: &'a str,
188        values: &'a [f64],
189        bins: i32,
190    },
191    Heatmap {
192        label: &'a str,
193        values: &'a [f64],
194        rows: usize,
195        cols: usize,
196    },
197    PieChart {
198        labels: Vec<&'a str>,
199        values: &'a [f64],
200        center: (f64, f64),
201        radius: f64,
202    },
203}
204
205impl<'a> PlotBuilder<'a> {
206    /// Create a line plot
207    pub fn line(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
208        Self {
209            plot_type: PlotType::Line {
210                label,
211                x_data,
212                y_data,
213            },
214        }
215    }
216
217    /// Create a scatter plot
218    pub fn scatter(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
219        Self {
220            plot_type: PlotType::Scatter {
221                label,
222                x_data,
223                y_data,
224            },
225        }
226    }
227
228    /// Create a bar plot
229    pub fn bar(label: &'a str, values: &'a [f64]) -> Self {
230        Self {
231            plot_type: PlotType::Bar {
232                label,
233                values,
234                width: 0.67,
235            },
236        }
237    }
238
239    /// Create a histogram
240    pub fn histogram(label: &'a str, values: &'a [f64]) -> Self {
241        Self {
242            plot_type: PlotType::Histogram {
243                label,
244                values,
245                bins: crate::BinMethod::Sturges as i32,
246            },
247        }
248    }
249
250    /// Create a heatmap
251    pub fn heatmap(label: &'a str, values: &'a [f64], rows: usize, cols: usize) -> Self {
252        Self {
253            plot_type: PlotType::Heatmap {
254                label,
255                values,
256                rows,
257                cols,
258            },
259        }
260    }
261
262    /// Create a pie chart
263    pub fn pie_chart(
264        labels: Vec<&'a str>,
265        values: &'a [f64],
266        center: (f64, f64),
267        radius: f64,
268    ) -> Self {
269        Self {
270            plot_type: PlotType::PieChart {
271                labels,
272                values,
273                center,
274                radius,
275            },
276        }
277    }
278
279    /// Build and plot the chart
280    pub fn build(self) -> Result<(), PlotError> {
281        match self.plot_type {
282            PlotType::Line {
283                label,
284                x_data,
285                y_data,
286            } => {
287                let plot = line::LinePlot::new(label, x_data, y_data);
288                plot.validate()?;
289                plot.plot();
290            }
291            PlotType::Scatter {
292                label,
293                x_data,
294                y_data,
295            } => {
296                let plot = scatter::ScatterPlot::new(label, x_data, y_data);
297                plot.validate()?;
298                plot.plot();
299            }
300            PlotType::Bar {
301                label,
302                values,
303                width,
304            } => {
305                let plot = bar::BarPlot::new(label, values).with_bar_size(width);
306                plot.validate()?;
307                plot.plot();
308            }
309            PlotType::Histogram {
310                label,
311                values,
312                bins,
313            } => {
314                let plot = histogram::HistogramPlot::new(label, values).with_bins(bins);
315                plot.validate()?;
316                plot.plot();
317            }
318            PlotType::Heatmap {
319                label,
320                values,
321                rows,
322                cols,
323            } => {
324                let plot = heatmap::HeatmapPlot::new(label, values, rows, cols);
325                plot.validate()?;
326                plot.plot();
327            }
328            PlotType::PieChart {
329                labels,
330                values,
331                center,
332                radius,
333            } => {
334                let plot = pie::PieChartPlot::new(labels, values, center.0, center.1, radius);
335                plot.validate()?;
336                plot.plot();
337            }
338        }
339        Ok(())
340    }
341}