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) {
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 with_plot_str_or_empty(self.label, |label_ptr| unsafe {
128 let spec = plot_spec_with_style(
129 self.style,
130 self.flags.bits() | self.item_flags.bits(),
131 PlotDataLayout::DEFAULT,
132 );
133 sys::ImPlot_PlotHistogram_doublePtr(
134 label_ptr,
135 self.values.as_ptr(),
136 count,
137 self.bins.raw("HistogramPlot::plot()"),
138 self.bar_scale,
139 range,
140 spec,
141 );
142 })
143 }
144
145 fn label(&self) -> &str {
146 self.label
147 }
148}
149
150pub struct Histogram2DPlot<'a> {
152 label: &'a str,
153 x_values: &'a [f64],
154 y_values: &'a [f64],
155 style: PlotItemStyle,
156 x_bins: HistogramBins,
157 y_bins: HistogramBins,
158 range: Option<sys::ImPlotRect>,
159 flags: HistogramFlags,
160 item_flags: ItemFlags,
161}
162
163impl<'a> super::PlotItemStyled for Histogram2DPlot<'a> {
164 fn style_mut(&mut self) -> &mut PlotItemStyle {
165 &mut self.style
166 }
167}
168
169impl<'a> Histogram2DPlot<'a> {
170 pub fn new(label: &'a str, x_values: &'a [f64], y_values: &'a [f64]) -> Self {
172 Self {
173 label,
174 x_values,
175 y_values,
176 style: PlotItemStyle::default(),
177 x_bins: HistogramBins::DEFAULT,
178 y_bins: HistogramBins::DEFAULT,
179 range: None, flags: HistogramFlags::NONE,
181 item_flags: ItemFlags::NONE,
182 }
183 }
184
185 pub fn with_bins(
187 mut self,
188 x_bins: impl Into<HistogramBins>,
189 y_bins: impl Into<HistogramBins>,
190 ) -> Self {
191 self.x_bins = x_bins.into();
192 self.y_bins = y_bins.into();
193 self
194 }
195
196 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 pub fn with_range_struct(mut self, range: sys::ImPlotRect) -> Self {
213 self.range = Some(range);
214 self
215 }
216
217 pub fn with_flags(mut self, flags: HistogramFlags) -> Self {
219 self.flags = flags;
220 self
221 }
222
223 pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
225 self.item_flags = flags;
226 self
227 }
228
229 pub fn density(mut self) -> Self {
231 self.flags |= HistogramFlags::DENSITY;
232 self
233 }
234
235 pub fn no_outliers(mut self) -> Self {
237 self.flags |= HistogramFlags::NO_OUTLIERS;
238 self
239 }
240
241 pub fn column_major(mut self) -> Self {
243 self.flags |= HistogramFlags::COL_MAJOR;
244 self
245 }
246
247 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 PlotDataLayout::DEFAULT,
276 );
277 sys::ImPlot_PlotHistogram2D_doublePtr(
278 label_ptr,
279 self.x_values.as_ptr(),
280 self.y_values.as_ptr(),
281 count,
282 self.x_bins.raw("Histogram2DPlot::plot()"),
283 self.y_bins.raw("Histogram2DPlot::plot()"),
284 range,
285 spec,
286 );
287 })
288 }
289
290 fn label(&self) -> &str {
291 self.label
292 }
293}
294
295impl<'ui> crate::PlotUi<'ui> {
297 pub fn histogram_plot(&self, label: &str, values: &[f64]) -> Result<(), PlotError> {
299 let plot = HistogramPlot::new(label, values);
300 plot.validate()?;
301 self.bind();
302 plot.plot();
303 Ok(())
304 }
305
306 pub fn histogram_plot_with_bins(
308 &self,
309 label: &str,
310 values: &[f64],
311 bins: impl Into<HistogramBins>,
312 ) -> Result<(), PlotError> {
313 let plot = HistogramPlot::new(label, values).with_bins(bins);
314 plot.validate()?;
315 self.bind();
316 plot.plot();
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 self.bind();
330 plot.plot();
331 Ok(())
332 }
333
334 pub fn histogram_2d_plot_with_bins(
336 &self,
337 label: &str,
338 x_values: &[f64],
339 y_values: &[f64],
340 x_bins: impl Into<HistogramBins>,
341 y_bins: impl Into<HistogramBins>,
342 ) -> Result<(), PlotError> {
343 let plot = Histogram2DPlot::new(label, x_values, y_values).with_bins(x_bins, y_bins);
344 plot.validate()?;
345 self.bind();
346 plot.plot();
347 Ok(())
348 }
349}
350
351#[cfg(test)]
352mod tests {
353 use super::{Histogram2DPlot, HistogramPlot};
354 use crate::{BinMethod, HistogramBins};
355
356 #[test]
357 fn histogram_bins_distinguish_counts_from_methods() {
358 assert_eq!(HistogramBins::from(8usize).raw("test"), 8);
359 assert_eq!(
360 HistogramBins::from(BinMethod::Rice).raw("test"),
361 BinMethod::Rice as i32
362 );
363 assert_eq!(
364 HistogramBins::DEFAULT.raw("test"),
365 BinMethod::Sturges as i32
366 );
367 }
368
369 #[test]
370 #[should_panic(expected = "test bin count must be positive")]
371 fn histogram_bins_reject_zero_counts_before_ffi() {
372 let _ = HistogramBins::from(0usize).raw("test");
373 }
374
375 #[test]
376 #[should_panic(expected = "test bin count exceeded ImPlot's i32 range")]
377 fn histogram_bins_reject_oversized_counts_before_ffi() {
378 let _ = HistogramBins::from(i32::MAX as usize + 1).raw("test");
379 }
380
381 #[test]
382 fn histogram_builders_accept_typed_bins() {
383 let values = [1.0, 2.0, 3.0];
384 let _ = HistogramPlot::new("hist", &values).with_bins(8usize);
385 let _ = HistogramPlot::new("hist", &values).with_bins(BinMethod::Scott);
386 let _ = Histogram2DPlot::new("hist2d", &values, &values).with_bins(4usize, BinMethod::Rice);
387 }
388}