1use super::{
4 Plot, PlotDataLayout, PlotError, PlotItemStyle, plot_spec_with_style, with_plot_str_or_empty,
5};
6use crate::sys;
7use crate::{HistogramBins, HistogramFlags, ItemFlags};
8
9pub struct HistogramPlot<'a> {
11 label: &'a str,
12 values: &'a [f64],
13 style: PlotItemStyle,
14 bins: HistogramBins,
15 bar_scale: f64,
16 range: Option<sys::ImPlotRange>,
17 flags: HistogramFlags,
18 item_flags: ItemFlags,
19}
20
21impl<'a> super::PlotItemStyled for HistogramPlot<'a> {
22 fn style_mut(&mut self) -> &mut PlotItemStyle {
23 &mut self.style
24 }
25}
26
27impl<'a> HistogramPlot<'a> {
28 pub fn new(label: &'a str, values: &'a [f64]) -> Self {
30 Self {
31 label,
32 values,
33 style: PlotItemStyle::default(),
34 bins: HistogramBins::DEFAULT,
35 bar_scale: 1.0,
36 range: None, flags: HistogramFlags::NONE,
38 item_flags: ItemFlags::NONE,
39 }
40 }
41
42 pub fn with_bins(mut self, bins: impl Into<HistogramBins>) -> Self {
44 self.bins = bins.into();
45 self
46 }
47
48 pub fn with_bar_scale(mut self, scale: f64) -> Self {
50 self.bar_scale = scale;
51 self
52 }
53
54 pub fn with_range(mut self, min: f64, max: f64) -> Self {
57 self.range = Some(sys::ImPlotRange { Min: min, Max: max });
58 self
59 }
60
61 pub fn with_range_struct(mut self, range: sys::ImPlotRange) -> Self {
63 self.range = Some(range);
64 self
65 }
66
67 pub fn with_flags(mut self, flags: HistogramFlags) -> Self {
69 self.flags = flags;
70 self
71 }
72
73 pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
75 self.item_flags = flags;
76 self
77 }
78
79 pub fn horizontal(mut self) -> Self {
81 self.flags |= HistogramFlags::HORIZONTAL;
82 self
83 }
84
85 pub fn cumulative(mut self) -> Self {
87 self.flags |= HistogramFlags::CUMULATIVE;
88 self
89 }
90
91 pub fn density(mut self) -> Self {
93 self.flags |= HistogramFlags::DENSITY;
94 self
95 }
96
97 pub fn no_outliers(mut self) -> Self {
99 self.flags |= HistogramFlags::NO_OUTLIERS;
100 self
101 }
102
103 pub fn validate(&self) -> Result<(), PlotError> {
105 if self.values.is_empty() {
106 return Err(PlotError::EmptyData);
107 }
108 Ok(())
109 }
110}
111
112impl<'a> Plot for HistogramPlot<'a> {
113 fn plot(&self, plot_ui: &crate::PlotUi<'_>) {
114 if self.validate().is_err() {
115 return;
116 }
117 let Ok(count) = i32::try_from(self.values.len()) else {
118 return;
119 };
120
121 let range = if let Some(range) = &self.range {
122 *range
123 } else {
124 sys::ImPlotRange { Min: 0.0, Max: 0.0 }
125 };
126
127 let _guard = plot_ui.bind();
128 with_plot_str_or_empty(self.label, |label_ptr| unsafe {
129 let spec = plot_spec_with_style(
130 self.style,
131 self.flags.bits() | self.item_flags.bits(),
132 PlotDataLayout::DEFAULT,
133 );
134 sys::ImPlot_PlotHistogram_doublePtr(
135 label_ptr,
136 self.values.as_ptr(),
137 count,
138 self.bins.raw("HistogramPlot::plot()"),
139 self.bar_scale,
140 range,
141 spec,
142 );
143 })
144 }
145
146 fn label(&self) -> &str {
147 self.label
148 }
149}
150
151pub struct Histogram2DPlot<'a> {
153 label: &'a str,
154 x_values: &'a [f64],
155 y_values: &'a [f64],
156 style: PlotItemStyle,
157 x_bins: HistogramBins,
158 y_bins: HistogramBins,
159 range: Option<sys::ImPlotRect>,
160 flags: HistogramFlags,
161 item_flags: ItemFlags,
162}
163
164impl<'a> super::PlotItemStyled for Histogram2DPlot<'a> {
165 fn style_mut(&mut self) -> &mut PlotItemStyle {
166 &mut self.style
167 }
168}
169
170impl<'a> Histogram2DPlot<'a> {
171 pub fn new(label: &'a str, x_values: &'a [f64], y_values: &'a [f64]) -> Self {
173 Self {
174 label,
175 x_values,
176 y_values,
177 style: PlotItemStyle::default(),
178 x_bins: HistogramBins::DEFAULT,
179 y_bins: HistogramBins::DEFAULT,
180 range: None, flags: HistogramFlags::NONE,
182 item_flags: ItemFlags::NONE,
183 }
184 }
185
186 pub fn with_bins(
188 mut self,
189 x_bins: impl Into<HistogramBins>,
190 y_bins: impl Into<HistogramBins>,
191 ) -> Self {
192 self.x_bins = x_bins.into();
193 self.y_bins = y_bins.into();
194 self
195 }
196
197 pub fn with_range(mut self, x_min: f64, x_max: f64, y_min: f64, y_max: f64) -> Self {
199 self.range = Some(sys::ImPlotRect {
200 X: sys::ImPlotRange {
201 Min: x_min,
202 Max: x_max,
203 },
204 Y: sys::ImPlotRange {
205 Min: y_min,
206 Max: y_max,
207 },
208 });
209 self
210 }
211
212 pub fn with_range_struct(mut self, range: sys::ImPlotRect) -> Self {
214 self.range = Some(range);
215 self
216 }
217
218 pub fn with_flags(mut self, flags: HistogramFlags) -> Self {
220 self.flags = flags;
221 self
222 }
223
224 pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
226 self.item_flags = flags;
227 self
228 }
229
230 pub fn density(mut self) -> Self {
232 self.flags |= HistogramFlags::DENSITY;
233 self
234 }
235
236 pub fn no_outliers(mut self) -> Self {
238 self.flags |= HistogramFlags::NO_OUTLIERS;
239 self
240 }
241
242 pub fn column_major(mut self) -> Self {
244 self.flags |= HistogramFlags::COL_MAJOR;
245 self
246 }
247
248 pub fn validate(&self) -> Result<(), PlotError> {
250 super::validate_data_lengths(self.x_values, self.y_values)
251 }
252}
253
254impl<'a> Plot for Histogram2DPlot<'a> {
255 fn plot(&self, plot_ui: &crate::PlotUi<'_>) {
256 if self.validate().is_err() {
257 return;
258 }
259 let Ok(count) = i32::try_from(self.x_values.len()) else {
260 return;
261 };
262
263 let range = if let Some(range) = &self.range {
264 *range
265 } else {
266 sys::ImPlotRect {
267 X: sys::ImPlotRange { Min: 0.0, Max: 0.0 },
268 Y: sys::ImPlotRange { Min: 0.0, Max: 0.0 },
269 }
270 };
271
272 let _guard = plot_ui.bind();
273 with_plot_str_or_empty(self.label, |label_ptr| unsafe {
274 let spec = plot_spec_with_style(
275 self.style,
276 self.flags.bits() | self.item_flags.bits(),
277 PlotDataLayout::DEFAULT,
278 );
279 sys::ImPlot_PlotHistogram2D_doublePtr(
280 label_ptr,
281 self.x_values.as_ptr(),
282 self.y_values.as_ptr(),
283 count,
284 self.x_bins.raw("Histogram2DPlot::plot()"),
285 self.y_bins.raw("Histogram2DPlot::plot()"),
286 range,
287 spec,
288 );
289 })
290 }
291
292 fn label(&self) -> &str {
293 self.label
294 }
295}
296
297impl<'ui> crate::PlotUi<'ui> {
299 pub fn histogram_plot(&self, label: &str, values: &[f64]) -> Result<(), PlotError> {
301 let plot = HistogramPlot::new(label, values);
302 plot.validate()?;
303 plot.plot(self);
304 Ok(())
305 }
306
307 pub fn histogram_plot_with_bins(
309 &self,
310 label: &str,
311 values: &[f64],
312 bins: impl Into<HistogramBins>,
313 ) -> Result<(), PlotError> {
314 let plot = HistogramPlot::new(label, values).with_bins(bins);
315 plot.validate()?;
316 plot.plot(self);
317 Ok(())
318 }
319
320 pub fn histogram_2d_plot(
322 &self,
323 label: &str,
324 x_values: &[f64],
325 y_values: &[f64],
326 ) -> Result<(), PlotError> {
327 let plot = Histogram2DPlot::new(label, x_values, y_values);
328 plot.validate()?;
329 plot.plot(self);
330 Ok(())
331 }
332
333 pub fn histogram_2d_plot_with_bins(
335 &self,
336 label: &str,
337 x_values: &[f64],
338 y_values: &[f64],
339 x_bins: impl Into<HistogramBins>,
340 y_bins: impl Into<HistogramBins>,
341 ) -> Result<(), PlotError> {
342 let plot = Histogram2DPlot::new(label, x_values, y_values).with_bins(x_bins, y_bins);
343 plot.validate()?;
344 plot.plot(self);
345 Ok(())
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::{Histogram2DPlot, HistogramPlot};
352 use crate::{BinMethod, HistogramBins};
353
354 #[test]
355 fn histogram_bins_distinguish_counts_from_methods() {
356 assert_eq!(HistogramBins::from(8usize).raw("test"), 8);
357 assert_eq!(
358 HistogramBins::from(BinMethod::Rice).raw("test"),
359 BinMethod::Rice as i32
360 );
361 assert_eq!(
362 HistogramBins::DEFAULT.raw("test"),
363 BinMethod::Sturges as i32
364 );
365 }
366
367 #[test]
368 #[should_panic(expected = "test bin count must be positive")]
369 fn histogram_bins_reject_zero_counts_before_ffi() {
370 let _ = HistogramBins::from(0usize).raw("test");
371 }
372
373 #[test]
374 #[should_panic(expected = "test bin count exceeded ImPlot's i32 range")]
375 fn histogram_bins_reject_oversized_counts_before_ffi() {
376 let _ = HistogramBins::from(i32::MAX as usize + 1).raw("test");
377 }
378
379 #[test]
380 fn histogram_builders_accept_typed_bins() {
381 let values = [1.0, 2.0, 3.0];
382 let _ = HistogramPlot::new("hist", &values).with_bins(8usize);
383 let _ = HistogramPlot::new("hist", &values).with_bins(BinMethod::Scott);
384 let _ = Histogram2DPlot::new("hist2d", &values, &values).with_bins(4usize, BinMethod::Rice);
385 }
386}