Skip to main content

dear_implot/plots/
histogram.rs

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