#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum XyOrientation {
#[default]
Vertical,
Horizontal,
}
#[derive(Debug, Clone, PartialEq)]
pub enum XAxis {
Categorical { labels: Vec<String> },
Numeric {
label: Option<String>,
min: f64,
max: f64,
},
}
impl Default for XAxis {
fn default() -> Self {
XAxis::Categorical { labels: Vec::new() }
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct YAxis {
pub label: Option<String>,
pub min: f64,
pub max: f64,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct XyChart {
pub title: Option<String>,
pub orientation: XyOrientation,
pub x_axis: XAxis,
pub y_axis: YAxis,
pub bar_series: Vec<f64>,
pub line_series: Vec<f64>,
}
impl XyChart {
pub fn data_count(&self) -> usize {
if !self.bar_series.is_empty() {
self.bar_series.len()
} else if !self.line_series.is_empty() {
self.line_series.len()
} else {
match &self.x_axis {
XAxis::Categorical { labels } => labels.len(),
XAxis::Numeric { .. } => 0,
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_chart_has_empty_series() {
let chart = XyChart::default();
assert!(chart.bar_series.is_empty());
assert!(chart.line_series.is_empty());
assert!(chart.title.is_none());
assert_eq!(chart.orientation, XyOrientation::Vertical);
}
#[test]
fn data_count_from_bar_series() {
let chart = XyChart {
bar_series: vec![1.0, 2.0, 3.0],
..Default::default()
};
assert_eq!(chart.data_count(), 3);
}
#[test]
fn data_count_from_line_series_when_bar_empty() {
let chart = XyChart {
line_series: vec![1.0, 2.0, 3.0, 4.0],
..Default::default()
};
assert_eq!(chart.data_count(), 4);
}
#[test]
fn data_count_prefers_bar_over_line() {
let chart = XyChart {
bar_series: vec![1.0, 2.0],
line_series: vec![1.0, 2.0, 3.0],
..Default::default()
};
assert_eq!(chart.data_count(), 2);
}
#[test]
fn equality_holds_for_identical_charts() {
let a = XyChart {
title: Some("My Chart".to_string()),
bar_series: vec![1.0, 2.0, 3.0],
..Default::default()
};
let b = a.clone();
assert_eq!(a, b);
let c = XyChart {
title: Some("Other".to_string()),
..Default::default()
};
assert_ne!(a, c);
}
#[test]
fn x_axis_default_is_categorical_empty() {
let ax = XAxis::default();
match ax {
XAxis::Categorical { labels } => assert!(labels.is_empty()),
XAxis::Numeric { .. } => panic!("expected Categorical default"),
}
}
#[test]
fn y_axis_default_has_zero_range() {
let y = YAxis::default();
assert!(y.label.is_none());
assert!((y.min - 0.0).abs() < f64::EPSILON);
assert!((y.max - 0.0).abs() < f64::EPSILON);
}
}