aboutsummaryrefslogtreecommitdiff
path: root/src/coord/ranged1d/discrete.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/coord/ranged1d/discrete.rs')
-rw-r--r--src/coord/ranged1d/discrete.rs270
1 files changed, 270 insertions, 0 deletions
diff --git a/src/coord/ranged1d/discrete.rs b/src/coord/ranged1d/discrete.rs
new file mode 100644
index 0000000..5797dce
--- /dev/null
+++ b/src/coord/ranged1d/discrete.rs
@@ -0,0 +1,270 @@
+use crate::coord::ranged1d::{
+ AsRangedCoord, KeyPointHint, NoDefaultFormatting, Ranged, ReversibleRanged, ValueFormatter,
+};
+use std::ops::Range;
+
+/// The trait indicates the coordinate is discrete
+/// This means we can bidirectionally map the range value to 0 to N
+/// in which N is the number of distinct values of the range.
+///
+/// This is useful since for a histgoram, this is an abstraction of bucket.
+pub trait DiscreteRanged
+where
+ Self: Ranged,
+{
+ /// Get the number of element in the range
+ /// Note: we assume that all the ranged discrete coordinate has finite value
+ ///
+ /// - **returns** The number of values in the range
+ fn size(&self) -> usize;
+
+ /// Map a value to the index
+ ///
+ /// Note: This function doesn't guareentee return None when the value is out of range.
+ /// The only way to confirm the value is in the range is to examing the return value isn't
+ /// larger than self.size.
+ ///
+ /// - `value`: The value to map
+ /// - **returns** The index of the value
+ fn index_of(&self, value: &Self::ValueType) -> Option<usize>;
+
+ /// Reverse map the index to the value
+ ///
+ /// Note: This function doesn't guareentee returning None when the index is out of range.
+ ///
+ /// - `value`: The index to map
+ /// - **returns** The value
+ fn from_index(&self, index: usize) -> Option<Self::ValueType>;
+
+ /// Return a iterator that iterates over the all possible values
+ ///
+ /// - **returns** The value iterator
+ fn values(&self) -> DiscreteValueIter<'_, Self>
+ where
+ Self: Sized,
+ {
+ DiscreteValueIter(self, 0, self.size())
+ }
+
+ /// Returns the previous value in this range
+ ///
+ /// Normally, it's based on the `from_index` and `index_of` function. But for
+ /// some of the coord spec, it's possible that we value faster implementation.
+ /// If this is the case, we can impelemnet the type specific impl for the `previous`
+ /// and `next`.
+ ///
+ /// - `value`: The current value
+ /// - **returns**: The value piror to current value
+ fn previous(&self, value: &Self::ValueType) -> Option<Self::ValueType> {
+ if let Some(idx) = self.index_of(value) {
+ if idx > 0 {
+ return self.from_index(idx - 1);
+ }
+ }
+ None
+ }
+
+ /// Returns the next value in this range
+ ///
+ /// Normally, it's based on the `from_index` and `index_of` function. But for
+ /// some of the coord spec, it's possible that we value faster implementation.
+ /// If this is the case, we can impelemnet the type specific impl for the `previous`
+ /// and `next`.
+ ///
+ /// - `value`: The current value
+ /// - **returns**: The value next to current value
+ fn next(&self, value: &Self::ValueType) -> Option<Self::ValueType> {
+ if let Some(idx) = self.index_of(value) {
+ if idx + 1 < self.size() {
+ return self.from_index(idx + 1);
+ }
+ }
+ None
+ }
+}
+
+/// A `SegmentedCoord` is a decorator on any discrete coordinate specification.
+/// This decorator will convert the discrete coordiante in two ways:
+/// - Add an extra dummy element after all the values in origianl discrete coordinate
+/// - Logically each value `v` from original coordinate system is mapped into an segment `[v, v+1)` where `v+1` denotes the sucessor of the `v`
+/// - Introduce two types of values `SegmentValue::Exact(value)` which denotes the left end of value's segment and `SegmentValue::CenterOf(value)` which refers the center of the segment.
+/// This is used in histogram types, which uses a discrete coordinate as the buckets. The segmented coord always emits `CenterOf(value)` key points, thus it allows all the label and tick marks
+/// of the coordinate rendered in the middle of each segment.
+/// The coresponding trait [IntoSegmentedCoord](trait.IntoSegmentedCoord.html) is used to apply this decorator to coordinates.
+#[derive(Clone)]
+pub struct SegmentedCoord<D: DiscreteRanged>(D);
+
+/// The trait for types that can decorated by [SegmentedCoord](struct.SegmentedCoord.html) decorator.
+pub trait IntoSegmentedCoord: AsRangedCoord
+where
+ Self::CoordDescType: DiscreteRanged,
+{
+ /// Convert current ranged value into a segmented coordinate
+ fn into_segmented(self) -> SegmentedCoord<Self::CoordDescType> {
+ SegmentedCoord(self.into())
+ }
+}
+
+impl<R: AsRangedCoord> IntoSegmentedCoord for R where R::CoordDescType: DiscreteRanged {}
+
+/// The value that used by the segmented coordinate.
+#[derive(Clone, Debug)]
+pub enum SegmentValue<T> {
+ /// Means we are referring the exact position of value `T`
+ Exact(T),
+ /// Means we are referring the center of position `T` and the successor of `T`
+ CenterOf(T),
+ /// Referring the last dummy element
+ Last,
+}
+
+impl<T, D: DiscreteRanged + Ranged<ValueType = T>> ValueFormatter<SegmentValue<T>>
+ for SegmentedCoord<D>
+where
+ D: ValueFormatter<T>,
+{
+ fn format(value: &SegmentValue<T>) -> String {
+ match value {
+ SegmentValue::Exact(ref value) => D::format(value),
+ SegmentValue::CenterOf(ref value) => D::format(value),
+ _ => "".to_string(),
+ }
+ }
+}
+
+impl<D: DiscreteRanged> Ranged for SegmentedCoord<D> {
+ type FormatOption = NoDefaultFormatting;
+ type ValueType = SegmentValue<D::ValueType>;
+
+ fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 {
+ let margin = ((limit.1 - limit.0) as f32 / self.0.size() as f32).round() as i32;
+
+ match value {
+ SegmentValue::Exact(coord) => self.0.map(coord, (limit.0, limit.1 - margin)),
+ SegmentValue::CenterOf(coord) => {
+ let left = self.0.map(coord, (limit.0, limit.1 - margin));
+ if let Some(idx) = self.0.index_of(coord) {
+ if idx + 1 < self.0.size() {
+ let right = self.0.map(
+ &self.0.from_index(idx + 1).unwrap(),
+ (limit.0, limit.1 - margin),
+ );
+ return (left + right) / 2;
+ }
+ }
+ left + margin / 2
+ }
+ SegmentValue::Last => limit.1,
+ }
+ }
+
+ fn key_points<HintType: KeyPointHint>(&self, hint: HintType) -> Vec<Self::ValueType> {
+ self.0
+ .key_points(hint)
+ .into_iter()
+ .map(SegmentValue::CenterOf)
+ .collect()
+ }
+
+ fn range(&self) -> Range<Self::ValueType> {
+ let range = self.0.range();
+ SegmentValue::Exact(range.start)..SegmentValue::Exact(range.end)
+ }
+}
+
+impl<D: DiscreteRanged> DiscreteRanged for SegmentedCoord<D> {
+ fn size(&self) -> usize {
+ self.0.size() + 1
+ }
+
+ fn index_of(&self, value: &Self::ValueType) -> Option<usize> {
+ match value {
+ SegmentValue::Exact(value) => self.0.index_of(value),
+ SegmentValue::CenterOf(value) => self.0.index_of(value),
+ SegmentValue::Last => Some(self.0.size()),
+ }
+ }
+
+ fn from_index(&self, idx: usize) -> Option<Self::ValueType> {
+ match idx {
+ idx if idx < self.0.size() => self.0.from_index(idx).map(SegmentValue::Exact),
+ idx if idx == self.0.size() => Some(SegmentValue::Last),
+ _ => None,
+ }
+ }
+}
+
+impl<T> From<T> for SegmentValue<T> {
+ fn from(this: T) -> SegmentValue<T> {
+ SegmentValue::Exact(this)
+ }
+}
+
+impl<DC: DiscreteRanged> ReversibleRanged for DC {
+ fn unmap(&self, input: i32, limit: (i32, i32)) -> Option<Self::ValueType> {
+ let idx = (f64::from(input - limit.0) * (self.size() as f64) / f64::from(limit.1 - limit.0))
+ .floor() as usize;
+ self.from_index(idx)
+ }
+}
+
+/// The iterator that can be used to iterate all the values defined by a discrete coordinate
+pub struct DiscreteValueIter<'a, T: DiscreteRanged>(&'a T, usize, usize);
+
+impl<'a, T: DiscreteRanged> Iterator for DiscreteValueIter<'a, T> {
+ type Item = T::ValueType;
+ fn next(&mut self) -> Option<T::ValueType> {
+ if self.1 >= self.2 {
+ return None;
+ }
+ let idx = self.1;
+ self.1 += 1;
+ self.0.from_index(idx)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ #[test]
+ fn test_value_iter() {
+ let range: crate::coord::ranged1d::types::RangedCoordi32 = (-10..10).into();
+
+ let values: Vec<_> = range.values().collect();
+
+ assert_eq!(21, values.len());
+
+ for (expected, value) in (-10..=10).zip(values) {
+ assert_eq!(expected, value);
+ }
+ assert_eq!(range.next(&5), Some(6));
+ assert_eq!(range.next(&10), None);
+ assert_eq!(range.previous(&-10), None);
+ assert_eq!(range.previous(&10), Some(9));
+ }
+
+ #[test]
+ fn test_centric_coord() {
+ let coord = (0..10).into_segmented();
+
+ assert_eq!(coord.size(), 12);
+ for i in 0..=11 {
+ match coord.from_index(i as usize) {
+ Some(SegmentValue::Exact(value)) => assert_eq!(i, value),
+ Some(SegmentValue::Last) => assert_eq!(i, 11),
+ _ => panic!(),
+ }
+ }
+
+ for (kps, idx) in coord.key_points(20).into_iter().zip(0..) {
+ match kps {
+ SegmentValue::CenterOf(value) if value <= 10 => assert_eq!(value, idx),
+ _ => panic!(),
+ }
+ }
+
+ assert_eq!(coord.map(&SegmentValue::CenterOf(0), (0, 24)), 1);
+ assert_eq!(coord.map(&SegmentValue::Exact(0), (0, 24)), 0);
+ assert_eq!(coord.map(&SegmentValue::Exact(1), (0, 24)), 2);
+ }
+}