use std::ops::Range;
use crate::{
basis::Basis,
display::PolynomialDisplay,
statistics::{Confidence, ConfidenceBand, Tolerance},
value::{FloatClampedCast, Value},
CurveFit, Polynomial,
};
#[derive(Debug, Clone)]
pub enum PlottingElement<T: Value> {
Fit(Vec<(T, T)>, Vec<(T, ConfidenceBand<T>)>, String),
Canonical(Vec<(T, T)>, String),
Data(Vec<(T, T)>, Option<String>),
Markers(Vec<(T, T, Option<String>)>),
}
impl<T: Value> PlottingElement<T> {
pub fn from_data(data: impl Iterator<Item = (T, T)>, label: Option<String>) -> Self {
Self::Data(data.collect(), label)
}
pub fn from_markers(data: impl Iterator<Item = (T, T, Option<String>)>) -> Self {
Self::Markers(data.collect())
}
pub fn from_outliers(data: impl Iterator<Item = (T, T, ConfidenceBand<T>)>) -> Self {
let points = data
.map(|(x, y, cb)| {
(
x,
y,
Some(format!(
"Outlier: y={:.3}, band=({:.3},{:.3})",
y, cb.lower, cb.upper
)),
)
})
.collect();
Self::Markers(points)
}
pub fn from_polynomial<B: Basis<T> + PolynomialDisplay<T>>(
poly: &Polynomial<'_, B, T>,
xs: &[T],
) -> Self {
let data = poly.solve(xs.iter().copied());
let equation = poly.equation();
Self::Canonical(data, equation)
}
pub fn from_curve_fit<B: Basis<T> + PolynomialDisplay<T>>(
fit: &CurveFit<B, T>,
confidence: Confidence,
noise_tolerance: Option<Tolerance<T>>,
) -> Self {
let equation = fit.equation();
let raw_data = fit.data().to_vec();
let bands = fit
.covariance()
.and_then(|covariance| covariance.solution_confidence(confidence, noise_tolerance))
.unwrap_or_else(|_| {
fit.solution()
.iter()
.map(|(x, y)| {
(
*x,
ConfidenceBand {
level: Confidence::Custom(1.0),
tolerance: None,
value: *y,
lower: *y,
upper: *y,
},
)
})
.collect()
});
Self::Fit(raw_data, bands, equation)
}
#[must_use]
pub fn equation(&self) -> Option<String> {
match self {
PlottingElement::Fit(_, _, equation) | PlottingElement::Canonical(_, equation) => {
Some(equation.clone())
}
PlottingElement::Data(_, _) | PlottingElement::Markers(_) => None,
}
}
#[must_use]
pub fn x_values(&self) -> Vec<T> {
match self {
PlottingElement::Fit(data, _, _) => data.iter().map(|(x, _)| *x).collect(),
PlottingElement::Canonical(points, _) => points.iter().map(|(x, _)| *x).collect(),
PlottingElement::Data(points, _) => points.iter().map(|(x, _)| *x).collect(),
PlottingElement::Markers(points) => points.iter().map(|(x, _, _)| *x).collect(),
}
}
#[must_use]
pub fn x_range(&self) -> Range<T> {
match self {
PlottingElement::Fit(data, _, _)
| PlottingElement::Canonical(data, _)
| PlottingElement::Data(data, _) => match data.last() {
Some((x_max, _)) => data[0].0..*x_max,
None => T::zero()..T::one(),
},
PlottingElement::Markers(data) => match data.last() {
Some((x_max, _, _)) => data[0].0..*x_max,
None => T::zero()..T::one(),
},
}
}
#[must_use]
pub fn y_range(&self) -> Range<T> {
match self {
PlottingElement::Fit(_, bands, _) => {
let (mut min, mut max) = (None, None);
for (_, band) in bands {
min = Some(match min {
Some(m) => nalgebra::RealField::min(m, band.min()),
None => band.min(),
});
max = Some(match max {
Some(m) => nalgebra::RealField::max(m, band.max()),
None => band.max(),
});
}
let padding = padding_for_range(min, max);
if let (Some(min), Some(max)) = (min, max) {
(min - padding)..(max + padding)
} else {
T::zero()..T::one()
}
}
PlottingElement::Canonical(data, _) | PlottingElement::Data(data, _) => {
let (mut min, mut max) = (None, None);
for (_, y) in data {
min = Some(match min {
Some(m) => nalgebra::RealField::min(m, *y),
None => *y,
});
max = Some(match max {
Some(m) => nalgebra::RealField::max(m, *y),
None => *y,
});
}
let padding = padding_for_range(min, max);
if let (Some(min), Some(max)) = (min, max) {
(min - padding)..(max + padding)
} else {
T::zero()..T::one()
}
}
PlottingElement::Markers(data) => {
let (mut min, mut max) = (None, None);
for (_, y, _) in data {
min = Some(match min {
Some(m) => nalgebra::RealField::min(m, *y),
None => *y,
});
max = Some(match max {
Some(m) => nalgebra::RealField::max(m, *y),
None => *y,
});
}
let padding = padding_for_range(min, max);
if let (Some(min), Some(max)) = (min, max) {
(min - padding)..(max + padding)
} else {
T::zero()..T::one()
}
}
}
}
}
fn padding_for_range<T: Value>(min: Option<T>, max: Option<T>) -> T {
if let (Some(min), Some(max)) = (min, max) {
let range = max - min;
if range > T::zero() {
range * 0.1f64.clamped_cast()
} else {
T::one()
}
} else {
T::one()
}
}
pub trait AsPlottingElement<T: Value> {
fn as_plotting_element(
&self,
xs: &[T],
confidence: Confidence,
noise_tolerance: Option<Tolerance<T>>,
) -> PlottingElement<T>;
}
impl<T> AsPlottingElement<T> for (T, T)
where
T: Value,
{
fn as_plotting_element(
&self,
_: &[T],
_: Confidence,
_: Option<Tolerance<T>>,
) -> PlottingElement<T> {
PlottingElement::Markers(vec![(self.0, self.1, None)])
}
}
impl<T> AsPlottingElement<T> for (T, T, Option<String>)
where
T: Value,
{
fn as_plotting_element(
&self,
_: &[T],
_: Confidence,
_: Option<Tolerance<T>>,
) -> PlottingElement<T> {
PlottingElement::Markers(vec![(self.0, self.1, self.2.clone())])
}
}
impl<T> AsPlottingElement<T> for (T, T, String)
where
T: Value,
{
fn as_plotting_element(
&self,
_: &[T],
_: Confidence,
_: Option<Tolerance<T>>,
) -> PlottingElement<T> {
PlottingElement::Markers(vec![(self.0, self.1, Some(self.2.clone()))])
}
}
impl<T> AsPlottingElement<T> for PlottingElement<T>
where
T: Value,
{
fn as_plotting_element(
&self,
_: &[T],
_: Confidence,
_: Option<Tolerance<T>>,
) -> PlottingElement<T> {
self.clone()
}
}
impl<T> AsPlottingElement<T> for &PlottingElement<T>
where
T: Value,
{
fn as_plotting_element(
&self,
_: &[T],
_: Confidence,
_: Option<Tolerance<T>>,
) -> PlottingElement<T> {
(*self).clone()
}
}
impl<B, T> AsPlottingElement<T> for CurveFit<'_, B, T>
where
B: Basis<T>,
B: PolynomialDisplay<T>,
T: Value,
{
fn as_plotting_element(
&self,
_: &[T],
confidence: Confidence,
noise_tolerance: Option<Tolerance<T>>,
) -> PlottingElement<T> {
PlottingElement::from_curve_fit(self, confidence, noise_tolerance)
}
}
impl<B, T> AsPlottingElement<T> for Polynomial<'_, B, T>
where
B: Basis<T>,
B: PolynomialDisplay<T>,
T: Value,
{
fn as_plotting_element(
&self,
xs: &[T],
_: Confidence,
_: Option<Tolerance<T>>,
) -> PlottingElement<T> {
PlottingElement::from_polynomial(self, xs)
}
}
impl<T> AsPlottingElement<T> for &[(T, T)]
where
T: Value,
{
fn as_plotting_element(
&self,
_: &[T],
_: Confidence,
_: Option<Tolerance<T>>,
) -> PlottingElement<T> {
PlottingElement::from_data(self.iter().copied(), None)
}
}
impl<T> AsPlottingElement<T> for Vec<(T, T)>
where
T: Value,
{
fn as_plotting_element(
&self,
_: &[T],
_: Confidence,
_: Option<Tolerance<T>>,
) -> PlottingElement<T> {
PlottingElement::Data(self.clone(), None)
}
}
impl<T> AsPlottingElement<T> for (&Vec<(T, T)>, &str)
where
T: Value,
{
fn as_plotting_element(
&self,
_: &[T],
_: Confidence,
_: Option<Tolerance<T>>,
) -> PlottingElement<T> {
PlottingElement::Data(self.0.clone(), Some(self.1.to_string()))
}
}
impl<T> AsPlottingElement<T> for (&[(T, T)], &str)
where
T: Value,
{
fn as_plotting_element(
&self,
_: &[T],
_: Confidence,
_: Option<Tolerance<T>>,
) -> PlottingElement<T> {
PlottingElement::Data(self.0.to_vec(), Some(self.1.to_string()))
}
}