dear_implot/plots/
histogram.rs

1//! Histogram plot implementation
2
3use super::{Plot, PlotError, with_plot_str_or_empty};
4use crate::sys;
5use crate::{BinMethod, HistogramFlags};
6
7/// Builder for 1D histogram plots
8pub struct HistogramPlot<'a> {
9    label: &'a str,
10    values: &'a [f64],
11    bins: i32,
12    bar_scale: f64,
13    range: Option<sys::ImPlotRange>,
14    flags: HistogramFlags,
15}
16
17impl<'a> HistogramPlot<'a> {
18    /// Create a new histogram plot with the given label and data
19    pub fn new(label: &'a str, values: &'a [f64]) -> Self {
20        Self {
21            label,
22            values,
23            bins: BinMethod::Sturges as i32,
24            bar_scale: 1.0,
25            range: None, // Auto-range
26            flags: HistogramFlags::NONE,
27        }
28    }
29
30    /// Set the number of bins (positive integer) or binning method (negative value)
31    /// Common binning methods:
32    /// - ImPlotBin_Sqrt = -1
33    /// - ImPlotBin_Sturges = -2  (default)
34    /// - ImPlotBin_Rice = -3
35    /// - ImPlotBin_Scott = -4
36    pub fn with_bins(mut self, bins: i32) -> Self {
37        self.bins = bins;
38        self
39    }
40
41    /// Set the bar scale factor
42    pub fn with_bar_scale(mut self, scale: f64) -> Self {
43        self.bar_scale = scale;
44        self
45    }
46
47    /// Set the data range for binning
48    /// Values outside this range will be treated as outliers
49    pub fn with_range(mut self, min: f64, max: f64) -> Self {
50        self.range = Some(sys::ImPlotRange { Min: min, Max: max });
51        self
52    }
53
54    /// Set the data range using ImPlotRange
55    pub fn with_range_struct(mut self, range: sys::ImPlotRange) -> Self {
56        self.range = Some(range);
57        self
58    }
59
60    /// Set histogram flags for customization
61    pub fn with_flags(mut self, flags: HistogramFlags) -> Self {
62        self.flags = flags;
63        self
64    }
65
66    /// Make the histogram horizontal instead of vertical
67    pub fn horizontal(mut self) -> Self {
68        self.flags |= HistogramFlags::HORIZONTAL;
69        self
70    }
71
72    /// Make the histogram cumulative
73    pub fn cumulative(mut self) -> Self {
74        self.flags |= HistogramFlags::CUMULATIVE;
75        self
76    }
77
78    /// Normalize the histogram to show density (PDF)
79    pub fn density(mut self) -> Self {
80        self.flags |= HistogramFlags::DENSITY;
81        self
82    }
83
84    /// Exclude outliers from normalization
85    pub fn no_outliers(mut self) -> Self {
86        self.flags |= HistogramFlags::NO_OUTLIERS;
87        self
88    }
89
90    /// Validate the plot data
91    pub fn validate(&self) -> Result<(), PlotError> {
92        if self.values.is_empty() {
93            return Err(PlotError::EmptyData);
94        }
95        Ok(())
96    }
97}
98
99impl<'a> Plot for HistogramPlot<'a> {
100    fn plot(&self) {
101        if self.validate().is_err() {
102            return;
103        }
104        let Ok(count) = i32::try_from(self.values.len()) else {
105            return;
106        };
107
108        let range = if let Some(range) = &self.range {
109            *range
110        } else {
111            sys::ImPlotRange { Min: 0.0, Max: 0.0 }
112        };
113
114        with_plot_str_or_empty(self.label, |label_ptr| unsafe {
115            sys::ImPlot_PlotHistogram_doublePtr(
116                label_ptr,
117                self.values.as_ptr(),
118                count,
119                self.bins,
120                self.bar_scale,
121                range,
122                self.flags.bits() as i32,
123            );
124        })
125    }
126
127    fn label(&self) -> &str {
128        self.label
129    }
130}
131
132/// Builder for 2D histogram plots (bivariate histograms as heatmaps)
133pub struct Histogram2DPlot<'a> {
134    label: &'a str,
135    x_values: &'a [f64],
136    y_values: &'a [f64],
137    x_bins: i32,
138    y_bins: i32,
139    range: Option<sys::ImPlotRect>,
140    flags: HistogramFlags,
141}
142
143impl<'a> Histogram2DPlot<'a> {
144    /// Create a new 2D histogram plot with the given label and data
145    pub fn new(label: &'a str, x_values: &'a [f64], y_values: &'a [f64]) -> Self {
146        Self {
147            label,
148            x_values,
149            y_values,
150            x_bins: BinMethod::Sturges as i32,
151            y_bins: BinMethod::Sturges as i32,
152            range: None, // Auto-range
153            flags: HistogramFlags::NONE,
154        }
155    }
156
157    /// Set the number of bins for both X and Y axes
158    pub fn with_bins(mut self, x_bins: i32, y_bins: i32) -> Self {
159        self.x_bins = x_bins;
160        self.y_bins = y_bins;
161        self
162    }
163
164    /// Set the data range for binning
165    pub fn with_range(mut self, x_min: f64, x_max: f64, y_min: f64, y_max: f64) -> Self {
166        self.range = Some(sys::ImPlotRect {
167            X: sys::ImPlotRange {
168                Min: x_min,
169                Max: x_max,
170            },
171            Y: sys::ImPlotRange {
172                Min: y_min,
173                Max: y_max,
174            },
175        });
176        self
177    }
178
179    /// Set the data range using ImPlotRect
180    pub fn with_range_struct(mut self, range: sys::ImPlotRect) -> Self {
181        self.range = Some(range);
182        self
183    }
184
185    /// Set histogram flags for customization
186    pub fn with_flags(mut self, flags: HistogramFlags) -> Self {
187        self.flags = flags;
188        self
189    }
190
191    /// Normalize the histogram to show density
192    pub fn density(mut self) -> Self {
193        self.flags |= HistogramFlags::DENSITY;
194        self
195    }
196
197    /// Exclude outliers from normalization
198    pub fn no_outliers(mut self) -> Self {
199        self.flags |= HistogramFlags::NO_OUTLIERS;
200        self
201    }
202
203    /// Use column-major data ordering
204    pub fn column_major(mut self) -> Self {
205        self.flags |= HistogramFlags::COL_MAJOR;
206        self
207    }
208
209    /// Validate the plot data
210    pub fn validate(&self) -> Result<(), PlotError> {
211        super::validate_data_lengths(self.x_values, self.y_values)
212    }
213}
214
215impl<'a> Plot for Histogram2DPlot<'a> {
216    fn plot(&self) {
217        if self.validate().is_err() {
218            return;
219        }
220        let Ok(count) = i32::try_from(self.x_values.len()) else {
221            return;
222        };
223
224        let range = if let Some(range) = &self.range {
225            *range
226        } else {
227            sys::ImPlotRect {
228                X: sys::ImPlotRange { Min: 0.0, Max: 0.0 },
229                Y: sys::ImPlotRange { Min: 0.0, Max: 0.0 },
230            }
231        };
232
233        with_plot_str_or_empty(self.label, |label_ptr| unsafe {
234            sys::ImPlot_PlotHistogram2D_doublePtr(
235                label_ptr,
236                self.x_values.as_ptr(),
237                self.y_values.as_ptr(),
238                count,
239                self.x_bins,
240                self.y_bins,
241                range,
242                self.flags.bits() as i32,
243            );
244        })
245    }
246
247    fn label(&self) -> &str {
248        self.label
249    }
250}
251
252/// Convenience functions for quick histogram plotting
253impl<'ui> crate::PlotUi<'ui> {
254    /// Plot a 1D histogram with default settings
255    pub fn histogram_plot(&self, label: &str, values: &[f64]) -> Result<(), PlotError> {
256        let plot = HistogramPlot::new(label, values);
257        plot.validate()?;
258        plot.plot();
259        Ok(())
260    }
261
262    /// Plot a 1D histogram with custom bin count
263    pub fn histogram_plot_with_bins(
264        &self,
265        label: &str,
266        values: &[f64],
267        bins: i32,
268    ) -> Result<(), PlotError> {
269        let plot = HistogramPlot::new(label, values).with_bins(bins);
270        plot.validate()?;
271        plot.plot();
272        Ok(())
273    }
274
275    /// Plot a 2D histogram (bivariate histogram as heatmap)
276    pub fn histogram_2d_plot(
277        &self,
278        label: &str,
279        x_values: &[f64],
280        y_values: &[f64],
281    ) -> Result<(), PlotError> {
282        let plot = Histogram2DPlot::new(label, x_values, y_values);
283        plot.validate()?;
284        plot.plot();
285        Ok(())
286    }
287
288    /// Plot a 2D histogram with custom bin counts
289    pub fn histogram_2d_plot_with_bins(
290        &self,
291        label: &str,
292        x_values: &[f64],
293        y_values: &[f64],
294        x_bins: i32,
295        y_bins: i32,
296    ) -> Result<(), PlotError> {
297        let plot = Histogram2DPlot::new(label, x_values, y_values).with_bins(x_bins, y_bins);
298        plot.validate()?;
299        plot.plot();
300        Ok(())
301    }
302}