use super::Point2D;
use crate::error::Result;
pub trait DataSeries {
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn point(&self, index: usize) -> Result<Point2D>;
fn points(&self) -> Box<dyn Iterator<Item = Point2D> + '_>;
}
#[derive(Debug, Clone)]
pub struct Series {
x: Vec<f64>,
y: Vec<f64>,
}
impl Series {
pub fn new(x: Vec<f64>, y: Vec<f64>) -> Result<Self> {
if x.len() != y.len() {
return Err(crate::error::Error::InvalidData(format!(
"x and y must have same length (got {} and {})",
x.len(),
y.len()
)));
}
Ok(Self { x, y })
}
#[must_use]
pub fn from_tuples(data: &[(f64, f64)]) -> Self {
let (x, y): (Vec<_>, Vec<_>) = data.iter().copied().unzip();
Self { x, y }
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn from_function<F>(x_min: f64, x_max: f64, n_points: usize, f: F) -> Self
where
F: Fn(f64) -> f64,
{
let dx = (x_max - x_min) / (n_points - 1) as f64;
let x: Vec<f64> = (0..n_points).map(|i| x_min + i as f64 * dx).collect();
let y: Vec<f64> = x.iter().map(|&xi| f(xi)).collect();
Self { x, y }
}
}
impl DataSeries for Series {
fn len(&self) -> usize {
self.x.len()
}
fn point(&self, index: usize) -> Result<Point2D> {
if index >= self.len() {
return Err(crate::error::Error::InvalidData(format!(
"Index {} out of bounds (len = {})",
index,
self.len()
)));
}
Ok(Point2D::new(self.x[index], self.y[index]))
}
fn points(&self) -> Box<dyn Iterator<Item = Point2D> + '_> {
Box::new(
self.x
.iter()
.zip(self.y.iter())
.map(|(&x, &y)| Point2D::new(x, y)),
)
}
}