Skip to main content

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