use crate::artist::ErrorBarArtist;
use crate::primitives::Color;
impl ErrorBarArtist {
pub fn color(&mut self, color: Color) -> &mut Self {
self.color = color;
self
}
pub fn label(&mut self, label: &str) -> &mut Self {
self.label = Some(label.to_string());
self
}
pub fn cap_size(&mut self, size: f64) -> &mut Self {
self.cap_size = size.max(0.0);
self
}
pub fn line_width(&mut self, width: f64) -> &mut Self {
self.line_width = width.max(0.0);
self
}
pub fn yerr_symmetric(mut self, errs: &[f64]) -> Self {
self.yerr = Some(crate::artist::ErrorBarData::Symmetric(errs.to_vec()));
self
}
pub fn yerr_asymmetric(mut self, low: &[f64], high: &[f64]) -> Self {
self.yerr = Some(crate::artist::ErrorBarData::Asymmetric {
low: low.to_vec(),
high: high.to_vec(),
});
self
}
pub fn xerr_symmetric(mut self, errs: &[f64]) -> Self {
self.xerr = Some(crate::artist::ErrorBarData::Symmetric(errs.to_vec()));
self
}
pub fn xerr_asymmetric(mut self, low: &[f64], high: &[f64]) -> Self {
self.xerr = Some(crate::artist::ErrorBarData::Asymmetric {
low: low.to_vec(),
high: high.to_vec(),
});
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::artist::ErrorBarData;
use crate::series::Series;
const TOL: f64 = 1e-12;
fn approx_eq(a: f64, b: f64) -> bool {
(a - b).abs() < TOL
}
fn sample_errorbar() -> ErrorBarArtist {
ErrorBarArtist {
x: Series::new(vec![1.0, 2.0, 3.0]),
y: Series::new(vec![10.0, 20.0, 30.0]),
xerr: None,
yerr: None,
color: Color::TAB_BLUE,
label: None,
cap_size: 4.0,
line_width: 1.0,
}
}
#[test]
fn builder_color() {
let mut a = sample_errorbar();
a.color(Color::TAB_RED);
assert_eq!(a.color, Color::TAB_RED);
}
#[test]
fn builder_label() {
let mut a = sample_errorbar();
assert!(a.label.is_none());
a.label("Measurements");
assert_eq!(a.label.as_deref(), Some("Measurements"));
}
#[test]
fn builder_label_overwrite() {
let mut a = sample_errorbar();
a.label("first");
a.label("second");
assert_eq!(a.label.as_deref(), Some("second"));
}
#[test]
fn builder_cap_size() {
let mut a = sample_errorbar();
a.cap_size(8.0);
assert!(approx_eq(a.cap_size, 8.0));
}
#[test]
fn builder_cap_size_clamps_negative() {
let mut a = sample_errorbar();
a.cap_size(-5.0);
assert!(approx_eq(a.cap_size, 0.0));
}
#[test]
fn builder_line_width() {
let mut a = sample_errorbar();
a.line_width(2.5);
assert!(approx_eq(a.line_width, 2.5));
}
#[test]
fn builder_line_width_clamps_negative() {
let mut a = sample_errorbar();
a.line_width(-1.0);
assert!(approx_eq(a.line_width, 0.0));
}
#[test]
fn builder_yerr_symmetric() {
let a = sample_errorbar().yerr_symmetric(&[0.5, 1.0, 1.5]);
match &a.yerr {
Some(ErrorBarData::Symmetric(v)) => {
assert_eq!(v.len(), 3);
assert!(approx_eq(v[0], 0.5));
assert!(approx_eq(v[1], 1.0));
assert!(approx_eq(v[2], 1.5));
}
_ => panic!("expected Symmetric yerr"),
}
}
#[test]
fn builder_yerr_asymmetric() {
let a = sample_errorbar().yerr_asymmetric(&[0.3, 0.5, 0.7], &[1.0, 1.2, 1.4]);
match &a.yerr {
Some(ErrorBarData::Asymmetric { low, high }) => {
assert_eq!(low.len(), 3);
assert_eq!(high.len(), 3);
assert!(approx_eq(low[0], 0.3));
assert!(approx_eq(high[2], 1.4));
}
_ => panic!("expected Asymmetric yerr"),
}
}
#[test]
fn builder_xerr_symmetric() {
let a = sample_errorbar().xerr_symmetric(&[0.1, 0.2, 0.3]);
match &a.xerr {
Some(ErrorBarData::Symmetric(v)) => {
assert_eq!(v.len(), 3);
assert!(approx_eq(v[0], 0.1));
}
_ => panic!("expected Symmetric xerr"),
}
}
#[test]
fn builder_xerr_asymmetric() {
let a = sample_errorbar().xerr_asymmetric(&[0.1, 0.2, 0.3], &[0.4, 0.5, 0.6]);
match &a.xerr {
Some(ErrorBarData::Asymmetric { low, high }) => {
assert_eq!(low.len(), 3);
assert_eq!(high.len(), 3);
assert!(approx_eq(low[1], 0.2));
assert!(approx_eq(high[1], 0.5));
}
_ => panic!("expected Asymmetric xerr"),
}
}
#[test]
fn builder_chaining() {
let mut a = sample_errorbar();
a.color(Color::TAB_GREEN)
.label("Test")
.cap_size(6.0)
.line_width(2.0);
assert_eq!(a.color, Color::TAB_GREEN);
assert_eq!(a.label.as_deref(), Some("Test"));
assert!(approx_eq(a.cap_size, 6.0));
assert!(approx_eq(a.line_width, 2.0));
}
#[test]
fn data_bounds_no_errors() {
let a = sample_errorbar();
let (xmin, xmax, ymin, ymax) = a.data_bounds();
assert!(approx_eq(xmin, 1.0));
assert!(approx_eq(xmax, 3.0));
assert!(approx_eq(ymin, 10.0));
assert!(approx_eq(ymax, 30.0));
}
#[test]
fn data_bounds_with_symmetric_yerr() {
let a = sample_errorbar().yerr_symmetric(&[2.0, 3.0, 5.0]);
let (xmin, xmax, ymin, ymax) = a.data_bounds();
assert!(approx_eq(xmin, 1.0));
assert!(approx_eq(xmax, 3.0));
assert!(approx_eq(ymin, 8.0));
assert!(approx_eq(ymax, 35.0));
}
#[test]
fn data_bounds_with_asymmetric_yerr() {
let a = sample_errorbar().yerr_asymmetric(&[1.0, 2.0, 3.0], &[5.0, 6.0, 7.0]);
let (_, _, ymin, ymax) = a.data_bounds();
assert!(approx_eq(ymin, 9.0));
assert!(approx_eq(ymax, 37.0));
}
#[test]
fn data_bounds_with_symmetric_xerr() {
let a = sample_errorbar().xerr_symmetric(&[0.5, 0.5, 0.5]);
let (xmin, xmax, _, _) = a.data_bounds();
assert!(approx_eq(xmin, 0.5));
assert!(approx_eq(xmax, 3.5));
}
#[test]
fn data_bounds_with_both_errors() {
let a = sample_errorbar()
.xerr_symmetric(&[0.5, 0.5, 0.5])
.yerr_symmetric(&[2.0, 3.0, 5.0]);
let (xmin, xmax, ymin, ymax) = a.data_bounds();
assert!(approx_eq(xmin, 0.5));
assert!(approx_eq(xmax, 3.5));
assert!(approx_eq(ymin, 8.0));
assert!(approx_eq(ymax, 35.0));
}
#[test]
fn data_bounds_empty_series() {
let a = ErrorBarArtist {
x: Series::new(vec![]),
y: Series::new(vec![]),
xerr: None,
yerr: None,
color: Color::BLACK,
label: None,
cap_size: 4.0,
line_width: 1.0,
};
let (xmin, xmax, ymin, ymax) = a.data_bounds();
assert!(approx_eq(xmin, 0.0));
assert!(approx_eq(xmax, 1.0));
assert!(approx_eq(ymin, 0.0));
assert!(approx_eq(ymax, 1.0));
}
}