use super::{Plot, PlotError, PlotItemStyle, plot_spec_with_style, with_plot_str_or_empty};
use crate::{HeatmapFlags, ItemFlags, sys};
use dear_imgui_rs::with_scratch_txt_two;
pub struct HeatmapPlot<'a> {
label: &'a str,
values: &'a [f64],
style: PlotItemStyle,
rows: i32,
cols: i32,
scale_min: f64,
scale_max: f64,
label_fmt: Option<&'a str>,
bounds_min: sys::ImPlotPoint,
bounds_max: sys::ImPlotPoint,
flags: HeatmapFlags,
item_flags: ItemFlags,
}
impl<'a> super::PlotItemStyled for HeatmapPlot<'a> {
fn style_mut(&mut self) -> &mut PlotItemStyle {
&mut self.style
}
}
impl<'a> HeatmapPlot<'a> {
pub fn new(label: &'a str, values: &'a [f64], rows: usize, cols: usize) -> Self {
Self {
label,
values,
style: PlotItemStyle::default(),
rows: rows as i32,
cols: cols as i32,
scale_min: 0.0,
scale_max: 0.0, label_fmt: Some("%.1f"),
bounds_min: sys::ImPlotPoint { x: 0.0, y: 0.0 },
bounds_max: sys::ImPlotPoint { x: 1.0, y: 1.0 },
flags: HeatmapFlags::NONE,
item_flags: ItemFlags::NONE,
}
}
pub fn with_scale(mut self, min: f64, max: f64) -> Self {
self.scale_min = min;
self.scale_max = max;
self
}
pub fn with_label_format(mut self, fmt: Option<&'a str>) -> Self {
self.label_fmt = fmt;
self
}
pub fn with_bounds(mut self, min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> Self {
self.bounds_min = sys::ImPlotPoint { x: min_x, y: min_y };
self.bounds_max = sys::ImPlotPoint { x: max_x, y: max_y };
self
}
pub fn with_bounds_points(mut self, min: sys::ImPlotPoint, max: sys::ImPlotPoint) -> Self {
self.bounds_min = min;
self.bounds_max = max;
self
}
pub fn with_flags(mut self, flags: HeatmapFlags) -> Self {
self.flags = flags;
self
}
pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
self.item_flags = flags;
self
}
pub fn column_major(mut self) -> Self {
self.flags |= HeatmapFlags::COL_MAJOR;
self
}
pub fn validate(&self) -> Result<(), PlotError> {
if self.values.is_empty() {
return Err(PlotError::EmptyData);
}
let expected_size = (self.rows * self.cols) as usize;
if self.values.len() != expected_size {
return Err(PlotError::DataLengthMismatch {
x_len: expected_size,
y_len: self.values.len(),
});
}
if self.rows <= 0 || self.cols <= 0 {
return Err(PlotError::InvalidData(
"Rows and columns must be positive".to_string(),
));
}
Ok(())
}
}
impl<'a> Plot for HeatmapPlot<'a> {
fn plot(&self) {
if self.validate().is_err() {
return; }
let label_fmt = self.label_fmt.filter(|s| !s.contains('\0'));
match label_fmt {
Some(label_fmt) => {
let label = if self.label.contains('\0') {
""
} else {
self.label
};
with_scratch_txt_two(label, label_fmt, |label_ptr, label_fmt_ptr| unsafe {
let spec = plot_spec_with_style(
self.style,
self.flags.bits() | self.item_flags.bits(),
0,
crate::IMPLOT_AUTO,
);
sys::ImPlot_PlotHeatmap_doublePtr(
label_ptr,
self.values.as_ptr(),
self.rows,
self.cols,
self.scale_min,
self.scale_max,
label_fmt_ptr,
self.bounds_min,
self.bounds_max,
spec,
);
})
}
None => 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_PlotHeatmap_doublePtr(
label_ptr,
self.values.as_ptr(),
self.rows,
self.cols,
self.scale_min,
self.scale_max,
std::ptr::null(),
self.bounds_min,
self.bounds_max,
spec,
);
}),
}
}
fn label(&self) -> &str {
self.label
}
}
pub struct HeatmapPlotF32<'a> {
label: &'a str,
values: &'a [f32],
style: PlotItemStyle,
rows: i32,
cols: i32,
scale_min: f64,
scale_max: f64,
label_fmt: Option<&'a str>,
bounds_min: sys::ImPlotPoint,
bounds_max: sys::ImPlotPoint,
flags: HeatmapFlags,
item_flags: ItemFlags,
}
impl<'a> super::PlotItemStyled for HeatmapPlotF32<'a> {
fn style_mut(&mut self) -> &mut PlotItemStyle {
&mut self.style
}
}
impl<'a> HeatmapPlotF32<'a> {
pub fn new(label: &'a str, values: &'a [f32], rows: usize, cols: usize) -> Self {
Self {
label,
values,
style: PlotItemStyle::default(),
rows: rows as i32,
cols: cols as i32,
scale_min: 0.0,
scale_max: 0.0,
label_fmt: Some("%.1f"),
bounds_min: sys::ImPlotPoint { x: 0.0, y: 0.0 },
bounds_max: sys::ImPlotPoint { x: 1.0, y: 1.0 },
flags: HeatmapFlags::NONE,
item_flags: ItemFlags::NONE,
}
}
pub fn with_scale(mut self, min: f64, max: f64) -> Self {
self.scale_min = min;
self.scale_max = max;
self
}
pub fn with_label_format(mut self, fmt: Option<&'a str>) -> Self {
self.label_fmt = fmt;
self
}
pub fn with_bounds(mut self, min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> Self {
self.bounds_min = sys::ImPlotPoint { x: min_x, y: min_y };
self.bounds_max = sys::ImPlotPoint { x: max_x, y: max_y };
self
}
pub fn with_flags(mut self, flags: HeatmapFlags) -> Self {
self.flags = flags;
self
}
pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
self.item_flags = flags;
self
}
pub fn column_major(mut self) -> Self {
self.flags |= HeatmapFlags::COL_MAJOR;
self
}
pub fn validate(&self) -> Result<(), PlotError> {
if self.values.is_empty() {
return Err(PlotError::EmptyData);
}
let expected_size = (self.rows * self.cols) as usize;
if self.values.len() != expected_size {
return Err(PlotError::DataLengthMismatch {
x_len: expected_size,
y_len: self.values.len(),
});
}
if self.rows <= 0 || self.cols <= 0 {
return Err(PlotError::InvalidData(
"Rows and columns must be positive".to_string(),
));
}
Ok(())
}
}
impl<'a> Plot for HeatmapPlotF32<'a> {
fn plot(&self) {
if self.validate().is_err() {
return;
}
let label_fmt = self.label_fmt.filter(|s| !s.contains('\0'));
match label_fmt {
Some(label_fmt) => {
let label = if self.label.contains('\0') {
""
} else {
self.label
};
with_scratch_txt_two(label, label_fmt, |label_ptr, label_fmt_ptr| unsafe {
let spec = plot_spec_with_style(
self.style,
self.flags.bits() | self.item_flags.bits(),
0,
crate::IMPLOT_AUTO,
);
sys::ImPlot_PlotHeatmap_FloatPtr(
label_ptr,
self.values.as_ptr(),
self.rows,
self.cols,
self.scale_min,
self.scale_max,
label_fmt_ptr,
self.bounds_min,
self.bounds_max,
spec,
);
})
}
None => 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_PlotHeatmap_FloatPtr(
label_ptr,
self.values.as_ptr(),
self.rows,
self.cols,
self.scale_min,
self.scale_max,
std::ptr::null(),
self.bounds_min,
self.bounds_max,
spec,
);
}),
}
}
fn label(&self) -> &str {
self.label
}
}
impl<'ui> crate::PlotUi<'ui> {
pub fn heatmap_plot(
&self,
label: &str,
values: &[f64],
rows: usize,
cols: usize,
) -> Result<(), PlotError> {
let plot = HeatmapPlot::new(label, values, rows, cols);
plot.validate()?;
plot.plot();
Ok(())
}
pub fn heatmap_plot_f32(
&self,
label: &str,
values: &[f32],
rows: usize,
cols: usize,
) -> Result<(), PlotError> {
let plot = HeatmapPlotF32::new(label, values, rows, cols);
plot.validate()?;
plot.plot();
Ok(())
}
pub fn heatmap_plot_scaled(
&self,
label: &str,
values: &[f64],
rows: usize,
cols: usize,
scale_min: f64,
scale_max: f64,
bounds_min: sys::ImPlotPoint,
bounds_max: sys::ImPlotPoint,
) -> Result<(), PlotError> {
let plot = HeatmapPlot::new(label, values, rows, cols)
.with_scale(scale_min, scale_max)
.with_bounds_points(bounds_min, bounds_max);
plot.validate()?;
plot.plot();
Ok(())
}
}