mod channel_axis;
mod composite_axis;
mod coordinate_axis;
mod labelled_axis;
mod linear_axis;
mod logarithmic_axis;
mod numeric_axis;
mod tile_axis;
mod utils;
use ndarray::{Array1, SliceInfoElem};
use ordered_float::OrderedFloat;
use std::fmt::Debug;
pub use channel_axis::ChannelAxis;
pub use composite_axis::CompositeAxis;
pub use coordinate_axis::CoordinateAxis;
pub use labelled_axis::LabelledAxis;
pub use linear_axis::LinearAxis;
pub use logarithmic_axis::LogarithmicAxis;
pub use numeric_axis::NumericAxis;
pub use tile_axis::TileAxis;
pub use utils::{Monotonicity, SortOrder};
pub type Error = String;
#[ambassador::delegatable_trait]
pub trait Base: Debug {
fn size(&self) -> Result<usize, Error>;
fn slice(&self, slice_info: &SliceInfoElem) -> Result<ChannelAxis, Error>;
}
pub enum InterpolationType {
NearestNeighbor,
}
#[ambassador::delegatable_trait]
pub trait Numeric: Base {
fn name(&self) -> &str;
fn units(&self) -> &Option<String>;
fn coordinates(&self) -> Result<Array1<f64>, Error>;
fn make_monotonic(&self) -> Result<Vec<NumericAxis>, Error>;
fn monotonicity(&self) -> Result<Monotonicity, Error> {
let coordinates = self.coordinates()?;
if coordinates.len() <= 1 {
return Ok(Monotonicity {
order: SortOrder::Ascending,
strict: true,
});
}
let order = if coordinates.windows(2).into_iter().all(|w| w[0] <= w[1]) {
SortOrder::Ascending
} else if coordinates.windows(2).into_iter().all(|w| w[0] >= w[1]) {
SortOrder::Descending
} else {
SortOrder::Unsorted
};
let strict = coordinates.windows(2).into_iter().all(|w| w[0] != w[1]);
Ok(Monotonicity { order, strict })
}
fn interp(&self, coordinate: f64, interpolation_type: InterpolationType) -> Result<f64, Error> {
match interpolation_type {
InterpolationType::NearestNeighbor => {
let (nearest_index, _nearest_coordinate) = self
.coordinates()?
.iter()
.enumerate()
.min_by_key(|(_index, value)|
OrderedFloat((*value - coordinate).abs()))
.ok_or("Empty coordinates array".to_string())?;
Ok(nearest_index as f64)
}
}
}
fn rinterp(&self, index: f64, interpolation_type: InterpolationType) -> Result<f64, Error> {
match interpolation_type {
InterpolationType::NearestNeighbor => {
let coordinates = self.coordinates()?;
if coordinates.is_empty() {
return Err("Empty coordinates array".into());
}
let nearest_integer = (index.round() as usize).clamp(0, coordinates.len() - 1);
Ok(coordinates[nearest_integer])
}
}
}
fn bounds(&self) -> Result<[f64; 2], Error> {
let coordinates: Vec<_> = self.coordinates()?.into_iter().map(OrderedFloat).collect();
if coordinates.is_empty() {
return Err("Cannot calculate empty coordinates bounds".into());
}
Ok([
f64::from(*coordinates.iter().min().unwrap()),
f64::from(*coordinates.iter().max().unwrap()),
])
}
fn covers(&self, coordinate: f64) -> Result<bool, Error> {
let bounds = self.bounds()?;
Ok(coordinate >= bounds[0] && coordinate <= bounds[1])
}
}
pub trait OrderedNumeric: Numeric {
fn extents(&self) -> [f64; 2];
}