dear_implot/plots/
histogram.rs

1//! Histogram plot implementation
2
3use super::{Plot, PlotError, safe_cstring};
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
105        let label_cstr = safe_cstring(self.label);
106
107        let range = if let Some(range) = &self.range {
108            *range
109        } else {
110            sys::ImPlotRange { Min: 0.0, Max: 0.0 }
111        };
112
113        unsafe {
114            sys::ImPlot_PlotHistogram_doublePtr(
115                label_cstr.as_ptr(),
116                self.values.as_ptr(),
117                self.values.len() as i32,
118                self.bins,
119                self.bar_scale,
120                range,
121                self.flags.bits() as i32,
122            );
123        }
124    }
125
126    fn label(&self) -> &str {
127        self.label
128    }
129}
130
131/// Builder for 2D histogram plots (bivariate histograms as heatmaps)
132pub struct Histogram2DPlot<'a> {
133    label: &'a str,
134    x_values: &'a [f64],
135    y_values: &'a [f64],
136    x_bins: i32,
137    y_bins: i32,
138    range: Option<sys::ImPlotRect>,
139    flags: HistogramFlags,
140}
141
142impl<'a> Histogram2DPlot<'a> {
143    /// Create a new 2D histogram plot with the given label and data
144    pub fn new(label: &'a str, x_values: &'a [f64], y_values: &'a [f64]) -> Self {
145        Self {
146            label,
147            x_values,
148            y_values,
149            x_bins: BinMethod::Sturges as i32,
150            y_bins: BinMethod::Sturges as i32,
151            range: None, // Auto-range
152            flags: HistogramFlags::NONE,
153        }
154    }
155
156    /// Set the number of bins for both X and Y axes
157    pub fn with_bins(mut self, x_bins: i32, y_bins: i32) -> Self {
158        self.x_bins = x_bins;
159        self.y_bins = y_bins;
160        self
161    }
162
163    /// Set the data range for binning
164    pub fn with_range(mut self, x_min: f64, x_max: f64, y_min: f64, y_max: f64) -> Self {
165        self.range = Some(sys::ImPlotRect {
166            X: sys::ImPlotRange {
167                Min: x_min,
168                Max: x_max,
169            },
170            Y: sys::ImPlotRange {
171                Min: y_min,
172                Max: y_max,
173            },
174        });
175        self
176    }
177
178    /// Set the data range using ImPlotRect
179    pub fn with_range_struct(mut self, range: sys::ImPlotRect) -> Self {
180        self.range = Some(range);
181        self
182    }
183
184    /// Set histogram flags for customization
185    pub fn with_flags(mut self, flags: HistogramFlags) -> Self {
186        self.flags = flags;
187        self
188    }
189
190    /// Normalize the histogram to show density
191    pub fn density(mut self) -> Self {
192        self.flags |= HistogramFlags::DENSITY;
193        self
194    }
195
196    /// Exclude outliers from normalization
197    pub fn no_outliers(mut self) -> Self {
198        self.flags |= HistogramFlags::NO_OUTLIERS;
199        self
200    }
201
202    /// Use column-major data ordering
203    pub fn column_major(mut self) -> Self {
204        self.flags |= HistogramFlags::COL_MAJOR;
205        self
206    }
207
208    /// Validate the plot data
209    pub fn validate(&self) -> Result<(), PlotError> {
210        super::validate_data_lengths(self.x_values, self.y_values)
211    }
212}
213
214impl<'a> Plot for Histogram2DPlot<'a> {
215    fn plot(&self) {
216        if self.validate().is_err() {
217            return;
218        }
219
220        let label_cstr = safe_cstring(self.label);
221
222        let range = if let Some(range) = &self.range {
223            *range
224        } else {
225            sys::ImPlotRect {
226                X: sys::ImPlotRange { Min: 0.0, Max: 0.0 },
227                Y: sys::ImPlotRange { Min: 0.0, Max: 0.0 },
228            }
229        };
230
231        unsafe {
232            sys::ImPlot_PlotHistogram2D_doublePtr(
233                label_cstr.as_ptr(),
234                self.x_values.as_ptr(),
235                self.y_values.as_ptr(),
236                self.x_values.len() as i32,
237                self.x_bins,
238                self.y_bins,
239                range,
240                self.flags.bits() as i32,
241            );
242        }
243    }
244
245    fn label(&self) -> &str {
246        self.label
247    }
248}
249
250/// Convenience functions for quick histogram plotting
251impl<'ui> crate::PlotUi<'ui> {
252    /// Plot a 1D histogram with default settings
253    pub fn histogram_plot(&self, label: &str, values: &[f64]) -> Result<(), PlotError> {
254        let plot = HistogramPlot::new(label, values);
255        plot.validate()?;
256        plot.plot();
257        Ok(())
258    }
259
260    /// Plot a 1D histogram with custom bin count
261    pub fn histogram_plot_with_bins(
262        &self,
263        label: &str,
264        values: &[f64],
265        bins: i32,
266    ) -> Result<(), PlotError> {
267        let plot = HistogramPlot::new(label, values).with_bins(bins);
268        plot.validate()?;
269        plot.plot();
270        Ok(())
271    }
272
273    /// Plot a 2D histogram (bivariate histogram as heatmap)
274    pub fn histogram_2d_plot(
275        &self,
276        label: &str,
277        x_values: &[f64],
278        y_values: &[f64],
279    ) -> Result<(), PlotError> {
280        let plot = Histogram2DPlot::new(label, x_values, y_values);
281        plot.validate()?;
282        plot.plot();
283        Ok(())
284    }
285
286    /// Plot a 2D histogram with custom bin counts
287    pub fn histogram_2d_plot_with_bins(
288        &self,
289        label: &str,
290        x_values: &[f64],
291        y_values: &[f64],
292        x_bins: i32,
293        y_bins: i32,
294    ) -> Result<(), PlotError> {
295        let plot = Histogram2DPlot::new(label, x_values, y_values).with_bins(x_bins, y_bins);
296        plot.validate()?;
297        plot.plot();
298        Ok(())
299    }
300}