use super::{
Plot, PlotError, PlotItemStyle, plot_spec_with_style, validate_data_lengths,
with_plot_str_or_empty,
};
use crate::{ErrorBarsFlags, ItemFlags, sys};
pub struct ErrorBarsPlot<'a> {
label: &'a str,
x_data: &'a [f64],
y_data: &'a [f64],
err_data: &'a [f64],
style: PlotItemStyle,
flags: ErrorBarsFlags,
item_flags: ItemFlags,
offset: i32,
stride: i32,
}
impl<'a> super::PlotItemStyled for ErrorBarsPlot<'a> {
fn style_mut(&mut self) -> &mut PlotItemStyle {
&mut self.style
}
}
impl<'a> ErrorBarsPlot<'a> {
pub fn new(label: &'a str, x_data: &'a [f64], y_data: &'a [f64], err_data: &'a [f64]) -> Self {
Self {
label,
x_data,
y_data,
err_data,
style: PlotItemStyle::default(),
flags: ErrorBarsFlags::NONE,
item_flags: ItemFlags::NONE,
offset: 0,
stride: std::mem::size_of::<f64>() as i32,
}
}
pub fn with_flags(mut self, flags: ErrorBarsFlags) -> Self {
self.flags = flags;
self
}
pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
self.item_flags = flags;
self
}
pub fn with_offset(mut self, offset: i32) -> Self {
self.offset = offset;
self
}
pub fn with_stride(mut self, stride: i32) -> Self {
self.stride = stride;
self
}
pub fn horizontal(mut self) -> Self {
self.flags |= ErrorBarsFlags::HORIZONTAL;
self
}
pub fn validate(&self) -> Result<(), PlotError> {
validate_data_lengths(self.x_data, self.y_data)?;
validate_data_lengths(self.x_data, self.err_data)?;
if self.err_data.iter().any(|&err| err < 0.0) {
return Err(PlotError::InvalidData(
"Error values cannot be negative".to_string(),
));
}
Ok(())
}
}
impl<'a> Plot for ErrorBarsPlot<'a> {
fn plot(&self) {
if self.validate().is_err() {
return;
}
let Ok(count) = i32::try_from(self.x_data.len()) else {
return;
};
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(),
self.offset,
self.stride,
);
sys::ImPlot_PlotErrorBars_doublePtrdoublePtrdoublePtrInt(
label_ptr,
self.x_data.as_ptr(),
self.y_data.as_ptr(),
self.err_data.as_ptr(),
count,
spec,
);
})
}
fn label(&self) -> &str {
self.label
}
}
pub struct AsymmetricErrorBarsPlot<'a> {
label: &'a str,
x_data: &'a [f64],
y_data: &'a [f64],
err_neg: &'a [f64],
err_pos: &'a [f64],
style: PlotItemStyle,
flags: ErrorBarsFlags,
item_flags: ItemFlags,
offset: i32,
stride: i32,
}
impl<'a> super::PlotItemStyled for AsymmetricErrorBarsPlot<'a> {
fn style_mut(&mut self) -> &mut PlotItemStyle {
&mut self.style
}
}
impl<'a> AsymmetricErrorBarsPlot<'a> {
pub fn new(
label: &'a str,
x_data: &'a [f64],
y_data: &'a [f64],
err_neg: &'a [f64],
err_pos: &'a [f64],
) -> Self {
Self {
label,
x_data,
y_data,
err_neg,
err_pos,
style: PlotItemStyle::default(),
flags: ErrorBarsFlags::NONE,
item_flags: ItemFlags::NONE,
offset: 0,
stride: std::mem::size_of::<f64>() as i32,
}
}
pub fn with_flags(mut self, flags: ErrorBarsFlags) -> Self {
self.flags = flags;
self
}
pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
self.item_flags = flags;
self
}
pub fn with_offset(mut self, offset: i32) -> Self {
self.offset = offset;
self
}
pub fn with_stride(mut self, stride: i32) -> Self {
self.stride = stride;
self
}
pub fn horizontal(mut self) -> Self {
self.flags |= ErrorBarsFlags::HORIZONTAL;
self
}
pub fn validate(&self) -> Result<(), PlotError> {
validate_data_lengths(self.x_data, self.y_data)?;
validate_data_lengths(self.x_data, self.err_neg)?;
validate_data_lengths(self.x_data, self.err_pos)?;
if self.err_neg.iter().any(|&err| err < 0.0) || self.err_pos.iter().any(|&err| err < 0.0) {
return Err(PlotError::InvalidData(
"Error values cannot be negative".to_string(),
));
}
Ok(())
}
}
impl<'a> Plot for AsymmetricErrorBarsPlot<'a> {
fn plot(&self) {
if self.validate().is_err() {
return;
}
let Ok(count) = i32::try_from(self.x_data.len()) else {
return;
};
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(),
self.offset,
self.stride,
);
sys::ImPlot_PlotErrorBars_doublePtrdoublePtrdoublePtrdoublePtr(
label_ptr,
self.x_data.as_ptr(),
self.y_data.as_ptr(),
self.err_neg.as_ptr(),
self.err_pos.as_ptr(),
count,
spec,
);
})
}
fn label(&self) -> &str {
self.label
}
}
pub struct SimpleErrorBarsPlot<'a> {
label: &'a str,
values: &'a [f64],
errors: &'a [f64],
style: PlotItemStyle,
flags: ErrorBarsFlags,
item_flags: ItemFlags,
x_scale: f64,
x_start: f64,
}
impl<'a> super::PlotItemStyled for SimpleErrorBarsPlot<'a> {
fn style_mut(&mut self) -> &mut PlotItemStyle {
&mut self.style
}
}
impl<'a> SimpleErrorBarsPlot<'a> {
pub fn new(label: &'a str, values: &'a [f64], errors: &'a [f64]) -> Self {
Self {
label,
values,
errors,
style: PlotItemStyle::default(),
flags: ErrorBarsFlags::NONE,
item_flags: ItemFlags::NONE,
x_scale: 1.0,
x_start: 0.0,
}
}
pub fn with_flags(mut self, flags: ErrorBarsFlags) -> 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 |= ErrorBarsFlags::HORIZONTAL;
self
}
pub fn with_x_scale(mut self, scale: f64) -> Self {
self.x_scale = scale;
self
}
pub fn with_x_start(mut self, start: f64) -> Self {
self.x_start = start;
self
}
pub fn validate(&self) -> Result<(), PlotError> {
validate_data_lengths(self.values, self.errors)?;
if self.errors.iter().any(|&err| err < 0.0) {
return Err(PlotError::InvalidData(
"Error values cannot be negative".to_string(),
));
}
Ok(())
}
}
impl<'a> Plot for SimpleErrorBarsPlot<'a> {
fn plot(&self) {
if self.validate().is_err() {
return;
}
let Ok(count) = i32::try_from(self.values.len()) else {
return;
};
let x_data: Vec<f64> = (0..self.values.len())
.map(|i| self.x_start + i as f64 * self.x_scale)
.collect();
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,
std::mem::size_of::<f64>() as i32,
);
sys::ImPlot_PlotErrorBars_doublePtrdoublePtrdoublePtrInt(
label_ptr,
x_data.as_ptr(),
self.values.as_ptr(),
self.errors.as_ptr(),
count,
spec,
);
})
}
fn label(&self) -> &str {
self.label
}
}
impl<'ui> crate::PlotUi<'ui> {
pub fn error_bars_plot(
&self,
label: &str,
x_data: &[f64],
y_data: &[f64],
err_data: &[f64],
) -> Result<(), PlotError> {
let plot = ErrorBarsPlot::new(label, x_data, y_data, err_data);
plot.validate()?;
plot.plot();
Ok(())
}
pub fn asymmetric_error_bars_plot(
&self,
label: &str,
x_data: &[f64],
y_data: &[f64],
err_neg: &[f64],
err_pos: &[f64],
) -> Result<(), PlotError> {
let plot = AsymmetricErrorBarsPlot::new(label, x_data, y_data, err_neg, err_pos);
plot.validate()?;
plot.plot();
Ok(())
}
pub fn simple_error_bars_plot(
&self,
label: &str,
values: &[f64],
errors: &[f64],
) -> Result<(), PlotError> {
let plot = SimpleErrorBarsPlot::new(label, values, errors);
plot.validate()?;
plot.plot();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_error_bars_plot_flags() {
let values = [1.0, 2.0, 3.0, 4.0];
let errors = [0.1, 0.2, 0.3, 0.4];
let plot = SimpleErrorBarsPlot::new("test", &values, &errors)
.horizontal()
.with_item_flags(ItemFlags::NO_LEGEND);
assert_eq!(plot.label(), "test");
assert_eq!(plot.flags.bits(), ErrorBarsFlags::HORIZONTAL.bits());
assert_eq!(plot.item_flags, ItemFlags::NO_LEGEND);
}
}