Skip to main content

dear_implot/plots/
histogram.rs

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