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 plot_ui.with_bound_context(|| {
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
147 fn label(&self) -> &str {
148 self.label
149 }
150}
151
152pub struct Histogram2DPlot<'a> {
154 label: &'a str,
155 x_values: &'a [f64],
156 y_values: &'a [f64],
157 style: PlotItemStyle,
158 x_bins: HistogramBins,
159 y_bins: HistogramBins,
160 range: Option<sys::ImPlotRect>,
161 flags: HistogramFlags,
162 item_flags: ItemFlags,
163}
164
165impl<'a> super::PlotItemStyled for Histogram2DPlot<'a> {
166 fn style_mut(&mut self) -> &mut PlotItemStyle {
167 &mut self.style
168 }
169}
170
171impl<'a> Histogram2DPlot<'a> {
172 pub fn new(label: &'a str, x_values: &'a [f64], y_values: &'a [f64]) -> Self {
174 Self {
175 label,
176 x_values,
177 y_values,
178 style: PlotItemStyle::default(),
179 x_bins: HistogramBins::DEFAULT,
180 y_bins: HistogramBins::DEFAULT,
181 range: None, flags: HistogramFlags::NONE,
183 item_flags: ItemFlags::NONE,
184 }
185 }
186
187 pub fn with_bins(
189 mut self,
190 x_bins: impl Into<HistogramBins>,
191 y_bins: impl Into<HistogramBins>,
192 ) -> Self {
193 self.x_bins = x_bins.into();
194 self.y_bins = y_bins.into();
195 self
196 }
197
198 pub fn with_range(mut self, x_min: f64, x_max: f64, y_min: f64, y_max: f64) -> Self {
200 self.range = Some(sys::ImPlotRect {
201 X: sys::ImPlotRange {
202 Min: x_min,
203 Max: x_max,
204 },
205 Y: sys::ImPlotRange {
206 Min: y_min,
207 Max: y_max,
208 },
209 });
210 self
211 }
212
213 pub fn with_range_struct(mut self, range: sys::ImPlotRect) -> Self {
215 self.range = Some(range);
216 self
217 }
218
219 pub fn with_flags(mut self, flags: HistogramFlags) -> Self {
221 self.flags = flags;
222 self
223 }
224
225 pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
227 self.item_flags = flags;
228 self
229 }
230
231 pub fn density(mut self) -> Self {
233 self.flags |= HistogramFlags::DENSITY;
234 self
235 }
236
237 pub fn no_outliers(mut self) -> Self {
239 self.flags |= HistogramFlags::NO_OUTLIERS;
240 self
241 }
242
243 pub fn column_major(mut self) -> Self {
245 self.flags |= HistogramFlags::COL_MAJOR;
246 self
247 }
248
249 pub fn validate(&self) -> Result<(), PlotError> {
251 super::validate_data_lengths(self.x_values, self.y_values)
252 }
253}
254
255impl<'a> Plot for Histogram2DPlot<'a> {
256 fn plot(&self, plot_ui: &crate::PlotUi<'_>) {
257 if self.validate().is_err() {
258 return;
259 }
260 let Ok(count) = i32::try_from(self.x_values.len()) else {
261 return;
262 };
263
264 let range = if let Some(range) = &self.range {
265 *range
266 } else {
267 sys::ImPlotRect {
268 X: sys::ImPlotRange { Min: 0.0, Max: 0.0 },
269 Y: sys::ImPlotRange { Min: 0.0, Max: 0.0 },
270 }
271 };
272
273 plot_ui.with_bound_context(|| {
274 with_plot_str_or_empty(self.label, |label_ptr| unsafe {
275 let spec = plot_spec_with_style(
276 self.style,
277 self.flags.bits() | self.item_flags.bits(),
278 PlotDataLayout::DEFAULT,
279 );
280 sys::ImPlot_PlotHistogram2D_doublePtr(
281 label_ptr,
282 self.x_values.as_ptr(),
283 self.y_values.as_ptr(),
284 count,
285 self.x_bins.raw("Histogram2DPlot::plot()"),
286 self.y_bins.raw("Histogram2DPlot::plot()"),
287 range,
288 spec,
289 );
290 })
291 })
292 }
293
294 fn label(&self) -> &str {
295 self.label
296 }
297}
298
299impl<'ui> crate::PlotUi<'ui> {
301 pub fn histogram_plot(&self, label: &str, values: &[f64]) -> Result<(), PlotError> {
303 let plot = HistogramPlot::new(label, values);
304 plot.validate()?;
305 plot.plot(self);
306 Ok(())
307 }
308
309 pub fn histogram_plot_with_bins(
311 &self,
312 label: &str,
313 values: &[f64],
314 bins: impl Into<HistogramBins>,
315 ) -> Result<(), PlotError> {
316 let plot = HistogramPlot::new(label, values).with_bins(bins);
317 plot.validate()?;
318 plot.plot(self);
319 Ok(())
320 }
321
322 pub fn histogram_2d_plot(
324 &self,
325 label: &str,
326 x_values: &[f64],
327 y_values: &[f64],
328 ) -> Result<(), PlotError> {
329 let plot = Histogram2DPlot::new(label, x_values, y_values);
330 plot.validate()?;
331 plot.plot(self);
332 Ok(())
333 }
334
335 pub fn histogram_2d_plot_with_bins(
337 &self,
338 label: &str,
339 x_values: &[f64],
340 y_values: &[f64],
341 x_bins: impl Into<HistogramBins>,
342 y_bins: impl Into<HistogramBins>,
343 ) -> Result<(), PlotError> {
344 let plot = Histogram2DPlot::new(label, x_values, y_values).with_bins(x_bins, y_bins);
345 plot.validate()?;
346 plot.plot(self);
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}