use super::{Plot, PlotError, PlotItemStyle, plot_spec_with_style, with_plot_str_or_empty};
use crate::sys;
use crate::{BinMethod, HistogramFlags, ItemFlags};
pub struct HistogramPlot<'a> {
label: &'a str,
values: &'a [f64],
style: PlotItemStyle,
bins: i32,
bar_scale: f64,
range: Option<sys::ImPlotRange>,
flags: HistogramFlags,
item_flags: ItemFlags,
}
impl<'a> super::PlotItemStyled for HistogramPlot<'a> {
fn style_mut(&mut self) -> &mut PlotItemStyle {
&mut self.style
}
}
impl<'a> HistogramPlot<'a> {
pub fn new(label: &'a str, values: &'a [f64]) -> Self {
Self {
label,
values,
style: PlotItemStyle::default(),
bins: BinMethod::Sturges as i32,
bar_scale: 1.0,
range: None, flags: HistogramFlags::NONE,
item_flags: ItemFlags::NONE,
}
}
pub fn with_bins(mut self, bins: i32) -> Self {
self.bins = bins;
self
}
pub fn with_bar_scale(mut self, scale: f64) -> Self {
self.bar_scale = scale;
self
}
pub fn with_range(mut self, min: f64, max: f64) -> Self {
self.range = Some(sys::ImPlotRange { Min: min, Max: max });
self
}
pub fn with_range_struct(mut self, range: sys::ImPlotRange) -> Self {
self.range = Some(range);
self
}
pub fn with_flags(mut self, flags: HistogramFlags) -> Self {
self.flags = flags;
self
}
pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
self.item_flags = flags;
self
}
pub fn horizontal(mut self) -> Self {
self.flags |= HistogramFlags::HORIZONTAL;
self
}
pub fn cumulative(mut self) -> Self {
self.flags |= HistogramFlags::CUMULATIVE;
self
}
pub fn density(mut self) -> Self {
self.flags |= HistogramFlags::DENSITY;
self
}
pub fn no_outliers(mut self) -> Self {
self.flags |= HistogramFlags::NO_OUTLIERS;
self
}
pub fn validate(&self) -> Result<(), PlotError> {
if self.values.is_empty() {
return Err(PlotError::EmptyData);
}
Ok(())
}
}
impl<'a> Plot for HistogramPlot<'a> {
fn plot(&self) {
if self.validate().is_err() {
return;
}
let Ok(count) = i32::try_from(self.values.len()) else {
return;
};
let range = if let Some(range) = &self.range {
*range
} else {
sys::ImPlotRange { Min: 0.0, Max: 0.0 }
};
with_plot_str_or_empty(self.label, |label_ptr| unsafe {
let spec = plot_spec_with_style(
self.style,
self.flags.bits() | self.item_flags.bits(),
0,
crate::IMPLOT_AUTO,
);
sys::ImPlot_PlotHistogram_doublePtr(
label_ptr,
self.values.as_ptr(),
count,
self.bins,
self.bar_scale,
range,
spec,
);
})
}
fn label(&self) -> &str {
self.label
}
}
pub struct Histogram2DPlot<'a> {
label: &'a str,
x_values: &'a [f64],
y_values: &'a [f64],
style: PlotItemStyle,
x_bins: i32,
y_bins: i32,
range: Option<sys::ImPlotRect>,
flags: HistogramFlags,
item_flags: ItemFlags,
}
impl<'a> super::PlotItemStyled for Histogram2DPlot<'a> {
fn style_mut(&mut self) -> &mut PlotItemStyle {
&mut self.style
}
}
impl<'a> Histogram2DPlot<'a> {
pub fn new(label: &'a str, x_values: &'a [f64], y_values: &'a [f64]) -> Self {
Self {
label,
x_values,
y_values,
style: PlotItemStyle::default(),
x_bins: BinMethod::Sturges as i32,
y_bins: BinMethod::Sturges as i32,
range: None, flags: HistogramFlags::NONE,
item_flags: ItemFlags::NONE,
}
}
pub fn with_bins(mut self, x_bins: i32, y_bins: i32) -> Self {
self.x_bins = x_bins;
self.y_bins = y_bins;
self
}
pub fn with_range(mut self, x_min: f64, x_max: f64, y_min: f64, y_max: f64) -> Self {
self.range = Some(sys::ImPlotRect {
X: sys::ImPlotRange {
Min: x_min,
Max: x_max,
},
Y: sys::ImPlotRange {
Min: y_min,
Max: y_max,
},
});
self
}
pub fn with_range_struct(mut self, range: sys::ImPlotRect) -> Self {
self.range = Some(range);
self
}
pub fn with_flags(mut self, flags: HistogramFlags) -> Self {
self.flags = flags;
self
}
pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
self.item_flags = flags;
self
}
pub fn density(mut self) -> Self {
self.flags |= HistogramFlags::DENSITY;
self
}
pub fn no_outliers(mut self) -> Self {
self.flags |= HistogramFlags::NO_OUTLIERS;
self
}
pub fn column_major(mut self) -> Self {
self.flags |= HistogramFlags::COL_MAJOR;
self
}
pub fn validate(&self) -> Result<(), PlotError> {
super::validate_data_lengths(self.x_values, self.y_values)
}
}
impl<'a> Plot for Histogram2DPlot<'a> {
fn plot(&self) {
if self.validate().is_err() {
return;
}
let Ok(count) = i32::try_from(self.x_values.len()) else {
return;
};
let range = if let Some(range) = &self.range {
*range
} else {
sys::ImPlotRect {
X: sys::ImPlotRange { Min: 0.0, Max: 0.0 },
Y: sys::ImPlotRange { Min: 0.0, Max: 0.0 },
}
};
with_plot_str_or_empty(self.label, |label_ptr| unsafe {
let spec = plot_spec_with_style(
self.style,
self.flags.bits() | self.item_flags.bits(),
0,
crate::IMPLOT_AUTO,
);
sys::ImPlot_PlotHistogram2D_doublePtr(
label_ptr,
self.x_values.as_ptr(),
self.y_values.as_ptr(),
count,
self.x_bins,
self.y_bins,
range,
spec,
);
})
}
fn label(&self) -> &str {
self.label
}
}
impl<'ui> crate::PlotUi<'ui> {
pub fn histogram_plot(&self, label: &str, values: &[f64]) -> Result<(), PlotError> {
let plot = HistogramPlot::new(label, values);
plot.validate()?;
plot.plot();
Ok(())
}
pub fn histogram_plot_with_bins(
&self,
label: &str,
values: &[f64],
bins: i32,
) -> Result<(), PlotError> {
let plot = HistogramPlot::new(label, values).with_bins(bins);
plot.validate()?;
plot.plot();
Ok(())
}
pub fn histogram_2d_plot(
&self,
label: &str,
x_values: &[f64],
y_values: &[f64],
) -> Result<(), PlotError> {
let plot = Histogram2DPlot::new(label, x_values, y_values);
plot.validate()?;
plot.plot();
Ok(())
}
pub fn histogram_2d_plot_with_bins(
&self,
label: &str,
x_values: &[f64],
y_values: &[f64],
x_bins: i32,
y_bins: i32,
) -> Result<(), PlotError> {
let plot = Histogram2DPlot::new(label, x_values, y_values).with_bins(x_bins, y_bins);
plot.validate()?;
plot.plot();
Ok(())
}
}