#![doc = include_str!("../README.md")]
use core::iter;
use plotters::coord::{
combinators::NestedValue,
ranged1d::{AsRangedCoord, DiscreteRanged, NoDefaultFormatting, Ranged, ValueFormatter},
};
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum UniformNestedValue<C, V> {
Category(C),
Value(C, V),
}
impl<C, V> UniformNestedValue<C, V> {
pub fn category(&self) -> &C {
match self {
UniformNestedValue::Category(c) => c,
UniformNestedValue::Value(c, _) => c,
}
}
pub fn nested_value(&self) -> Option<&V> {
match self {
UniformNestedValue::Category(_) => None,
UniformNestedValue::Value(_, v) => Some(v),
}
}
pub fn maybe_into_tuple(self) -> Option<(C, V)> {
match self {
UniformNestedValue::Category(_) => None,
UniformNestedValue::Value(c, v) => Some((c, v)),
}
}
}
impl<C, V> From<(C, V)> for UniformNestedValue<C, V> {
#[inline]
fn from((c, v): (C, V)) -> Self {
Self::Value(c, v)
}
}
pub struct UniformNestedRange<Primary: DiscreteRanged, Secondary: Ranged> {
primary_coordinate_system: Primary,
secondary_coordinate_system: Secondary,
}
impl<PCoordType, SCoordType, P, S> ValueFormatter<NestedValue<PCoordType, SCoordType>>
for UniformNestedRange<P, S>
where
P: Ranged<ValueType = PCoordType> + DiscreteRanged,
S: Ranged<ValueType = SCoordType>,
P: ValueFormatter<PCoordType>,
S: ValueFormatter<SCoordType>,
{
fn format(value: &NestedValue<PCoordType, SCoordType>) -> String {
match value {
NestedValue::Category(c) => P::format(c),
NestedValue::Value(_, v) => S::format(v),
}
}
}
impl<P: DiscreteRanged, S: Ranged> Ranged for UniformNestedRange<P, S> {
type FormatOption = NoDefaultFormatting;
type ValueType = UniformNestedValue<P::ValueType, S::ValueType>;
fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 {
let index_in_primary = self
.primary_coordinate_system
.index_of(value.category())
.unwrap_or(0);
let total_primary_buckets = self.primary_coordinate_system.size();
let uniform_real_coordinates_buckets_delta =
(limit.1 - limit.0) / total_primary_buckets as i32;
let mut residual_extra_buckets_delta = (limit.1 - limit.0) % total_primary_buckets as i32;
if residual_extra_buckets_delta < 0 {
residual_extra_buckets_delta += uniform_real_coordinates_buckets_delta;
}
let secondary_bucket_left = limit.0
+ uniform_real_coordinates_buckets_delta * index_in_primary as i32
+ residual_extra_buckets_delta.min(index_in_primary as i32);
let secondary_bucket_right = secondary_bucket_left
+ uniform_real_coordinates_buckets_delta
+ if (residual_extra_buckets_delta as usize) < index_in_primary {
1
} else {
0
};
match value.nested_value() {
Some(v) => self
.secondary_coordinate_system
.map(v, (secondary_bucket_left, secondary_bucket_right)),
None => (secondary_bucket_left + secondary_bucket_right) / 2,
}
}
fn key_points<Hint: plotters::coord::ranged1d::KeyPointHint>(
&self,
hint: Hint,
) -> Vec<Self::ValueType> {
if !hint.weight().allow_light_points()
|| hint.max_num_points() < self.primary_coordinate_system.size() * 2
{
self.primary_coordinate_system
.key_points(hint)
.into_iter()
.map(UniformNestedValue::Category)
.collect()
} else {
let per_secondary_size = (hint.max_num_points()
- self.primary_coordinate_system.size())
/ self.primary_coordinate_system.size();
self.primary_coordinate_system
.values()
.enumerate()
.flat_map(|(category_idx, value)| {
iter::once(UniformNestedValue::Category(value)).chain(
self.secondary_coordinate_system
.key_points(per_secondary_size)
.into_iter()
.map(move |secondary_value| {
(
self.primary_coordinate_system
.from_index(category_idx)
.expect(
"Invalid index even though we iterated over allowed values",
),
secondary_value,
)
.into()
}),
)
})
.collect()
}
}
fn range(&self) -> core::ops::Range<Self::ValueType> {
let primary_range = self.primary_coordinate_system.range();
let secondary_range = self.secondary_coordinate_system.range();
(primary_range.start, secondary_range.start).into()
..(primary_range.end, secondary_range.end).into()
}
}
impl<Primary: DiscreteRanged, Secondary: DiscreteRanged> DiscreteRanged
for UniformNestedRange<Primary, Secondary>
{
#[inline]
fn size(&self) -> usize {
self.primary_coordinate_system.size() * self.secondary_coordinate_system.size()
}
fn index_of(&self, value: &Self::ValueType) -> Option<usize> {
let primary_index = self.primary_coordinate_system.index_of(value.category())?;
let secondary_size = self.secondary_coordinate_system.size();
let secondary_index = self
.secondary_coordinate_system
.index_of(value.nested_value()?)?;
Some(secondary_size * primary_index + secondary_index)
}
fn from_index(&self, index: usize) -> Option<Self::ValueType> {
let secondary_size = self.secondary_coordinate_system.size();
let primary_index = index / secondary_size;
let secondary_index = index % secondary_size;
let category = self.primary_coordinate_system.from_index(primary_index)?;
let value = self
.secondary_coordinate_system
.from_index(secondary_index)?;
Some((category, value).into())
}
}
pub trait BuildUniformNestedCoord: AsRangedCoord
where
Self::CoordDescType: DiscreteRanged,
{
fn uniform_nested_coord_with<Secondary: AsRangedCoord>(
self,
make_secondary: impl FnOnce() -> Secondary,
) -> UniformNestedRange<Self::CoordDescType, Secondary::CoordDescType> {
let primary: Self::CoordDescType = self.into();
assert!(primary.size() > 0);
let secondary: Secondary::CoordDescType = make_secondary().into();
UniformNestedRange {
primary_coordinate_system: primary,
secondary_coordinate_system: secondary,
}
}
fn uniform_nested_coord<Secondary: AsRangedCoord>(
self,
secondary: Secondary,
) -> UniformNestedRange<Self::CoordDescType, Secondary::CoordDescType> {
let primary: Self::CoordDescType = self.into();
assert!(primary.size() > 0);
let secondary: Secondary::CoordDescType = secondary.into();
UniformNestedRange {
primary_coordinate_system: primary,
secondary_coordinate_system: secondary,
}
}
}
impl<T: AsRangedCoord> BuildUniformNestedCoord for T where T::CoordDescType: DiscreteRanged {}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_uniform_nested_coord() {
let coord = (0..10).uniform_nested_coord(0..39);
let range = coord.range();
assert_eq!((0, 0).into()..(10, 39).into(), range);
assert_eq!(coord.size(), 11 * 40);
assert_eq!(coord.map(&UniformNestedValue::Category(0), (0, 1100)), 50);
assert_eq!(coord.map(&(0, 0).into(), (0, 1100)), 0);
assert_eq!(coord.map(&(0, 2).into(), (0, 1100)), 5);
assert_eq!(coord.map(&(5, 19).into(), (0, 1100)), 549);
assert_eq!(coord.from_index(40 * 3 + 8).unwrap(), (3, 8).into());
assert_eq!(coord.index_of(&(10, 39).into()).unwrap(), 439);
}
}
pub mod prelude {
pub use super::{BuildUniformNestedCoord, UniformNestedRange, UniformNestedValue};
}