diff options
Diffstat (limited to 'vendor/plotters/src/coord')
20 files changed, 4308 insertions, 0 deletions
diff --git a/vendor/plotters/src/coord/mod.rs b/vendor/plotters/src/coord/mod.rs new file mode 100644 index 000000000..574929d4c --- /dev/null +++ b/vendor/plotters/src/coord/mod.rs @@ -0,0 +1,73 @@ +/*! + +One of the key features of Plotters is flexible coordinate system abstraction and this module +provides all the abstraction used for the coordinate abstarction of Plotters. + +Generally speaking, the coordinate system in Plotters is responsible for mapping logic data points into +pixel based backend coordinate. This task is abstracted by a simple trait called +[CoordTranslate](trait.CoordTranslate.html). Please note `CoordTranslate` trait doesn't assume any property +about the coordinate values, thus we are able to extend Plotters's coordinate system to other types of coorindate +easily. + +Another important trait is [ReverseCoordTranslate](trait.ReverseCoordTranslate.html). This trait allows some coordinate +retrieve the logic value based on the pixel-based backend coordinate. This is particularly interesting for interactive plots. + +Plotters contains a set of pre-defined coordinate specifications that fulfills the most common use. See documentation for +module [types](types/index.html) for details about the basic 1D types. + +The coordinate system also can be tweaked by the coordinate combinators, such as logarithmic coordinate, nested coordinate, etc. +See documentation for module [combinators](combinators/index.html) for details. + +Currently we support the following 2D coordinate system: + +- 2-dimensional Cartesian Coordinate: This is done by the combinator [Cartesian2d](cartesian/struct.Cartesian2d.html). + +*/ + +use plotters_backend::BackendCoord; + +pub mod ranged1d; + +/// The coordinate combinators +/// +/// Coordinate combinators are very important part of Plotters' coordinate system. +/// The combinator is more about the "combinator pattern", which takes one or more coordinate specification +/// and transform them into a new coordinate specification. +pub mod combinators { + pub use super::ranged1d::combinators::*; +} + +/// The primitive types supported by Plotters coordinate system +pub mod types { + pub use super::ranged1d::types::*; +} + +mod ranged2d; +/// Ranged coordinates in 3d. +pub mod ranged3d; + +/// Groups Cartesian ranged coordinates in 2d and 3d. +pub mod cartesian { + pub use super::ranged2d::cartesian::{Cartesian2d, MeshLine}; + pub use super::ranged3d::Cartesian3d; +} + +mod translate; +pub use translate::{CoordTranslate, ReverseCoordTranslate}; + +/// The coordinate translation that only impose shift +#[derive(Debug, Clone)] +pub struct Shift(pub BackendCoord); + +impl CoordTranslate for Shift { + type From = BackendCoord; + fn translate(&self, from: &Self::From) -> BackendCoord { + (from.0 + (self.0).0, from.1 + (self.0).1) + } +} + +impl ReverseCoordTranslate for Shift { + fn reverse_translate(&self, input: BackendCoord) -> Option<BackendCoord> { + Some((input.0 - (self.0).0, input.1 - (self.0).1)) + } +} diff --git a/vendor/plotters/src/coord/ranged1d/combinators/ckps.rs b/vendor/plotters/src/coord/ranged1d/combinators/ckps.rs new file mode 100644 index 000000000..5e1ed72bd --- /dev/null +++ b/vendor/plotters/src/coord/ranged1d/combinators/ckps.rs @@ -0,0 +1,268 @@ +// The customized coordinate combinators. +// This file contains a set of coorindate combinators that allows you determine the +// keypoint by your own code. +use std::ops::Range; + +use crate::coord::ranged1d::{AsRangedCoord, DiscreteRanged, KeyPointHint, Ranged}; + +/// The coordinate decorator that binds a key point vector. +/// Normally, all the ranged coordinate implements its own keypoint algorithm +/// to determine how to render the tick mark and mesh grid. +/// This decorator allows customized tick mark specifiied by vector. +/// See [BindKeyPoints::with_key_points](trait.BindKeyPoints.html#tymethod.with_key_points) +/// for details. +/// Note: For any coordinate spec wrapped by this decorator, the maxium number of labels configured by +/// MeshStyle will be ignored and the key point function will always returns the entire vector +pub struct WithKeyPoints<Inner: Ranged> { + inner: Inner, + bold_points: Vec<Inner::ValueType>, + light_points: Vec<Inner::ValueType>, +} + +impl<I: Ranged> WithKeyPoints<I> { + /// Specify the light key points, which is used to render the light mesh line + pub fn with_light_points<T: IntoIterator<Item = I::ValueType>>(mut self, iter: T) -> Self { + self.light_points.clear(); + self.light_points.extend(iter); + self + } + + /// Get a reference to the bold points + pub fn bold_points(&self) -> &[I::ValueType] { + self.bold_points.as_ref() + } + + /// Get a mut reference to the bold points + pub fn bold_points_mut(&mut self) -> &mut [I::ValueType] { + self.bold_points.as_mut() + } + + /// Get a reference to light key points + pub fn light_points(&self) -> &[I::ValueType] { + self.light_points.as_ref() + } + + /// Get a mut reference to the light key points + pub fn light_points_mut(&mut self) -> &mut [I::ValueType] { + self.light_points.as_mut() + } +} + +impl<R: Ranged> Ranged for WithKeyPoints<R> +where + R::ValueType: Clone, +{ + type ValueType = R::ValueType; + type FormatOption = R::FormatOption; + + fn range(&self) -> Range<Self::ValueType> { + self.inner.range() + } + + fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { + self.inner.map(value, limit) + } + + fn key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<Self::ValueType> { + if hint.weight().allow_light_points() { + self.light_points.clone() + } else { + self.bold_points.clone() + } + } + + fn axis_pixel_range(&self, limit: (i32, i32)) -> Range<i32> { + self.inner.axis_pixel_range(limit) + } +} + +impl<R: DiscreteRanged> DiscreteRanged for WithKeyPoints<R> +where + R::ValueType: Clone, +{ + fn size(&self) -> usize { + self.inner.size() + } + fn index_of(&self, value: &Self::ValueType) -> Option<usize> { + self.inner.index_of(value) + } + fn from_index(&self, index: usize) -> Option<Self::ValueType> { + self.inner.from_index(index) + } +} + +/// Bind a existing coordinate spec with a given key points vector. See [WithKeyPoints](struct.WithKeyPoints.html ) for more details. +pub trait BindKeyPoints +where + Self: AsRangedCoord, +{ + /// Bind a existing coordinate spec with a given key points vector. See [WithKeyPoints](struct.WithKeyPoints.html ) for more details. + /// Example: + /// ``` + ///use plotters::prelude::*; + ///use plotters_bitmap::BitMapBackend; + ///let mut buffer = vec![0;1024*768*3]; + /// let root = BitMapBackend::with_buffer(&mut buffer, (1024, 768)).into_drawing_area(); + /// let mut chart = ChartBuilder::on(&root) + /// .build_cartesian_2d( + /// (0..100).with_key_points(vec![1,20,50,90]), // <= This line will make the plot shows 4 tick marks at 1, 20, 50, 90 + /// 0..10 + /// ).unwrap(); + /// chart.configure_mesh().draw().unwrap(); + ///``` + fn with_key_points(self, points: Vec<Self::Value>) -> WithKeyPoints<Self::CoordDescType> { + WithKeyPoints { + inner: self.into(), + bold_points: points, + light_points: vec![], + } + } +} + +impl<T: AsRangedCoord> BindKeyPoints for T {} + +/// The coordinate decorator that allows customized keypoint algorithms. +/// Normally, all the coordinate spec implements its own key point algorith +/// But this decorator allows you override the pre-defined key point algorithm. +/// +/// To use this decorator, see [BindKeyPointMethod::with_key_point_func](trait.BindKeyPointMethod.html#tymethod.with_key_point_func) +pub struct WithKeyPointMethod<R: Ranged> { + inner: R, + bold_func: Box<dyn Fn(usize) -> Vec<R::ValueType>>, + light_func: Box<dyn Fn(usize) -> Vec<R::ValueType>>, +} + +/// Bind an existing coordinate spec with a given key points algorithm. See [WithKeyPointMethod](struct.WithKeyMethod.html ) for more details. +pub trait BindKeyPointMethod +where + Self: AsRangedCoord, +{ + /// Bind a existing coordinate spec with a given key points algorithm. See [WithKeyPointMethod](struct.WithKeyMethod.html ) for more details. + /// Example: + /// ``` + ///use plotters::prelude::*; + ///use plotters_bitmap::BitMapBackend; + ///let mut buffer = vec![0;1024*768*3]; + /// let root = BitMapBackend::with_buffer(&mut buffer, (1024, 768)).into_drawing_area(); + /// let mut chart = ChartBuilder::on(&root) + /// .build_cartesian_2d( + /// (0..100).with_key_point_func(|n| (0..100 / n as i32).map(|x| x * 100 / n as i32).collect()), + /// 0..10 + /// ).unwrap(); + /// chart.configure_mesh().draw().unwrap(); + ///``` + fn with_key_point_func<F: Fn(usize) -> Vec<Self::Value> + 'static>( + self, + func: F, + ) -> WithKeyPointMethod<Self::CoordDescType> { + WithKeyPointMethod { + inner: self.into(), + bold_func: Box::new(func), + light_func: Box::new(|_| Vec::new()), + } + } +} + +impl<T: AsRangedCoord> BindKeyPointMethod for T {} + +impl<R: Ranged> WithKeyPointMethod<R> { + /// Define the light key point algorithm, by default this returns an empty set + pub fn with_light_point_func<F: Fn(usize) -> Vec<R::ValueType> + 'static>( + mut self, + func: F, + ) -> Self { + self.light_func = Box::new(func); + self + } +} + +impl<R: Ranged> Ranged for WithKeyPointMethod<R> { + type ValueType = R::ValueType; + type FormatOption = R::FormatOption; + + fn range(&self) -> Range<Self::ValueType> { + self.inner.range() + } + + fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { + self.inner.map(value, limit) + } + + fn key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<Self::ValueType> { + if hint.weight().allow_light_points() { + (self.light_func)(hint.max_num_points()) + } else { + (self.bold_func)(hint.max_num_points()) + } + } + + fn axis_pixel_range(&self, limit: (i32, i32)) -> Range<i32> { + self.inner.axis_pixel_range(limit) + } +} + +impl<R: DiscreteRanged> DiscreteRanged for WithKeyPointMethod<R> { + fn size(&self) -> usize { + self.inner.size() + } + fn index_of(&self, value: &Self::ValueType) -> Option<usize> { + self.inner.index_of(value) + } + fn from_index(&self, index: usize) -> Option<Self::ValueType> { + self.inner.from_index(index) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::coord::ranged1d::{BoldPoints, LightPoints}; + #[test] + fn test_with_key_points() { + let range = (0..100).with_key_points(vec![1, 2, 3]); + assert_eq!(range.map(&3, (0, 1000)), 30); + assert_eq!(range.range(), 0..100); + assert_eq!(range.key_points(BoldPoints(100)), vec![1, 2, 3]); + assert_eq!(range.key_points(LightPoints::new(100, 100)), vec![]); + let range = range.with_light_points(5..10); + assert_eq!(range.key_points(BoldPoints(10)), vec![1, 2, 3]); + assert_eq!( + range.key_points(LightPoints::new(10, 10)), + (5..10).collect::<Vec<_>>() + ); + + assert_eq!(range.size(), 101); + assert_eq!(range.index_of(&10), Some(10)); + assert_eq!(range.from_index(10), Some(10)); + + assert_eq!(range.axis_pixel_range((0, 1000)), 0..1000); + + let mut range = range; + + assert_eq!(range.light_points().len(), 5); + assert_eq!(range.light_points_mut().len(), 5); + assert_eq!(range.bold_points().len(), 3); + assert_eq!(range.bold_points_mut().len(), 3); + } + + #[test] + fn test_with_key_point_method() { + let range = (0..100).with_key_point_func(|_| vec![1, 2, 3]); + assert_eq!(range.map(&3, (0, 1000)), 30); + assert_eq!(range.range(), 0..100); + assert_eq!(range.key_points(BoldPoints(100)), vec![1, 2, 3]); + assert_eq!(range.key_points(LightPoints::new(100, 100)), vec![]); + let range = range.with_light_point_func(|_| (5..10).collect()); + assert_eq!(range.key_points(BoldPoints(10)), vec![1, 2, 3]); + assert_eq!( + range.key_points(LightPoints::new(10, 10)), + (5..10).collect::<Vec<_>>() + ); + + assert_eq!(range.size(), 101); + assert_eq!(range.index_of(&10), Some(10)); + assert_eq!(range.from_index(10), Some(10)); + + assert_eq!(range.axis_pixel_range((0, 1000)), 0..1000); + } +} diff --git a/vendor/plotters/src/coord/ranged1d/combinators/group_by.rs b/vendor/plotters/src/coord/ranged1d/combinators/group_by.rs new file mode 100644 index 000000000..5c0a4e567 --- /dev/null +++ b/vendor/plotters/src/coord/ranged1d/combinators/group_by.rs @@ -0,0 +1,119 @@ +use crate::coord::ranged1d::{ + AsRangedCoord, DiscreteRanged, KeyPointHint, NoDefaultFormatting, Ranged, ValueFormatter, +}; +use std::ops::Range; + +/// Grouping the value in the coordinate specification. +/// +/// This combinator doesn't change the coordinate mapping behavior. But it changes how +/// the key point is generated, this coordinate specification will enforce that only the first value in each group +/// can be emitted as the bold key points. +/// +/// This is useful, for example, when we have an X axis is a integer and denotes days. +/// And we are expecting the tick mark denotes weeks, in this way we can make the range +/// spec grouping by 7 elements. +/// With the help of the GroupBy decorator, this can be archived quite easily: +///```rust +///use plotters::prelude::*; +///let mut buf = vec![0;1024*768*3]; +///let area = BitMapBackend::with_buffer(buf.as_mut(), (1024, 768)).into_drawing_area(); +///let chart = ChartBuilder::on(&area) +/// .build_cartesian_2d((0..100).group_by(7), 0..100) +/// .unwrap(); +///``` +/// +/// To apply this combinator, call [ToGroupByRange::group_by](trait.ToGroupByRange.html#tymethod.group_by) method on any discrete coordinate spec. +#[derive(Clone)] +pub struct GroupBy<T: DiscreteRanged>(T, usize); + +/// The trait that provides method `Self::group_by` function which creates a +/// `GroupBy` decorated ranged value. +pub trait ToGroupByRange: AsRangedCoord + Sized +where + Self::CoordDescType: DiscreteRanged, +{ + /// Make a grouping ranged value, see the documentation for `GroupBy` for details. + /// + /// - `value`: The number of values we want to group it + /// - **return**: The newly created grouping range specification + fn group_by(self, value: usize) -> GroupBy<<Self as AsRangedCoord>::CoordDescType> { + GroupBy(self.into(), value) + } +} + +impl<T: AsRangedCoord + Sized> ToGroupByRange for T where T::CoordDescType: DiscreteRanged {} + +impl<T: DiscreteRanged> DiscreteRanged for GroupBy<T> { + fn size(&self) -> usize { + (self.0.size() + self.1 - 1) / self.1 + } + fn index_of(&self, value: &Self::ValueType) -> Option<usize> { + self.0.index_of(value).map(|idx| idx / self.1) + } + fn from_index(&self, index: usize) -> Option<Self::ValueType> { + self.0.from_index(index * self.1) + } +} + +impl<T, R: DiscreteRanged<ValueType = T> + ValueFormatter<T>> ValueFormatter<T> for GroupBy<R> { + fn format(value: &T) -> String { + R::format(value) + } +} + +impl<T: DiscreteRanged> Ranged for GroupBy<T> { + type FormatOption = NoDefaultFormatting; + type ValueType = T::ValueType; + fn map(&self, value: &T::ValueType, limit: (i32, i32)) -> i32 { + self.0.map(value, limit) + } + fn range(&self) -> Range<T::ValueType> { + self.0.range() + } + // TODO: See issue issue #88 + fn key_points<HintType: KeyPointHint>(&self, hint: HintType) -> Vec<T::ValueType> { + let range = 0..(self.0.size() + self.1) / self.1; + //let logic_range: RangedCoordusize = range.into(); + + let interval = + ((range.end - range.start + hint.bold_points() - 1) / hint.bold_points()).max(1); + let count = (range.end - range.start) / interval; + + let idx_iter = (0..hint.bold_points()).map(|x| x * interval); + + if hint.weight().allow_light_points() && count < hint.bold_points() * 2 { + let outter_ticks = idx_iter; + let outter_tick_size = interval * self.1; + let inner_ticks_per_group = hint.max_num_points() / outter_ticks.len(); + let inner_ticks = + (outter_tick_size + inner_ticks_per_group - 1) / inner_ticks_per_group; + let inner_ticks: Vec<_> = (0..(outter_tick_size / inner_ticks)) + .map(move |x| x * inner_ticks) + .collect(); + let size = self.0.size(); + return outter_ticks + .flat_map(|base| inner_ticks.iter().map(move |&ofs| base * self.1 + ofs)) + .take_while(|&idx| idx < size) + .map(|x| self.0.from_index(x).unwrap()) + .collect(); + } + + idx_iter + .map(|x| self.0.from_index(x * self.1).unwrap()) + .collect() + } +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_group_by() { + let coord = (0..100).group_by(10); + assert_eq!(coord.size(), 11); + for (idx, val) in (0..).zip(coord.values()) { + assert_eq!(val, idx * 10); + assert_eq!(coord.from_index(idx as usize), Some(val)); + } + } +} diff --git a/vendor/plotters/src/coord/ranged1d/combinators/linspace.rs b/vendor/plotters/src/coord/ranged1d/combinators/linspace.rs new file mode 100644 index 000000000..7f4ac8641 --- /dev/null +++ b/vendor/plotters/src/coord/ranged1d/combinators/linspace.rs @@ -0,0 +1,433 @@ +use crate::coord::ranged1d::types::RangedCoordusize; +use crate::coord::ranged1d::{ + AsRangedCoord, DiscreteRanged, KeyPointHint, NoDefaultFormatting, Ranged, ValueFormatter, +}; +use std::cmp::{Ordering, PartialOrd}; +use std::marker::PhantomData; +use std::ops::{Add, Range, Sub}; + +/// The type marker used to denote the rounding method. +/// Since we are mapping any range to a discrete range thus not all values are +/// perfect mapped to the grid points. In this case, this type marker gives hints +/// for the linspace coord for how to treat the non-grid-point values. +pub trait LinspaceRoundingMethod<V> { + /// Search for the value within the given values array and rounding method + /// + /// - `values`: The values we want to search + /// - `target`: The target value + /// - `returns`: The index if we found the matching item, otherwise none + fn search(values: &[V], target: &V) -> Option<usize>; +} + +/// This type marker means linspace do the exact match for searching +/// which means if there's no value strictly equals to the target, the coord spec +/// reports not found result. +#[derive(Clone)] +pub struct Exact<V>(PhantomData<V>); + +impl<V: PartialOrd> LinspaceRoundingMethod<V> for Exact<V> { + fn search(values: &[V], target: &V) -> Option<usize> { + values.iter().position(|x| target == x) + } +} + +/// This type marker means we round up the value. Which means we try to find a +/// minimal value in the values array that is greater or equal to the target. +#[derive(Clone)] +pub struct Ceil<V>(PhantomData<V>); + +impl<V: PartialOrd> LinspaceRoundingMethod<V> for Ceil<V> { + fn search(values: &[V], target: &V) -> Option<usize> { + let ascending = if values.len() < 2 { + true + } else { + values[0].partial_cmp(&values[1]) == Some(Ordering::Less) + }; + + match values.binary_search_by(|probe| { + if ascending { + probe.partial_cmp(target).unwrap() + } else { + target.partial_cmp(probe).unwrap() + } + }) { + Ok(idx) => Some(idx), + Err(idx) => { + let offset = if ascending { 0 } else { 1 }; + + if idx < offset || idx >= values.len() + offset { + return None; + } + Some(idx - offset) + } + } + } +} + +/// This means we use the round down. Which means we try to find a +/// maximum value in the values array that is less or equal to the target. +#[derive(Clone)] +pub struct Floor<V>(PhantomData<V>); + +impl<V: PartialOrd> LinspaceRoundingMethod<V> for Floor<V> { + fn search(values: &[V], target: &V) -> Option<usize> { + let ascending = if values.len() < 2 { + true + } else { + values[0].partial_cmp(&values[1]) == Some(Ordering::Less) + }; + + match values.binary_search_by(|probe| { + if ascending { + probe.partial_cmp(target).unwrap() + } else { + target.partial_cmp(probe).unwrap() + } + }) { + Ok(idx) => Some(idx), + Err(idx) => { + let offset = if ascending { 1 } else { 0 }; + + if idx < offset || idx >= values.len() + offset { + return None; + } + Some(idx - offset) + } + } + } +} + +/// This means we use the rounding. Which means we try to find the closet +/// value in the array that matches the target +#[derive(Clone)] +pub struct Round<V, S>(PhantomData<(V, S)>); + +impl<V, S> LinspaceRoundingMethod<V> for Round<V, S> +where + V: Add<S, Output = V> + PartialOrd + Sub<V, Output = S> + Clone, + S: PartialOrd + Clone, +{ + fn search(values: &[V], target: &V) -> Option<usize> { + let ascending = if values.len() < 2 { + true + } else { + values[0].partial_cmp(&values[1]) == Some(Ordering::Less) + }; + + match values.binary_search_by(|probe| { + if ascending { + probe.partial_cmp(target).unwrap() + } else { + target.partial_cmp(probe).unwrap() + } + }) { + Ok(idx) => Some(idx), + Err(idx) => { + if idx == 0 { + return Some(0); + } + + if idx == values.len() { + return Some(idx - 1); + } + + let left_delta = if ascending { + target.clone() - values[idx - 1].clone() + } else { + values[idx - 1].clone() - target.clone() + }; + let right_delta = if ascending { + values[idx].clone() - target.clone() + } else { + target.clone() - values[idx].clone() + }; + + if left_delta.partial_cmp(&right_delta) == Some(Ordering::Less) { + Some(idx - 1) + } else { + Some(idx) + } + } + } + } +} + +/// The coordinate combinator that transform a continous coordinate to a discrete coordinate +/// to a discrete coordinate by a giving step. +/// +/// For example, range `0f32..100f32` is a continuous coordinate, thus this prevent us having a +/// histogram on it since Plotters doesn't know how to segment the range into buckets. +/// In this case, to get a histogram, we need to split the original range to a +/// set of discrete buckets (for example, 0.5 per bucket). +/// +/// The linspace decorate abstracting this method. For example, we can have a discrete coordinate: +/// `(0f32..100f32).step(0.5)`. +/// +/// Linspace also supports different types of bucket matching method - This configuration alters the behavior of +/// [DiscreteCoord::index_of](../trait.DiscreteCoord.html#tymethod.index_of) for Linspace coord spec +/// - **Flooring**, the value falls into the nearst bucket smaller than it. See [Linspace::use_floor](struct.Linspace.html#method.use_floor) +/// - **Round**, the value falls into the nearst bucket. See [Linearspace::use_round](struct.Linspace.html#method.use_round) +/// - **Ceiling**, the value falls into the nearst bucket larger than itself. See [Linspace::use_ceil](struct.Linspace.html#method.use_ceil) +/// - **Exact Matchting**, the value must be exactly same as the butcket value. See [Linspace::use_exact](struct.Linspace.html#method.use_exact) +#[derive(Clone)] +pub struct Linspace<T: Ranged, S: Clone, R: LinspaceRoundingMethod<T::ValueType>> +where + T::ValueType: Add<S, Output = T::ValueType> + PartialOrd + Clone, +{ + step: S, + inner: T, + grid_value: Vec<T::ValueType>, + _phatom: PhantomData<R>, +} + +impl<T: Ranged, S: Clone, R: LinspaceRoundingMethod<T::ValueType>> Linspace<T, S, R> +where + T::ValueType: Add<S, Output = T::ValueType> + PartialOrd + Clone, +{ + fn compute_grid_values(&mut self) { + let range = self.inner.range(); + + match ( + range.start.partial_cmp(&range.end), + (range.start.clone() + self.step.clone()).partial_cmp(&range.end), + ) { + (Some(a), Some(b)) if a != b || a == Ordering::Equal || b == Ordering::Equal => (), + (Some(a), Some(_)) => { + let mut current = range.start; + while current.partial_cmp(&range.end) == Some(a) { + self.grid_value.push(current.clone()); + current = current + self.step.clone(); + } + } + _ => (), + } + } + + /// Set the linspace use the round up method for value matching + /// + /// - **returns**: The newly created linspace that uses new matching method + pub fn use_ceil(self) -> Linspace<T, S, Ceil<T::ValueType>> { + Linspace { + step: self.step, + inner: self.inner, + grid_value: self.grid_value, + _phatom: PhantomData, + } + } + + /// Set the linspace use the round down method for value matching + /// + /// - **returns**: The newly created linspace that uses new matching method + pub fn use_floor(self) -> Linspace<T, S, Floor<T::ValueType>> { + Linspace { + step: self.step, + inner: self.inner, + grid_value: self.grid_value, + _phatom: PhantomData, + } + } + + /// Set the linspace use the best match method for value matching + /// + /// - **returns**: The newly created linspace that uses new matching method + pub fn use_round(self) -> Linspace<T, S, Round<T::ValueType, S>> + where + T::ValueType: Sub<T::ValueType, Output = S>, + S: PartialOrd, + { + Linspace { + step: self.step, + inner: self.inner, + grid_value: self.grid_value, + _phatom: PhantomData, + } + } + + /// Set the linspace use the exact match method for value matching + /// + /// - **returns**: The newly created linspace that uses new matching method + pub fn use_exact(self) -> Linspace<T, S, Exact<T::ValueType>> + where + T::ValueType: Sub<T::ValueType, Output = S>, + S: PartialOrd, + { + Linspace { + step: self.step, + inner: self.inner, + grid_value: self.grid_value, + _phatom: PhantomData, + } + } +} + +impl<T, R, S, RM> ValueFormatter<T> for Linspace<R, S, RM> +where + R: Ranged<ValueType = T> + ValueFormatter<T>, + RM: LinspaceRoundingMethod<T>, + T: Add<S, Output = T> + PartialOrd + Clone, + S: Clone, +{ + fn format(value: &T) -> String { + R::format(value) + } +} + +impl<T: Ranged, S: Clone, R: LinspaceRoundingMethod<T::ValueType>> Ranged for Linspace<T, S, R> +where + T::ValueType: Add<S, Output = T::ValueType> + PartialOrd + Clone, +{ + type FormatOption = NoDefaultFormatting; + type ValueType = T::ValueType; + + fn range(&self) -> Range<T::ValueType> { + self.inner.range() + } + + fn map(&self, value: &T::ValueType, limit: (i32, i32)) -> i32 { + self.inner.map(value, limit) + } + + fn key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<T::ValueType> { + if self.grid_value.is_empty() { + return vec![]; + } + let idx_range: RangedCoordusize = (0..(self.grid_value.len() - 1)).into(); + + idx_range + .key_points(hint) + .into_iter() + .map(|x| self.grid_value[x].clone()) + .collect() + } +} + +impl<T: Ranged, S: Clone, R: LinspaceRoundingMethod<T::ValueType>> DiscreteRanged + for Linspace<T, S, R> +where + T::ValueType: Add<S, Output = T::ValueType> + PartialOrd + Clone, +{ + fn size(&self) -> usize { + self.grid_value.len() + } + + fn index_of(&self, value: &T::ValueType) -> Option<usize> { + R::search(self.grid_value.as_ref(), value) + } + + fn from_index(&self, idx: usize) -> Option<T::ValueType> { + self.grid_value.get(idx).map(Clone::clone) + } +} + +/// Makes a linspace coordinate from the ranged coordinates. +pub trait IntoLinspace: AsRangedCoord { + /// Set the step value, make a linspace coordinate from the given range. + /// By default the matching method use the exact match + /// + /// - `val`: The step value + /// - **returns*: The newly created linspace + fn step<S: Clone>(self, val: S) -> Linspace<Self::CoordDescType, S, Exact<Self::Value>> + where + Self::Value: Add<S, Output = Self::Value> + PartialOrd + Clone, + { + let mut ret = Linspace { + step: val, + inner: self.into(), + grid_value: vec![], + _phatom: PhantomData, + }; + + ret.compute_grid_values(); + + ret + } +} + +impl<T: AsRangedCoord> IntoLinspace for T {} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_float_linspace() { + let coord = (0.0f64..100.0f64).step(0.1); + + assert_eq!(coord.map(&23.12, (0, 10000)), 2312); + assert_eq!(coord.range(), 0.0..100.0); + assert_eq!(coord.key_points(100000).len(), 1001); + assert_eq!(coord.size(), 1001); + assert_eq!(coord.index_of(&coord.from_index(230).unwrap()), Some(230)); + assert!((coord.from_index(230).unwrap() - 23.0).abs() < 1e-5); + } + + #[test] + fn test_rounding_methods() { + let coord = (0.0f64..100.0f64).step(1.0); + + assert_eq!(coord.index_of(&1.0), Some(1)); + assert_eq!(coord.index_of(&1.2), None); + + let coord = coord.use_floor(); + assert_eq!(coord.index_of(&1.0), Some(1)); + assert_eq!(coord.index_of(&1.2), Some(1)); + assert_eq!(coord.index_of(&23.9), Some(23)); + assert_eq!(coord.index_of(&10000.0), Some(99)); + assert_eq!(coord.index_of(&-1.0), None); + + let coord = coord.use_ceil(); + assert_eq!(coord.index_of(&1.0), Some(1)); + assert_eq!(coord.index_of(&1.2), Some(2)); + assert_eq!(coord.index_of(&23.9), Some(24)); + assert_eq!(coord.index_of(&10000.0), None); + assert_eq!(coord.index_of(&-1.0), Some(0)); + + let coord = coord.use_round(); + assert_eq!(coord.index_of(&1.0), Some(1)); + assert_eq!(coord.index_of(&1.2), Some(1)); + assert_eq!(coord.index_of(&1.7), Some(2)); + assert_eq!(coord.index_of(&23.9), Some(24)); + assert_eq!(coord.index_of(&10000.0), Some(99)); + assert_eq!(coord.index_of(&-1.0), Some(0)); + + let coord = (0.0f64..-100.0f64).step(-1.0); + + assert_eq!(coord.index_of(&-1.0), Some(1)); + assert_eq!(coord.index_of(&-1.2), None); + + let coord = coord.use_floor(); + assert_eq!(coord.index_of(&-1.0), Some(1)); + assert_eq!(coord.index_of(&-1.2), Some(2)); + assert_eq!(coord.index_of(&-23.9), Some(24)); + assert_eq!(coord.index_of(&-10000.0), None); + assert_eq!(coord.index_of(&1.0), Some(0)); + + let coord = coord.use_ceil(); + assert_eq!(coord.index_of(&-1.0), Some(1)); + assert_eq!(coord.index_of(&-1.2), Some(1)); + assert_eq!(coord.index_of(&-23.9), Some(23)); + assert_eq!(coord.index_of(&-10000.0), Some(99)); + assert_eq!(coord.index_of(&1.0), None); + + let coord = coord.use_round(); + assert_eq!(coord.index_of(&-1.0), Some(1)); + assert_eq!(coord.index_of(&-1.2), Some(1)); + assert_eq!(coord.index_of(&-1.7), Some(2)); + assert_eq!(coord.index_of(&-23.9), Some(24)); + assert_eq!(coord.index_of(&-10000.0), Some(99)); + assert_eq!(coord.index_of(&1.0), Some(0)); + } + + #[cfg(feature = "chrono")] + #[test] + fn test_duration_linspace() { + use chrono::Duration; + let coord = (Duration::seconds(0)..Duration::seconds(100)).step(Duration::milliseconds(1)); + + assert_eq!(coord.size(), 100_000); + assert_eq!(coord.index_of(&coord.from_index(230).unwrap()), Some(230)); + assert_eq!(coord.key_points(10000000).len(), 100_000); + assert_eq!(coord.range(), Duration::seconds(0)..Duration::seconds(100)); + assert_eq!(coord.map(&Duration::seconds(25), (0, 100_000)), 25000); + } +} diff --git a/vendor/plotters/src/coord/ranged1d/combinators/logarithmic.rs b/vendor/plotters/src/coord/ranged1d/combinators/logarithmic.rs new file mode 100644 index 000000000..fee36f374 --- /dev/null +++ b/vendor/plotters/src/coord/ranged1d/combinators/logarithmic.rs @@ -0,0 +1,284 @@ +use crate::coord::ranged1d::types::RangedCoordf64; +use crate::coord::ranged1d::{AsRangedCoord, DefaultFormatting, KeyPointHint, Ranged}; +use std::marker::PhantomData; +use std::ops::Range; + +/// The trait for the type that is able to be presented in the log scale. +/// This trait is primarily used by [LogRangeExt](struct.LogRangeExt.html). +pub trait LogScalable: Clone { + /// Make the conversion from the type to the floating point number + fn as_f64(&self) -> f64; + /// Convert a floating point number to the scale + fn from_f64(f: f64) -> Self; +} + +macro_rules! impl_log_scalable { + (i, $t:ty) => { + impl LogScalable for $t { + fn as_f64(&self) -> f64 { + if *self != 0 { + return *self as f64; + } + // If this is an integer, we should allow zero point to be shown + // on the chart, thus we can't map the zero point to inf. + // So we just assigning a value smaller than 1 as the alternative + // of the zero point. + return 0.5; + } + fn from_f64(f: f64) -> $t { + f.round() as $t + } + } + }; + (f, $t:ty) => { + impl LogScalable for $t { + fn as_f64(&self) -> f64 { + *self as f64 + } + fn from_f64(f: f64) -> $t { + f as $t + } + } + }; +} + +impl_log_scalable!(i, u8); +impl_log_scalable!(i, u16); +impl_log_scalable!(i, u32); +impl_log_scalable!(i, u64); + +impl_log_scalable!(i, i8); +impl_log_scalable!(i, i16); +impl_log_scalable!(i, i32); +impl_log_scalable!(i, i64); + +impl_log_scalable!(f, f32); +impl_log_scalable!(f, f64); + +/// Convert a range to a log scale coordinate spec +pub trait IntoLogRange { + /// The type of the value + type ValueType: LogScalable; + + /// Make the log scale coordinate + fn log_scale(self) -> LogRangeExt<Self::ValueType>; +} + +impl<T: LogScalable> IntoLogRange for Range<T> { + type ValueType = T; + fn log_scale(self) -> LogRangeExt<T> { + LogRangeExt { + range: self, + zero: 0.0, + base: 10.0, + } + } +} + +/// The logarithmic coodinate decorator. +/// This decorator is used to make the axis rendered as logarithmically. +#[derive(Clone)] +pub struct LogRangeExt<V: LogScalable> { + range: Range<V>, + zero: f64, + base: f64, +} + +impl<V: LogScalable> LogRangeExt<V> { + /// Set the zero point of the log scale coordinate. Zero point is the point where we map -inf + /// of the axis to the coordinate + pub fn zero_point(mut self, value: V) -> Self + where + V: PartialEq, + { + self.zero = if V::from_f64(0.0) == value { + 0.0 + } else { + value.as_f64() + }; + + self + } + + /// Set the base multipler + pub fn base(mut self, base: f64) -> Self { + if self.base > 1.0 { + self.base = base; + } + self + } +} + +impl<V: LogScalable> From<LogRangeExt<V>> for LogCoord<V> { + fn from(spec: LogRangeExt<V>) -> LogCoord<V> { + let zero_point = spec.zero; + let mut start = spec.range.start.as_f64() - zero_point; + let mut end = spec.range.end.as_f64() - zero_point; + let negative = if start < 0.0 || end < 0.0 { + start = -start; + end = -end; + true + } else { + false + }; + + if start < end { + if start == 0.0 { + start = start.max(end * 1e-5); + } + } else if end == 0.0 { + end = end.max(start * 1e-5); + } + + LogCoord { + linear: (start.ln()..end.ln()).into(), + logic: spec.range, + normalized: start..end, + base: spec.base, + zero_point, + negative, + marker: PhantomData, + } + } +} + +impl<V: LogScalable> AsRangedCoord for LogRangeExt<V> { + type CoordDescType = LogCoord<V>; + type Value = V; +} + +/// A log scaled coordinate axis +pub struct LogCoord<V: LogScalable> { + linear: RangedCoordf64, + logic: Range<V>, + normalized: Range<f64>, + base: f64, + zero_point: f64, + negative: bool, + marker: PhantomData<V>, +} + +impl<V: LogScalable> LogCoord<V> { + fn value_to_f64(&self, value: &V) -> f64 { + let fv = value.as_f64() - self.zero_point; + if self.negative { + -fv + } else { + fv + } + } + + fn f64_to_value(&self, fv: f64) -> V { + let fv = if self.negative { -fv } else { fv }; + V::from_f64(fv + self.zero_point) + } + + fn is_inf(&self, fv: f64) -> bool { + let fv = if self.negative { -fv } else { fv }; + let a = V::from_f64(fv + self.zero_point); + let b = V::from_f64(self.zero_point); + + (V::as_f64(&a) - V::as_f64(&b)).abs() < std::f64::EPSILON + } +} + +impl<V: LogScalable> Ranged for LogCoord<V> { + type FormatOption = DefaultFormatting; + type ValueType = V; + + fn map(&self, value: &V, limit: (i32, i32)) -> i32 { + let fv = self.value_to_f64(value); + let value_ln = fv.ln(); + self.linear.map(&value_ln, limit) + } + + fn key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<Self::ValueType> { + let max_points = hint.max_num_points(); + + let base = self.base; + let base_ln = base.ln(); + + let Range { mut start, mut end } = self.normalized; + + if start > end { + std::mem::swap(&mut start, &mut end); + } + + let bold_count = ((end / start).ln().abs() / base_ln).floor().max(1.0) as usize; + + let light_density = if max_points < bold_count { + 0 + } else { + let density = 1 + (max_points - bold_count) / bold_count; + let mut exp = 1; + while exp * 10 <= density { + exp *= 10; + } + exp - 1 + }; + + let mut multiplier = base; + let mut cnt = 1; + while max_points < bold_count / cnt { + multiplier *= base; + cnt += 1; + } + + let mut ret = vec![]; + let mut val = (base).powf((start.ln() / base_ln).ceil()); + + while val <= end { + if !self.is_inf(val) { + ret.push(self.f64_to_value(val)); + } + for i in 1..=light_density { + let v = val + * (1.0 + + multiplier / f64::from(light_density as u32 + 1) * f64::from(i as u32)); + if v > end { + break; + } + if !self.is_inf(val) { + ret.push(self.f64_to_value(v)); + } + } + val *= multiplier; + } + + ret + } + + fn range(&self) -> Range<V> { + self.logic.clone() + } +} + +/// The logarithmic coodinate decorator. +/// This decorator is used to make the axis rendered as logarithmically. +#[deprecated(note = "LogRange is deprecated, use IntoLogRange trait method instead")] +#[derive(Clone)] +pub struct LogRange<V: LogScalable>(pub Range<V>); + +#[allow(deprecated)] +impl<V: LogScalable> AsRangedCoord for LogRange<V> { + type CoordDescType = LogCoord<V>; + type Value = V; +} + +#[allow(deprecated)] +impl<V: LogScalable> From<LogRange<V>> for LogCoord<V> { + fn from(range: LogRange<V>) -> LogCoord<V> { + range.0.log_scale().into() + } +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn regression_test_issue_143() { + let range: LogCoord<f64> = (1.0..5.0).log_scale().into(); + + range.key_points(100); + } +} diff --git a/vendor/plotters/src/coord/ranged1d/combinators/mod.rs b/vendor/plotters/src/coord/ranged1d/combinators/mod.rs new file mode 100644 index 000000000..2d5280248 --- /dev/null +++ b/vendor/plotters/src/coord/ranged1d/combinators/mod.rs @@ -0,0 +1,20 @@ +mod ckps; +pub use ckps::{BindKeyPointMethod, BindKeyPoints, WithKeyPointMethod, WithKeyPoints}; + +mod group_by; +pub use group_by::{GroupBy, ToGroupByRange}; + +mod linspace; +pub use linspace::{IntoLinspace, Linspace}; + +mod logarithmic; +pub use logarithmic::{IntoLogRange, LogCoord, LogScalable}; + +#[allow(deprecated)] +pub use logarithmic::LogRange; + +mod nested; +pub use nested::{BuildNestedCoord, NestedRange, NestedValue}; + +mod partial_axis; +pub use partial_axis::{make_partial_axis, IntoPartialAxis}; diff --git a/vendor/plotters/src/coord/ranged1d/combinators/nested.rs b/vendor/plotters/src/coord/ranged1d/combinators/nested.rs new file mode 100644 index 000000000..e4a493ec8 --- /dev/null +++ b/vendor/plotters/src/coord/ranged1d/combinators/nested.rs @@ -0,0 +1,205 @@ +use crate::coord::ranged1d::{ + AsRangedCoord, DiscreteRanged, KeyPointHint, NoDefaultFormatting, Ranged, ValueFormatter, +}; +use std::ops::Range; + +/// Describe a value for a nested coordinate +#[derive(PartialEq, Eq, Clone, Debug)] +pub enum NestedValue<C, V> { + /// Category value + Category(C), + /// One exact nested value + Value(C, V), +} + +impl<C, V> NestedValue<C, V> { + /// Get the category of current nest value + pub fn category(&self) -> &C { + match self { + NestedValue::Category(cat) => cat, + NestedValue::Value(cat, _) => cat, + } + } + /// Get the nested value from this value + pub fn nested_value(&self) -> Option<&V> { + match self { + NestedValue::Category(_) => None, + NestedValue::Value(_, val) => Some(val), + } + } +} + +impl<C, V> From<(C, V)> for NestedValue<C, V> { + fn from((cat, val): (C, V)) -> NestedValue<C, V> { + NestedValue::Value(cat, val) + } +} + +impl<C, V> From<C> for NestedValue<C, V> { + fn from(cat: C) -> NestedValue<C, V> { + NestedValue::Category(cat) + } +} + +/// A nested coordinate spec which is a discrete coordinate on the top level and +/// for each value in discrete value, there is a secondary coordinate system. +/// And the value is defined as a tuple of primary coordinate value and secondary +/// coordinate value +pub struct NestedRange<Primary: DiscreteRanged, Secondary: Ranged> { + primary: Primary, + secondary: Vec<Secondary>, +} + +impl<PT, ST, P, S> ValueFormatter<NestedValue<PT, ST>> for NestedRange<P, S> +where + P: Ranged<ValueType = PT> + DiscreteRanged, + S: Ranged<ValueType = ST>, + P: ValueFormatter<PT>, + S: ValueFormatter<ST>, +{ + fn format(value: &NestedValue<PT, ST>) -> String { + match value { + NestedValue::Category(cat) => P::format(cat), + NestedValue::Value(_, val) => S::format(val), + } + } +} + +impl<P: DiscreteRanged, S: Ranged> Ranged for NestedRange<P, S> { + type FormatOption = NoDefaultFormatting; + type ValueType = NestedValue<P::ValueType, S::ValueType>; + + fn range(&self) -> Range<Self::ValueType> { + let primary_range = self.primary.range(); + + let secondary_left = self.secondary[0].range().start; + let secondary_right = self.secondary[self.primary.size() - 1].range().end; + + NestedValue::Value(primary_range.start, secondary_left) + ..NestedValue::Value(primary_range.end, secondary_right) + } + + fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { + let idx = self.primary.index_of(value.category()).unwrap_or(0); + let total = self.primary.size(); + + let bucket_size = (limit.1 - limit.0) / total as i32; + let mut residual = (limit.1 - limit.0) % total as i32; + + if residual < 0 { + residual += total as i32; + } + + let s_left = limit.0 + bucket_size * idx as i32 + residual.min(idx as i32); + let s_right = s_left + bucket_size + if (residual as usize) < idx { 1 } else { 0 }; + + if let Some(secondary_value) = value.nested_value() { + self.secondary[idx].map(secondary_value, (s_left, s_right)) + } else { + (s_left + s_right) / 2 + } + } + + fn key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<Self::ValueType> { + if !hint.weight().allow_light_points() || hint.max_num_points() < self.primary.size() * 2 { + self.primary + .key_points(hint) + .into_iter() + .map(NestedValue::Category) + .collect() + } else { + let secondary_size = + (hint.max_num_points() - self.primary.size()) / self.primary.size(); + self.primary + .values() + .enumerate() + .flat_map(|(idx, val)| { + std::iter::once(NestedValue::Category(val)).chain( + self.secondary[idx] + .key_points(secondary_size) + .into_iter() + .map(move |v| { + NestedValue::Value(self.primary.from_index(idx).unwrap(), v) + }), + ) + }) + .collect() + } + } +} + +impl<P: DiscreteRanged, S: DiscreteRanged> DiscreteRanged for NestedRange<P, S> { + fn size(&self) -> usize { + self.secondary.iter().map(|x| x.size()).sum::<usize>() + } + + fn index_of(&self, value: &Self::ValueType) -> Option<usize> { + let p_idx = self.primary.index_of(value.category())?; + let s_idx = self.secondary[p_idx].index_of(value.nested_value()?)?; + Some( + s_idx + + self.secondary[..p_idx] + .iter() + .map(|x| x.size()) + .sum::<usize>(), + ) + } + + fn from_index(&self, mut index: usize) -> Option<Self::ValueType> { + for (p_idx, snd) in self.secondary.iter().enumerate() { + if snd.size() > index { + return Some(NestedValue::Value( + self.primary.from_index(p_idx).unwrap(), + snd.from_index(index).unwrap(), + )); + } + index -= snd.size(); + } + None + } +} + +/// Used to build a nested coordinate system. +pub trait BuildNestedCoord: AsRangedCoord +where + Self::CoordDescType: DiscreteRanged, +{ + /// Builds a nested coordinate system. + fn nested_coord<S: AsRangedCoord>( + self, + builder: impl Fn(<Self::CoordDescType as Ranged>::ValueType) -> S, + ) -> NestedRange<Self::CoordDescType, S::CoordDescType> { + let primary: Self::CoordDescType = self.into(); + assert!(primary.size() > 0); + + let secondary = primary + .values() + .map(|value| builder(value).into()) + .collect(); + + NestedRange { primary, secondary } + } +} + +impl<T: AsRangedCoord> BuildNestedCoord for T where T::CoordDescType: DiscreteRanged {} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_nested_coord() { + let coord = (0..10).nested_coord(|x| 0..(x + 1)); + + let range = coord.range(); + + assert_eq!(NestedValue::Value(0, 0)..NestedValue::Value(10, 11), range); + assert_eq!(coord.map(&NestedValue::Category(0), (0, 1100)), 50); + assert_eq!(coord.map(&NestedValue::Value(0, 0), (0, 1100)), 0); + assert_eq!(coord.map(&NestedValue::Value(5, 4), (0, 1100)), 567); + + assert_eq!(coord.size(), (2 + 12) * 11 / 2); + assert_eq!(coord.index_of(&NestedValue::Value(5, 4)), Some(24)); + assert_eq!(coord.from_index(24), Some(NestedValue::Value(5, 4))); + } +} diff --git a/vendor/plotters/src/coord/ranged1d/combinators/partial_axis.rs b/vendor/plotters/src/coord/ranged1d/combinators/partial_axis.rs new file mode 100644 index 000000000..b778ee2c7 --- /dev/null +++ b/vendor/plotters/src/coord/ranged1d/combinators/partial_axis.rs @@ -0,0 +1,113 @@ +use crate::coord::ranged1d::{ + AsRangedCoord, DefaultFormatting, DiscreteRanged, KeyPointHint, Ranged, +}; +use std::ops::Range; + +/// This axis decorator will make the axis partially display on the axis. +/// At some time, we want the axis only covers some part of the value. +/// This decorator will have an additional display range defined. +#[derive(Clone)] +pub struct PartialAxis<R: Ranged>(R, Range<R::ValueType>); + +/// The trait for the types that can be converted into a partial axis +pub trait IntoPartialAxis: AsRangedCoord { + /// Make the partial axis + /// + /// - `axis_range`: The range of the axis to be displayed + /// - **returns**: The converted range specification + fn partial_axis( + self, + axis_range: Range<<Self::CoordDescType as Ranged>::ValueType>, + ) -> PartialAxis<Self::CoordDescType> { + PartialAxis(self.into(), axis_range) + } +} + +impl<R: AsRangedCoord> IntoPartialAxis for R {} + +impl<R: Ranged> Ranged for PartialAxis<R> +where + R::ValueType: Clone, +{ + type FormatOption = DefaultFormatting; + type ValueType = R::ValueType; + + fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { + self.0.map(value, limit) + } + + fn key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<Self::ValueType> { + self.0.key_points(hint) + } + + fn range(&self) -> Range<Self::ValueType> { + self.0.range() + } + + fn axis_pixel_range(&self, limit: (i32, i32)) -> Range<i32> { + let left = self.map(&self.1.start, limit); + let right = self.map(&self.1.end, limit); + + left.min(right)..left.max(right) + } +} + +impl<R: DiscreteRanged> DiscreteRanged for PartialAxis<R> +where + R: Ranged, + <R as Ranged>::ValueType: Eq + Clone, +{ + fn size(&self) -> usize { + self.0.size() + } + + fn index_of(&self, value: &R::ValueType) -> Option<usize> { + self.0.index_of(value) + } + + fn from_index(&self, index: usize) -> Option<Self::ValueType> { + self.0.from_index(index) + } +} + +/// Make a partial axis based on the percentage of visible portion. +/// We can use `into_partial_axis` to create a partial axis range specification. +/// But sometimes, we want to directly specify the percentage visible to the user. +/// +/// - `axis_range`: The range specification +/// - `part`: The visible part of the axis. Each value is from [0.0, 1.0] +/// - **returns**: The partial axis created from the input, or `None` when not possible +pub fn make_partial_axis<T>( + axis_range: Range<T>, + part: Range<f64>, +) -> Option<PartialAxis<<Range<T> as AsRangedCoord>::CoordDescType>> +where + Range<T>: AsRangedCoord, + T: num_traits::NumCast + Clone, +{ + let left: f64 = num_traits::cast(axis_range.start.clone())?; + let right: f64 = num_traits::cast(axis_range.end.clone())?; + + let full_range_size = (right - left) / (part.end - part.start); + + let full_left = left - full_range_size * part.start; + let full_right = right + full_range_size * (1.0 - part.end); + + let full_range: Range<T> = num_traits::cast(full_left)?..num_traits::cast(full_right)?; + + let axis_range: <Range<T> as AsRangedCoord>::CoordDescType = axis_range.into(); + + Some(PartialAxis(full_range.into(), axis_range.range())) +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_make_partial_axis() { + let r = make_partial_axis(20..80, 0.2..0.8).unwrap(); + assert_eq!(r.size(), 101); + assert_eq!(r.range(), 0..100); + assert_eq!(r.axis_pixel_range((0, 100)), 20..80); + } +} diff --git a/vendor/plotters/src/coord/ranged1d/discrete.rs b/vendor/plotters/src/coord/ranged1d/discrete.rs new file mode 100644 index 000000000..074eece81 --- /dev/null +++ b/vendor/plotters/src/coord/ranged1d/discrete.rs @@ -0,0 +1,273 @@ +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 + // TODO: This doesn't follows rust's naming convention - however, this is a protential breaking + // change, so postpone the fix to the next major release + #[allow(clippy::wrong_self_convention)] + 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); + } +} diff --git a/vendor/plotters/src/coord/ranged1d/mod.rs b/vendor/plotters/src/coord/ranged1d/mod.rs new file mode 100644 index 000000000..97664a2f0 --- /dev/null +++ b/vendor/plotters/src/coord/ranged1d/mod.rs @@ -0,0 +1,243 @@ +/*! + The one-dimensional coordinate system abstraction. + + Plotters build complex coordinate system with a combinator pattern and all the coordinate system is + built from the one dimensional coordinate system. This module defines the fundamental types used by + the one-dimensional coordinate system. + + The key trait for a one dimensional coordinate is [Ranged](trait.Ranged.html). This trait describes a + set of values which served as the 1D coordinate system in Plotters. In order to extend the coordinate system, + the new coordinate spec must implement this trait. + + The following example demonstrate how to make a customized coordinate specification + ``` +use plotters::coord::ranged1d::{Ranged, DefaultFormatting, KeyPointHint}; +use std::ops::Range; + +struct ZeroToOne; + +impl Ranged for ZeroToOne { + type ValueType = f64; + type FormatOption = DefaultFormatting; + + fn map(&self, &v: &f64, pixel_range: (i32, i32)) -> i32 { + let size = pixel_range.1 - pixel_range.0; + let v = v.min(1.0).max(0.0); + ((size as f64) * v).round() as i32 + } + + fn key_points<Hint:KeyPointHint>(&self, hint: Hint) -> Vec<f64> { + if hint.max_num_points() < 3 { + vec![] + } else { + vec![0.0, 0.5, 1.0] + } + } + + fn range(&self) -> Range<f64> { + 0.0..1.0 + } +} + +use plotters::prelude::*; + +let mut buffer = vec![0; 1024 * 768 * 3]; +let root = BitMapBackend::with_buffer(&mut buffer, (1024, 768)).into_drawing_area(); + +let chart = ChartBuilder::on(&root) + .build_cartesian_2d(ZeroToOne, ZeroToOne) + .unwrap(); + + ``` +*/ +use std::fmt::Debug; +use std::ops::Range; + +pub(super) mod combinators; +pub(super) mod types; + +mod discrete; +pub use discrete::{DiscreteRanged, IntoSegmentedCoord, SegmentValue, SegmentedCoord}; + +/// Since stable Rust doesn't have specialization, it's very hard to make our own trait that +/// automatically implemented the value formatter. This trait uses as a marker indicates if we +/// should automatically implement the default value formater based on it's `Debug` trait +pub trait DefaultValueFormatOption {} + +/// This makes the ranged coord uses the default `Debug` based formatting +pub struct DefaultFormatting; +impl DefaultValueFormatOption for DefaultFormatting {} + +/// This markers prevent Plotters to implement the default `Debug` based formatting +pub struct NoDefaultFormatting; +impl DefaultValueFormatOption for NoDefaultFormatting {} + +/// Determine how we can format a value in a coordinate system by default +pub trait ValueFormatter<V> { + /// Format the value + fn format(_value: &V) -> String { + panic!("Unimplemented formatting method"); + } + /// Determine how we can format a value in a coordinate system by default + fn format_ext(&self, value: &V) -> String { + Self::format(value) + } +} + +// By default the value is formatted by the debug trait +impl<R: Ranged<FormatOption = DefaultFormatting>> ValueFormatter<R::ValueType> for R +where + R::ValueType: Debug, +{ + fn format(value: &R::ValueType) -> String { + format!("{:?}", value) + } +} + +/// Specify the weight of key points. +pub enum KeyPointWeight { + /// Allows only bold key points + Bold, + /// Allows any key points + Any, +} + +impl KeyPointWeight { + /// Check if this key point weight setting allows light point + pub fn allow_light_points(&self) -> bool { + match self { + KeyPointWeight::Bold => false, + KeyPointWeight::Any => true, + } + } +} + +/// The trait for a hint provided to the key point algorithm used by the coordinate specs. +/// The most important constraint is the `max_num_points` which means the algorithm could emit no more than specific number of key points +/// `weight` is used to determine if this is used as a bold grid line or light grid line +/// `bold_points` returns the max number of coresponding bold grid lines +pub trait KeyPointHint { + /// Returns the max number of key points + fn max_num_points(&self) -> usize; + /// Returns the weight for this hint + fn weight(&self) -> KeyPointWeight; + /// Returns the point number constraint for the bold points + fn bold_points(&self) -> usize { + self.max_num_points() + } +} + +impl KeyPointHint for usize { + fn max_num_points(&self) -> usize { + *self + } + + fn weight(&self) -> KeyPointWeight { + KeyPointWeight::Any + } +} + +/// The key point hint indicates we only need key point for the bold grid lines +pub struct BoldPoints(pub usize); + +impl KeyPointHint for BoldPoints { + fn max_num_points(&self) -> usize { + self.0 + } + + fn weight(&self) -> KeyPointWeight { + KeyPointWeight::Bold + } +} + +/// The key point hint indicates that we are using the key points for the light grid lines +pub struct LightPoints { + bold_points_num: usize, + light_limit: usize, +} + +impl LightPoints { + /// Create a new light key point hind + pub fn new(bold_count: usize, requested: usize) -> Self { + Self { + bold_points_num: bold_count, + light_limit: requested, + } + } +} + +impl KeyPointHint for LightPoints { + fn max_num_points(&self) -> usize { + self.light_limit + } + + fn bold_points(&self) -> usize { + self.bold_points_num + } + + fn weight(&self) -> KeyPointWeight { + KeyPointWeight::Any + } +} + +/// The trait that indicates we have a ordered and ranged value +/// Which is used to describe any 1D axis. +pub trait Ranged { + /// This marker decides if Plotters default [ValueFormatter](trait.ValueFormatter.html) implementation should be used. + /// This associated type can be one of the following two types: + /// - [DefaultFormatting](struct.DefaultFormatting.html) will allow Plotters to automatically impl + /// the formatter based on `Debug` trait, if `Debug` trait is not impl for the `Self::Value`, + /// [ValueFormatter](trait.ValueFormatter.html) will not impl unless you impl it manually. + /// + /// - [NoDefaultFormatting](struct.NoDefaultFormatting.html) Disable the automatic `Debug` + /// based value formatting. Thus you have to impl the + /// [ValueFormatter](trait.ValueFormatter.html) manually. + /// + type FormatOption: DefaultValueFormatOption; + + /// The type of this value in this range specification + type ValueType; + + /// This function maps the value to i32, which is the drawing coordinate + fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32; + + /// This function gives the key points that we can draw a grid based on this + fn key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<Self::ValueType>; + + /// Get the range of this value + fn range(&self) -> Range<Self::ValueType>; + + /// This function provides the on-axis part of its range + #[allow(clippy::range_plus_one)] + fn axis_pixel_range(&self, limit: (i32, i32)) -> Range<i32> { + if limit.0 < limit.1 { + limit.0..limit.1 + } else { + limit.1..limit.0 + } + } +} + +/// The trait indicates the ranged value can be map reversely, which means +/// an pixel-based coordinate is given, it's possible to figure out the underlying +/// logic value. +pub trait ReversibleRanged: Ranged { + /// Perform the reverse mapping + fn unmap(&self, input: i32, limit: (i32, i32)) -> Option<Self::ValueType>; +} + +/// The trait for the type that can be converted into a ranged coordinate axis +pub trait AsRangedCoord: Sized { + /// Type to describe a coordinate system + type CoordDescType: Ranged<ValueType = Self::Value> + From<Self>; + /// Type for values in the given coordinate system + type Value; +} + +impl<T> AsRangedCoord for T +where + T: Ranged, +{ + type CoordDescType = T; + type Value = T::ValueType; +} diff --git a/vendor/plotters/src/coord/ranged1d/types/datetime.rs b/vendor/plotters/src/coord/ranged1d/types/datetime.rs new file mode 100644 index 000000000..9b12358c0 --- /dev/null +++ b/vendor/plotters/src/coord/ranged1d/types/datetime.rs @@ -0,0 +1,1171 @@ +/// The datetime coordinates +use chrono::{Date, DateTime, Datelike, Duration, NaiveDate, NaiveDateTime, TimeZone, Timelike}; +use std::ops::{Add, Range, Sub}; + +use crate::coord::ranged1d::{ + AsRangedCoord, DefaultFormatting, DiscreteRanged, KeyPointHint, NoDefaultFormatting, Ranged, + ValueFormatter, +}; + +/// The trait that describe some time value. This is the uniformed abstraction that works +/// for both Date, DateTime and Duration, etc. +pub trait TimeValue: Eq { + type DateType: Datelike + PartialOrd; + + /// Returns the date that is no later than the time + fn date_floor(&self) -> Self::DateType; + /// Returns the date that is no earlier than the time + fn date_ceil(&self) -> Self::DateType; + /// Returns the maximum value that is earlier than the given date + fn earliest_after_date(date: Self::DateType) -> Self; + /// Returns the duration between two time value + fn subtract(&self, other: &Self) -> Duration; + /// Instantiate a date type for current time value; + fn ymd(&self, year: i32, month: u32, date: u32) -> Self::DateType; + /// Cast current date type into this type + fn from_date(date: Self::DateType) -> Self; + + /// Map the coord spec + fn map_coord(value: &Self, begin: &Self, end: &Self, limit: (i32, i32)) -> i32 { + let total_span = end.subtract(begin); + let value_span = value.subtract(begin); + + // First, lets try the nanoseconds precision + if let Some(total_ns) = total_span.num_nanoseconds() { + if let Some(value_ns) = value_span.num_nanoseconds() { + return (f64::from(limit.1 - limit.0) * value_ns as f64 / total_ns as f64) as i32 + + limit.0; + } + } + + // Yes, converting them to floating point may lose precision, but this is Ok. + // If it overflows, it means we have a time span nearly 300 years, we are safe to ignore the + // portion less than 1 day. + let total_days = total_span.num_days() as f64; + let value_days = value_span.num_days() as f64; + + (f64::from(limit.1 - limit.0) * value_days / total_days) as i32 + limit.0 + } +} + +impl TimeValue for NaiveDate { + type DateType = NaiveDate; + fn date_floor(&self) -> NaiveDate { + *self + } + fn date_ceil(&self) -> NaiveDate { + *self + } + fn earliest_after_date(date: NaiveDate) -> Self { + date + } + fn subtract(&self, other: &NaiveDate) -> Duration { + *self - *other + } + + fn ymd(&self, year: i32, month: u32, date: u32) -> Self::DateType { + NaiveDate::from_ymd(year, month, date) + } + + fn from_date(date: Self::DateType) -> Self { + date + } +} + +impl<Z: TimeZone> TimeValue for Date<Z> { + type DateType = Date<Z>; + fn date_floor(&self) -> Date<Z> { + self.clone() + } + fn date_ceil(&self) -> Date<Z> { + self.clone() + } + fn earliest_after_date(date: Date<Z>) -> Self { + date + } + fn subtract(&self, other: &Date<Z>) -> Duration { + self.clone() - other.clone() + } + + fn ymd(&self, year: i32, month: u32, date: u32) -> Self::DateType { + self.timezone().ymd(year, month, date) + } + + fn from_date(date: Self::DateType) -> Self { + date + } +} + +impl<Z: TimeZone> TimeValue for DateTime<Z> { + type DateType = Date<Z>; + fn date_floor(&self) -> Date<Z> { + self.date() + } + fn date_ceil(&self) -> Date<Z> { + if self.time().num_seconds_from_midnight() > 0 { + self.date() + Duration::days(1) + } else { + self.date() + } + } + fn earliest_after_date(date: Date<Z>) -> DateTime<Z> { + date.and_hms(0, 0, 0) + } + + fn subtract(&self, other: &DateTime<Z>) -> Duration { + self.clone() - other.clone() + } + + fn ymd(&self, year: i32, month: u32, date: u32) -> Self::DateType { + self.timezone().ymd(year, month, date) + } + + fn from_date(date: Self::DateType) -> Self { + date.and_hms(0, 0, 0) + } +} + +impl TimeValue for NaiveDateTime { + type DateType = NaiveDate; + fn date_floor(&self) -> NaiveDate { + self.date() + } + fn date_ceil(&self) -> NaiveDate { + if self.time().num_seconds_from_midnight() > 0 { + self.date() + Duration::days(1) + } else { + self.date() + } + } + fn earliest_after_date(date: NaiveDate) -> NaiveDateTime { + date.and_hms(0, 0, 0) + } + + fn subtract(&self, other: &NaiveDateTime) -> Duration { + *self - *other + } + + fn ymd(&self, year: i32, month: u32, date: u32) -> Self::DateType { + NaiveDate::from_ymd(year, month, date) + } + + fn from_date(date: Self::DateType) -> Self { + date.and_hms(0, 0, 0) + } +} + +/// The ranged coordinate for date +#[derive(Clone)] +pub struct RangedDate<D: Datelike>(D, D); + +impl<D: Datelike> From<Range<D>> for RangedDate<D> { + fn from(range: Range<D>) -> Self { + Self(range.start, range.end) + } +} + +impl<D> Ranged for RangedDate<D> +where + D: Datelike + TimeValue + Sub<D, Output = Duration> + Add<Duration, Output = D> + Clone, +{ + type FormatOption = DefaultFormatting; + type ValueType = D; + + fn range(&self) -> Range<D> { + self.0.clone()..self.1.clone() + } + + fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { + TimeValue::map_coord(value, &self.0, &self.1, limit) + } + + fn key_points<HintType: KeyPointHint>(&self, hint: HintType) -> Vec<Self::ValueType> { + let max_points = hint.max_num_points(); + let mut ret = vec![]; + + let total_days = (self.1.clone() - self.0.clone()).num_days(); + let total_weeks = (self.1.clone() - self.0.clone()).num_weeks(); + + if total_days > 0 && total_days as usize <= max_points { + for day_idx in 0..=total_days { + ret.push(self.0.clone() + Duration::days(day_idx)); + } + return ret; + } + + if total_weeks > 0 && total_weeks as usize <= max_points { + for day_idx in 0..=total_weeks { + ret.push(self.0.clone() + Duration::weeks(day_idx)); + } + return ret; + } + + // When all data is in the same week, just plot properly. + if total_weeks == 0 { + ret.push(self.0.clone()); + return ret; + } + + let week_per_point = ((total_weeks as f64) / (max_points as f64)).ceil() as usize; + + for idx in 0..=(total_weeks as usize / week_per_point) { + ret.push(self.0.clone() + Duration::weeks((idx * week_per_point) as i64)); + } + + ret + } +} + +impl<D> DiscreteRanged for RangedDate<D> +where + D: Datelike + TimeValue + Sub<D, Output = Duration> + Add<Duration, Output = D> + Clone, +{ + fn size(&self) -> usize { + ((self.1.clone() - self.0.clone()).num_days().max(-1) + 1) as usize + } + + fn index_of(&self, value: &D) -> Option<usize> { + let ret = (value.clone() - self.0.clone()).num_days(); + if ret < 0 { + return None; + } + Some(ret as usize) + } + + fn from_index(&self, index: usize) -> Option<D> { + Some(self.0.clone() + Duration::days(index as i64)) + } +} + +impl<Z: TimeZone> AsRangedCoord for Range<Date<Z>> { + type CoordDescType = RangedDate<Date<Z>>; + type Value = Date<Z>; +} + +impl AsRangedCoord for Range<NaiveDate> { + type CoordDescType = RangedDate<NaiveDate>; + type Value = NaiveDate; +} + +/// Indicates the coord has a monthly resolution +/// +/// Note: since month doesn't have a constant duration. +/// We can't use a simple granularity to describe it. Thus we have +/// this axis decorator to make it yield monthly key-points. +#[derive(Clone)] +pub struct Monthly<T: TimeValue>(Range<T>); + +impl<T: TimeValue + Datelike + Clone> ValueFormatter<T> for Monthly<T> { + fn format(value: &T) -> String { + format!("{}-{}", value.year(), value.month()) + } +} + +impl<T: TimeValue + Clone> Monthly<T> { + fn bold_key_points<H: KeyPointHint>(&self, hint: &H) -> Vec<T> { + let max_points = hint.max_num_points(); + let start_date = self.0.start.date_ceil(); + let end_date = self.0.end.date_floor(); + + let mut start_year = start_date.year(); + let mut start_month = start_date.month(); + let start_day = start_date.day(); + + let end_year = end_date.year(); + let end_month = end_date.month(); + + if start_day != 1 { + start_month += 1; + if start_month == 13 { + start_month = 1; + start_year += 1; + } + } + + let total_month = (end_year - start_year) * 12 + end_month as i32 - start_month as i32; + + fn generate_key_points<T: TimeValue>( + mut start_year: i32, + mut start_month: i32, + end_year: i32, + end_month: i32, + step: u32, + builder: &T, + ) -> Vec<T> { + let mut ret = vec![]; + while end_year > start_year || (end_year == start_year && end_month >= start_month) { + ret.push(T::earliest_after_date(builder.ymd( + start_year, + start_month as u32, + 1, + ))); + start_month += step as i32; + + if start_month >= 13 { + start_year += start_month / 12; + start_month %= 12; + } + } + + ret + } + + if total_month as usize <= max_points { + // Monthly + return generate_key_points( + start_year, + start_month as i32, + end_year, + end_month as i32, + 1, + &self.0.start, + ); + } else if total_month as usize <= max_points * 3 { + // Quarterly + return generate_key_points( + start_year, + start_month as i32, + end_year, + end_month as i32, + 3, + &self.0.start, + ); + } else if total_month as usize <= max_points * 6 { + // Biyearly + return generate_key_points( + start_year, + start_month as i32, + end_year, + end_month as i32, + 6, + &self.0.start, + ); + } + + // Otherwise we could generate the yearly keypoints + generate_yearly_keypoints( + max_points, + start_year, + start_month, + end_year, + end_month, + &self.0.start, + ) + } +} + +impl<T: TimeValue + Clone> Ranged for Monthly<T> +where + Range<T>: AsRangedCoord<Value = T>, +{ + type FormatOption = NoDefaultFormatting; + type ValueType = T; + + fn range(&self) -> Range<T> { + self.0.start.clone()..self.0.end.clone() + } + + fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { + T::map_coord(value, &self.0.start, &self.0.end, limit) + } + + fn key_points<HintType: KeyPointHint>(&self, hint: HintType) -> Vec<Self::ValueType> { + if hint.weight().allow_light_points() && self.size() <= hint.bold_points() * 2 { + let coord: <Range<T> as AsRangedCoord>::CoordDescType = self.0.clone().into(); + let normal = coord.key_points(hint.max_num_points()); + return normal; + } + self.bold_key_points(&hint) + } +} + +impl<T: TimeValue + Clone> DiscreteRanged for Monthly<T> +where + Range<T>: AsRangedCoord<Value = T>, +{ + fn size(&self) -> usize { + let (start_year, start_month) = { + let ceil = self.0.start.date_ceil(); + (ceil.year(), ceil.month()) + }; + let (end_year, end_month) = { + let floor = self.0.end.date_floor(); + (floor.year(), floor.month()) + }; + ((end_year - start_year).max(0) * 12 + + (1 - start_month as i32) + + (end_month as i32 - 1) + + 1) + .max(0) as usize + } + + fn index_of(&self, value: &T) -> Option<usize> { + let this_year = value.date_floor().year(); + let this_month = value.date_floor().month(); + + let start_year = self.0.start.date_ceil().year(); + let start_month = self.0.start.date_ceil().month(); + + let ret = (this_year - start_year).max(0) * 12 + + (1 - start_month as i32) + + (this_month as i32 - 1); + if ret >= 0 { + return Some(ret as usize); + } + None + } + + fn from_index(&self, index: usize) -> Option<T> { + if index == 0 { + return Some(T::earliest_after_date(self.0.start.date_ceil())); + } + let index_from_start_year = index + (self.0.start.date_ceil().month() - 1) as usize; + let year = self.0.start.date_ceil().year() + index_from_start_year as i32 / 12; + let month = index_from_start_year % 12; + Some(T::earliest_after_date(self.0.start.ymd( + year, + month as u32 + 1, + 1, + ))) + } +} + +/// Indicate the coord has a yearly granularity. +#[derive(Clone)] +pub struct Yearly<T: TimeValue>(Range<T>); + +fn generate_yearly_keypoints<T: TimeValue>( + max_points: usize, + mut start_year: i32, + start_month: u32, + mut end_year: i32, + end_month: u32, + builder: &T, +) -> Vec<T> { + if start_month > end_month { + end_year -= 1; + } + + let mut exp10 = 1; + + while (end_year - start_year + 1) as usize / (exp10 * 10) > max_points { + exp10 *= 10; + } + + let mut freq = exp10; + + for try_freq in &[1, 2, 5, 10] { + freq = *try_freq * exp10; + if (end_year - start_year + 1) as usize / (exp10 * *try_freq) <= max_points { + break; + } + } + + let mut ret = vec![]; + + while start_year <= end_year { + ret.push(T::earliest_after_date(builder.ymd( + start_year, + start_month, + 1, + ))); + start_year += freq as i32; + } + + ret +} + +impl<T: TimeValue + Datelike + Clone> ValueFormatter<T> for Yearly<T> { + fn format(value: &T) -> String { + format!("{}-{}", value.year(), value.month()) + } +} + +impl<T: TimeValue + Clone> Ranged for Yearly<T> +where + Range<T>: AsRangedCoord<Value = T>, +{ + type FormatOption = NoDefaultFormatting; + type ValueType = T; + + fn range(&self) -> Range<T> { + self.0.start.clone()..self.0.end.clone() + } + + fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { + T::map_coord(value, &self.0.start, &self.0.end, limit) + } + + fn key_points<HintType: KeyPointHint>(&self, hint: HintType) -> Vec<Self::ValueType> { + if hint.weight().allow_light_points() && self.size() <= hint.bold_points() * 2 { + return Monthly(self.0.clone()).key_points(hint); + } + let max_points = hint.max_num_points(); + let start_date = self.0.start.date_ceil(); + let end_date = self.0.end.date_floor(); + + let mut start_year = start_date.year(); + let mut start_month = start_date.month(); + let start_day = start_date.day(); + + let end_year = end_date.year(); + let end_month = end_date.month(); + + if start_day != 1 { + start_month += 1; + if start_month == 13 { + start_month = 1; + start_year += 1; + } + } + + generate_yearly_keypoints( + max_points, + start_year, + start_month, + end_year, + end_month, + &self.0.start, + ) + } +} + +impl<T: TimeValue + Clone> DiscreteRanged for Yearly<T> +where + Range<T>: AsRangedCoord<Value = T>, +{ + fn size(&self) -> usize { + let year_start = self.0.start.date_ceil().year(); + let year_end = self.0.end.date_floor().year(); + ((year_end - year_start).max(-1) + 1) as usize + } + + fn index_of(&self, value: &T) -> Option<usize> { + let year_start = self.0.start.date_ceil().year(); + let year_value = value.date_floor().year(); + let ret = year_value - year_start; + if ret < 0 { + return None; + } + Some(ret as usize) + } + + fn from_index(&self, index: usize) -> Option<T> { + let year = self.0.start.date_ceil().year() + index as i32; + let ret = T::earliest_after_date(self.0.start.ymd(year, 1, 1)); + if ret.date_ceil() <= self.0.start.date_floor() { + return Some(self.0.start.clone()); + } + Some(ret) + } +} + +/// The trait that converts a normal date coord into a monthly one +pub trait IntoMonthly<T: TimeValue> { + /// Converts a normal date coord into a monthly one + fn monthly(self) -> Monthly<T>; +} + +/// The trait that converts a normal date coord into a yearly one +pub trait IntoYearly<T: TimeValue> { + /// Converts a normal date coord into a yearly one + fn yearly(self) -> Yearly<T>; +} + +impl<T: TimeValue> IntoMonthly<T> for Range<T> { + fn monthly(self) -> Monthly<T> { + Monthly(self) + } +} + +impl<T: TimeValue> IntoYearly<T> for Range<T> { + fn yearly(self) -> Yearly<T> { + Yearly(self) + } +} + +/// The ranged coordinate for the date and time +#[derive(Clone)] +pub struct RangedDateTime<DT: Datelike + Timelike + TimeValue>(DT, DT); + +impl<Z: TimeZone> AsRangedCoord for Range<DateTime<Z>> { + type CoordDescType = RangedDateTime<DateTime<Z>>; + type Value = DateTime<Z>; +} + +impl<Z: TimeZone> From<Range<DateTime<Z>>> for RangedDateTime<DateTime<Z>> { + fn from(range: Range<DateTime<Z>>) -> Self { + Self(range.start, range.end) + } +} + +impl From<Range<NaiveDateTime>> for RangedDateTime<NaiveDateTime> { + fn from(range: Range<NaiveDateTime>) -> Self { + Self(range.start, range.end) + } +} + +impl<DT> Ranged for RangedDateTime<DT> +where + DT: Datelike + Timelike + TimeValue + Clone + PartialOrd, + DT: Add<Duration, Output = DT>, + DT: Sub<DT, Output = Duration>, + RangedDate<DT::DateType>: Ranged<ValueType = DT::DateType>, +{ + type FormatOption = DefaultFormatting; + type ValueType = DT; + + fn range(&self) -> Range<DT> { + self.0.clone()..self.1.clone() + } + + fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { + TimeValue::map_coord(value, &self.0, &self.1, limit) + } + + fn key_points<HintType: KeyPointHint>(&self, hint: HintType) -> Vec<Self::ValueType> { + let max_points = hint.max_num_points(); + let total_span = self.1.clone() - self.0.clone(); + + if let Some(total_ns) = total_span.num_nanoseconds() { + if let Some(actual_ns_per_point) = + compute_period_per_point(total_ns as u64, max_points, true) + { + let start_time_ns = u64::from(self.0.num_seconds_from_midnight()) * 1_000_000_000 + + u64::from(self.0.nanosecond()); + + let mut start_time = DT::from_date(self.0.date_floor()) + + Duration::nanoseconds(if start_time_ns % actual_ns_per_point > 0 { + start_time_ns + (actual_ns_per_point - start_time_ns % actual_ns_per_point) + } else { + start_time_ns + } as i64); + + let mut ret = vec![]; + + while start_time < self.1 { + ret.push(start_time.clone()); + start_time = start_time + Duration::nanoseconds(actual_ns_per_point as i64); + } + + return ret; + } + } + + // Otherwise, it actually behaves like a date + let date_range = RangedDate(self.0.date_ceil(), self.1.date_floor()); + + date_range + .key_points(max_points) + .into_iter() + .map(DT::from_date) + .collect() + } +} + +/// The coordinate that for duration of time +#[derive(Clone)] +pub struct RangedDuration(Duration, Duration); + +impl AsRangedCoord for Range<Duration> { + type CoordDescType = RangedDuration; + type Value = Duration; +} + +impl From<Range<Duration>> for RangedDuration { + fn from(range: Range<Duration>) -> Self { + Self(range.start, range.end) + } +} + +impl Ranged for RangedDuration { + type FormatOption = DefaultFormatting; + type ValueType = Duration; + + fn range(&self) -> Range<Duration> { + self.0..self.1 + } + + fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { + let total_span = self.1 - self.0; + let value_span = *value - self.0; + + if let Some(total_ns) = total_span.num_nanoseconds() { + if let Some(value_ns) = value_span.num_nanoseconds() { + return limit.0 + + (f64::from(limit.1 - limit.0) * value_ns as f64 / total_ns as f64 + 1e-10) + as i32; + } + return limit.1; + } + + let total_days = total_span.num_days(); + let value_days = value_span.num_days(); + + limit.0 + + (f64::from(limit.1 - limit.0) * value_days as f64 / total_days as f64 + 1e-10) as i32 + } + + fn key_points<HintType: KeyPointHint>(&self, hint: HintType) -> Vec<Self::ValueType> { + let max_points = hint.max_num_points(); + let total_span = self.1 - self.0; + + if let Some(total_ns) = total_span.num_nanoseconds() { + if let Some(period) = compute_period_per_point(total_ns as u64, max_points, false) { + let mut start_ns = self.0.num_nanoseconds().unwrap(); + + if start_ns as u64 % period > 0 { + if start_ns > 0 { + start_ns += period as i64 - (start_ns % period as i64); + } else { + start_ns -= start_ns % period as i64; + } + } + + let mut current = Duration::nanoseconds(start_ns); + let mut ret = vec![]; + + while current < self.1 { + ret.push(current); + current = current + Duration::nanoseconds(period as i64); + } + + return ret; + } + } + + let begin_days = self.0.num_days(); + let end_days = self.1.num_days(); + + let mut days_per_tick = 1; + let mut idx = 0; + const MULTIPLIER: &[i32] = &[1, 2, 5]; + + while (end_days - begin_days) / i64::from(days_per_tick * MULTIPLIER[idx]) + > max_points as i64 + { + idx += 1; + if idx == MULTIPLIER.len() { + idx = 0; + days_per_tick *= 10; + } + } + + days_per_tick *= MULTIPLIER[idx]; + + let mut ret = vec![]; + + let mut current = Duration::days( + self.0.num_days() + + if Duration::days(self.0.num_days()) != self.0 { + 1 + } else { + 0 + }, + ); + + while current < self.1 { + ret.push(current); + current = current + Duration::days(i64::from(days_per_tick)); + } + + ret + } +} + +#[allow(clippy::inconsistent_digit_grouping)] +fn compute_period_per_point(total_ns: u64, max_points: usize, sub_daily: bool) -> Option<u64> { + let min_ns_per_point = total_ns as f64 / max_points as f64; + let actual_ns_per_point: u64 = (10u64).pow((min_ns_per_point as f64).log10().floor() as u32); + + fn determine_actual_ns_per_point( + total_ns: u64, + mut actual_ns_per_point: u64, + units: &[u64], + base: u64, + max_points: usize, + ) -> u64 { + let mut unit_per_point_idx = 0; + while total_ns / actual_ns_per_point > max_points as u64 * units[unit_per_point_idx] { + unit_per_point_idx += 1; + if unit_per_point_idx == units.len() { + unit_per_point_idx = 0; + actual_ns_per_point *= base; + } + } + units[unit_per_point_idx] * actual_ns_per_point + } + + if actual_ns_per_point < 1_000_000_000 { + Some(determine_actual_ns_per_point( + total_ns as u64, + actual_ns_per_point, + &[1, 2, 5], + 10, + max_points, + )) + } else if actual_ns_per_point < 3600_000_000_000 { + Some(determine_actual_ns_per_point( + total_ns as u64, + 1_000_000_000, + &[1, 2, 5, 10, 15, 20, 30], + 60, + max_points, + )) + } else if actual_ns_per_point < 3600_000_000_000 * 24 { + Some(determine_actual_ns_per_point( + total_ns as u64, + 3600_000_000_000, + &[1, 2, 4, 8, 12], + 24, + max_points, + )) + } else if !sub_daily { + if actual_ns_per_point < 3600_000_000_000 * 24 * 10 { + Some(determine_actual_ns_per_point( + total_ns as u64, + 3600_000_000_000 * 24, + &[1, 2, 5, 7], + 10, + max_points, + )) + } else { + Some(determine_actual_ns_per_point( + total_ns as u64, + 3600_000_000_000 * 24 * 10, + &[1, 2, 5], + 10, + max_points, + )) + } + } else { + None + } +} + +#[cfg(test)] +mod test { + use super::*; + use chrono::{TimeZone, Utc}; + + #[test] + fn test_date_range_long() { + let range = Utc.ymd(1000, 1, 1)..Utc.ymd(2999, 1, 1); + + let ranged_coord = Into::<RangedDate<_>>::into(range); + + assert_eq!(ranged_coord.map(&Utc.ymd(1000, 8, 10), (0, 100)), 0); + assert_eq!(ranged_coord.map(&Utc.ymd(2999, 8, 10), (0, 100)), 100); + + let kps = ranged_coord.key_points(23); + + assert!(kps.len() <= 23); + let max = kps + .iter() + .zip(kps.iter().skip(1)) + .map(|(p, n)| (*n - *p).num_days()) + .max() + .unwrap(); + let min = kps + .iter() + .zip(kps.iter().skip(1)) + .map(|(p, n)| (*n - *p).num_days()) + .min() + .unwrap(); + assert_eq!(max, min); + assert_eq!(max % 7, 0); + } + + #[test] + fn test_date_range_short() { + let range = Utc.ymd(2019, 1, 1)..Utc.ymd(2019, 1, 21); + let ranged_coord = Into::<RangedDate<_>>::into(range); + + let kps = ranged_coord.key_points(4); + + assert_eq!(kps.len(), 3); + + let max = kps + .iter() + .zip(kps.iter().skip(1)) + .map(|(p, n)| (*n - *p).num_days()) + .max() + .unwrap(); + let min = kps + .iter() + .zip(kps.iter().skip(1)) + .map(|(p, n)| (*n - *p).num_days()) + .min() + .unwrap(); + assert_eq!(max, min); + assert_eq!(max, 7); + + let kps = ranged_coord.key_points(30); + assert_eq!(kps.len(), 21); + let max = kps + .iter() + .zip(kps.iter().skip(1)) + .map(|(p, n)| (*n - *p).num_days()) + .max() + .unwrap(); + let min = kps + .iter() + .zip(kps.iter().skip(1)) + .map(|(p, n)| (*n - *p).num_days()) + .min() + .unwrap(); + assert_eq!(max, min); + assert_eq!(max, 1); + } + + #[test] + fn test_yearly_date_range() { + use crate::coord::ranged1d::BoldPoints; + let range = Utc.ymd(1000, 8, 5)..Utc.ymd(2999, 1, 1); + let ranged_coord = range.yearly(); + + assert_eq!(ranged_coord.map(&Utc.ymd(1000, 8, 10), (0, 100)), 0); + assert_eq!(ranged_coord.map(&Utc.ymd(2999, 8, 10), (0, 100)), 100); + + let kps = ranged_coord.key_points(23); + + assert!(kps.len() <= 23); + let max = kps + .iter() + .zip(kps.iter().skip(1)) + .map(|(p, n)| (*n - *p).num_days()) + .max() + .unwrap(); + let min = kps + .iter() + .zip(kps.iter().skip(1)) + .map(|(p, n)| (*n - *p).num_days()) + .min() + .unwrap(); + assert!(max != min); + + assert!(kps.into_iter().all(|x| x.month() == 9 && x.day() == 1)); + + let range = Utc.ymd(2019, 8, 5)..Utc.ymd(2020, 1, 1); + let ranged_coord = range.yearly(); + let kps = ranged_coord.key_points(BoldPoints(23)); + assert!(kps.len() == 1); + } + + #[test] + fn test_monthly_date_range() { + let range = Utc.ymd(2019, 8, 5)..Utc.ymd(2020, 9, 1); + let ranged_coord = range.monthly(); + + use crate::coord::ranged1d::BoldPoints; + + let kps = ranged_coord.key_points(BoldPoints(15)); + + assert!(kps.len() <= 15); + assert!(kps.iter().all(|x| x.day() == 1)); + assert!(kps.into_iter().any(|x| x.month() != 9)); + + let kps = ranged_coord.key_points(BoldPoints(5)); + assert!(kps.len() <= 5); + assert!(kps.iter().all(|x| x.day() == 1)); + let kps: Vec<_> = kps.into_iter().map(|x| x.month()).collect(); + assert_eq!(kps, vec![9, 12, 3, 6, 9]); + + // TODO: Investigate why max_point = 1 breaks the contract + let kps = ranged_coord.key_points(3); + assert!(kps.len() == 3); + assert!(kps.iter().all(|x| x.day() == 1)); + let kps: Vec<_> = kps.into_iter().map(|x| x.month()).collect(); + assert_eq!(kps, vec![9, 3, 9]); + } + + #[test] + fn test_datetime_long_range() { + let coord: RangedDateTime<_> = + (Utc.ymd(1000, 1, 1).and_hms(0, 0, 0)..Utc.ymd(3000, 1, 1).and_hms(0, 0, 0)).into(); + + assert_eq!( + coord.map(&Utc.ymd(1000, 1, 1).and_hms(0, 0, 0), (0, 100)), + 0 + ); + assert_eq!( + coord.map(&Utc.ymd(3000, 1, 1).and_hms(0, 0, 0), (0, 100)), + 100 + ); + + let kps = coord.key_points(23); + + assert!(kps.len() <= 23); + let max = kps + .iter() + .zip(kps.iter().skip(1)) + .map(|(p, n)| (*n - *p).num_seconds()) + .max() + .unwrap(); + let min = kps + .iter() + .zip(kps.iter().skip(1)) + .map(|(p, n)| (*n - *p).num_seconds()) + .min() + .unwrap(); + assert!(max == min); + assert!(max % (24 * 3600 * 7) == 0); + } + + #[test] + fn test_datetime_medium_range() { + let coord: RangedDateTime<_> = + (Utc.ymd(2019, 1, 1).and_hms(0, 0, 0)..Utc.ymd(2019, 1, 11).and_hms(0, 0, 0)).into(); + + let kps = coord.key_points(23); + + assert!(kps.len() <= 23); + let max = kps + .iter() + .zip(kps.iter().skip(1)) + .map(|(p, n)| (*n - *p).num_seconds()) + .max() + .unwrap(); + let min = kps + .iter() + .zip(kps.iter().skip(1)) + .map(|(p, n)| (*n - *p).num_seconds()) + .min() + .unwrap(); + assert!(max == min); + assert_eq!(max, 12 * 3600); + } + + #[test] + fn test_datetime_short_range() { + let coord: RangedDateTime<_> = + (Utc.ymd(2019, 1, 1).and_hms(0, 0, 0)..Utc.ymd(2019, 1, 2).and_hms(0, 0, 0)).into(); + + let kps = coord.key_points(50); + + assert!(kps.len() <= 50); + let max = kps + .iter() + .zip(kps.iter().skip(1)) + .map(|(p, n)| (*n - *p).num_seconds()) + .max() + .unwrap(); + let min = kps + .iter() + .zip(kps.iter().skip(1)) + .map(|(p, n)| (*n - *p).num_seconds()) + .min() + .unwrap(); + assert!(max == min); + assert_eq!(max, 1800); + } + + #[test] + fn test_datetime_nano_range() { + let start = Utc.ymd(2019, 1, 1).and_hms(0, 0, 0); + let end = start.clone() + Duration::nanoseconds(100); + let coord: RangedDateTime<_> = (start..end).into(); + + let kps = coord.key_points(50); + + assert!(kps.len() <= 50); + let max = kps + .iter() + .zip(kps.iter().skip(1)) + .map(|(p, n)| (*n - *p).num_nanoseconds().unwrap()) + .max() + .unwrap(); + let min = kps + .iter() + .zip(kps.iter().skip(1)) + .map(|(p, n)| (*n - *p).num_nanoseconds().unwrap()) + .min() + .unwrap(); + assert!(max == min); + assert_eq!(max, 2); + } + + #[test] + fn test_duration_long_range() { + let coord: RangedDuration = (Duration::days(-1000000)..Duration::days(1000000)).into(); + + assert_eq!(coord.map(&Duration::days(-1000000), (0, 100)), 0); + assert_eq!(coord.map(&Duration::days(1000000), (0, 100)), 100); + + let kps = coord.key_points(23); + + assert!(kps.len() <= 23); + let max = kps + .iter() + .zip(kps.iter().skip(1)) + .map(|(p, n)| (*n - *p).num_seconds()) + .max() + .unwrap(); + let min = kps + .iter() + .zip(kps.iter().skip(1)) + .map(|(p, n)| (*n - *p).num_seconds()) + .min() + .unwrap(); + assert!(max == min); + assert!(max % (24 * 3600 * 10000) == 0); + } + + #[test] + fn test_duration_daily_range() { + let coord: RangedDuration = (Duration::days(0)..Duration::hours(25)).into(); + + let kps = coord.key_points(23); + + assert!(kps.len() <= 23); + let max = kps + .iter() + .zip(kps.iter().skip(1)) + .map(|(p, n)| (*n - *p).num_seconds()) + .max() + .unwrap(); + let min = kps + .iter() + .zip(kps.iter().skip(1)) + .map(|(p, n)| (*n - *p).num_seconds()) + .min() + .unwrap(); + assert!(max == min); + assert_eq!(max, 3600 * 2); + } + + #[test] + fn test_date_discrete() { + let coord: RangedDate<Date<_>> = (Utc.ymd(2019, 1, 1)..Utc.ymd(2019, 12, 31)).into(); + assert_eq!(coord.size(), 365); + assert_eq!(coord.index_of(&Utc.ymd(2019, 2, 28)), Some(31 + 28 - 1)); + assert_eq!(coord.from_index(364), Some(Utc.ymd(2019, 12, 31))); + } + + #[test] + fn test_monthly_discrete() { + let coord1 = (Utc.ymd(2019, 1, 10)..Utc.ymd(2019, 12, 31)).monthly(); + let coord2 = (Utc.ymd(2019, 1, 10)..Utc.ymd(2020, 1, 1)).monthly(); + assert_eq!(coord1.size(), 12); + assert_eq!(coord2.size(), 13); + + for i in 1..=12 { + assert_eq!(coord1.from_index(i - 1).unwrap().month(), i as u32); + assert_eq!( + coord1.index_of(&coord1.from_index(i - 1).unwrap()).unwrap(), + i - 1 + ); + } + } + + #[test] + fn test_yearly_discrete() { + let coord1 = (Utc.ymd(2000, 1, 10)..Utc.ymd(2019, 12, 31)).yearly(); + assert_eq!(coord1.size(), 20); + + for i in 0..20 { + assert_eq!(coord1.from_index(i).unwrap().year(), 2000 + i as i32); + assert_eq!(coord1.index_of(&coord1.from_index(i).unwrap()).unwrap(), i); + } + } +} diff --git a/vendor/plotters/src/coord/ranged1d/types/mod.rs b/vendor/plotters/src/coord/ranged1d/types/mod.rs new file mode 100644 index 000000000..5a5ca4831 --- /dev/null +++ b/vendor/plotters/src/coord/ranged1d/types/mod.rs @@ -0,0 +1,15 @@ +#[cfg(feature = "chrono")] +mod datetime; +#[cfg(feature = "chrono")] +pub use datetime::{ + IntoMonthly, IntoYearly, Monthly, RangedDate, RangedDateTime, RangedDuration, Yearly, +}; + +mod numeric; +pub use numeric::{ + RangedCoordf32, RangedCoordf64, RangedCoordi128, RangedCoordi32, RangedCoordi64, + RangedCoordu128, RangedCoordu32, RangedCoordu64, RangedCoordusize, +}; + +mod slice; +pub use slice::RangedSlice; diff --git a/vendor/plotters/src/coord/ranged1d/types/numeric.rs b/vendor/plotters/src/coord/ranged1d/types/numeric.rs new file mode 100644 index 000000000..a4e7b2b61 --- /dev/null +++ b/vendor/plotters/src/coord/ranged1d/types/numeric.rs @@ -0,0 +1,453 @@ +use std::convert::TryFrom; +use std::ops::Range; + +use crate::coord::ranged1d::{ + AsRangedCoord, DefaultFormatting, DiscreteRanged, KeyPointHint, NoDefaultFormatting, Ranged, + ReversibleRanged, ValueFormatter, +}; + +macro_rules! impl_discrete_trait { + ($name:ident) => { + impl DiscreteRanged for $name { + fn size(&self) -> usize { + if &self.1 < &self.0 { + return 0; + } + let values = self.1 - self.0; + (values + 1) as usize + } + + fn index_of(&self, value: &Self::ValueType) -> Option<usize> { + if value < &self.0 { + return None; + } + let ret = value - self.0; + Some(ret as usize) + } + + fn from_index(&self, index: usize) -> Option<Self::ValueType> { + if let Ok(index) = Self::ValueType::try_from(index) { + return Some(self.0 + index); + } + None + } + } + }; +} + +macro_rules! impl_ranged_type_trait { + ($value:ty, $coord:ident) => { + impl AsRangedCoord for Range<$value> { + type CoordDescType = $coord; + type Value = $value; + } + }; +} +macro_rules! impl_reverse_mapping_trait { + ($type:ty, $name: ident) => { + impl ReversibleRanged for $name { + fn unmap(&self, p: i32, (min, max): (i32, i32)) -> Option<$type> { + if p < min.min(max) || p > max.max(min) || min == max { + return None; + } + + let logical_offset = f64::from(p - min) / f64::from(max - min); + + return Some(((self.1 - self.0) as f64 * logical_offset + self.0 as f64) as $type); + } + } + }; +} +macro_rules! make_numeric_coord { + ($type:ty, $name:ident, $key_points:ident, $doc: expr, $fmt: ident) => { + #[doc = $doc] + #[derive(Clone)] + pub struct $name($type, $type); + impl From<Range<$type>> for $name { + fn from(range: Range<$type>) -> Self { + return $name(range.start, range.end); + } + } + impl Ranged for $name { + type FormatOption = $fmt; + type ValueType = $type; + #[allow(clippy::float_cmp)] + fn map(&self, v: &$type, limit: (i32, i32)) -> i32 { + // Corner case: If we have a range that have only one value, + // then we just assign everything to the only point + if self.1 == self.0 { + return (limit.1 - limit.0) / 2; + } + + let logic_length = (*v as f64 - self.0 as f64) / (self.1 as f64 - self.0 as f64); + + let actual_length = limit.1 - limit.0; + + if actual_length == 0 { + return limit.1; + } + + if actual_length > 0 { + return limit.0 + (actual_length as f64 * logic_length + 1e-3).floor() as i32; + } else { + return limit.0 + (actual_length as f64 * logic_length - 1e-3).ceil() as i32; + } + } + fn key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<$type> { + $key_points((self.0, self.1), hint.max_num_points()) + } + fn range(&self) -> Range<$type> { + return self.0..self.1; + } + } + }; + ($type:ty, $name:ident, $key_points:ident, $doc: expr) => { + make_numeric_coord!($type, $name, $key_points, $doc, DefaultFormatting); + }; +} + +macro_rules! gen_key_points_comp { + (float, $name:ident, $type:ty) => { + fn $name(range: ($type, $type), max_points: usize) -> Vec<$type> { + if max_points == 0 { + return vec![]; + } + + let range = (range.0.min(range.1) as f64, range.1.max(range.0) as f64); + + assert!(!(range.0.is_nan() || range.1.is_nan())); + + if (range.0 - range.1).abs() < std::f64::EPSILON { + return vec![range.0 as $type]; + } + + let mut scale = (10f64).powf((range.1 - range.0).log(10.0).floor()); + // The value granularity controls how we round the values. + // To avoid generating key points like 1.00000000001, we round to the nearest multiple of the + // value granularity. + // By default, we make the granularity as the 1/10 of the scale. + let mut value_granularity = scale / 10.0; + fn rem_euclid(a: f64, b: f64) -> f64 { + let ret = if b > 0.0 { + a - (a / b).floor() * b + } else { + a - (a / b).ceil() * b + }; + if (ret - b).abs() < std::f64::EPSILON { + 0.0 + } else { + ret + } + } + + // At this point we need to make sure that the loop invariant: + // The scale must yield number of points than requested + if 1 + ((range.1 - range.0) / scale).floor() as usize > max_points { + scale *= 10.0; + value_granularity *= 10.0; + } + + 'outer: loop { + let old_scale = scale; + for nxt in [2.0, 5.0, 10.0].iter() { + let mut new_left = range.0 - rem_euclid(range.0, old_scale / nxt); + if new_left < range.0 { + new_left += old_scale / nxt; + } + let new_right = range.1 - rem_euclid(range.1, old_scale / nxt); + + let npoints = 1.0 + ((new_right - new_left) / old_scale * nxt); + + if npoints.round() as usize > max_points { + break 'outer; + } + + scale = old_scale / nxt; + } + scale = old_scale / 10.0; + value_granularity /= 10.0; + } + + let mut ret = vec![]; + // In some extreme cases, left might be too big, so that (left + scale) - left == 0 due to + // floating point error. + // In this case, we may loop forever. To avoid this, we need to use two variables to store + // the current left value. So we need keep a left_base and a left_relative. + let left = { + let mut value = range.0 - rem_euclid(range.0, scale); + if value < range.0 { + value += scale; + } + value + }; + let left_base = (left / value_granularity).floor() * value_granularity; + let mut left_relative = left - left_base; + let right = range.1 - rem_euclid(range.1, scale); + while (right - left_relative - left_base) >= -std::f64::EPSILON { + let new_left_relative = + (left_relative / value_granularity).round() * value_granularity; + if new_left_relative < 0.0 { + left_relative += value_granularity; + } + ret.push((left_relative + left_base) as $type); + left_relative += scale; + } + return ret; + } + }; + (integer, $name:ident, $type:ty) => { + fn $name(range: ($type, $type), max_points: usize) -> Vec<$type> { + let mut scale: $type = 1; + let range = (range.0.min(range.1), range.0.max(range.1)); + let range_size = range.1 as f64 - range.0 as f64; + 'outer: while (range_size / scale as f64).ceil() > max_points as f64 { + let next_scale = scale * 10; + for new_scale in [scale * 2, scale * 5, scale * 10].iter() { + scale = *new_scale; + if (range_size / *new_scale as f64).ceil() < max_points as f64 { + break 'outer; + } + } + scale = next_scale; + } + + let (mut left, right) = ( + range.0 + (scale - range.0 % scale) % scale, + range.1 - range.1 % scale, + ); + + let mut ret = vec![]; + while left <= right { + ret.push(left as $type); + if left < right { + left += scale; + } else { + break; + } + } + + return ret; + } + }; +} + +gen_key_points_comp!(float, compute_f32_key_points, f32); +gen_key_points_comp!(float, compute_f64_key_points, f64); +gen_key_points_comp!(integer, compute_i32_key_points, i32); +gen_key_points_comp!(integer, compute_u32_key_points, u32); +gen_key_points_comp!(integer, compute_i64_key_points, i64); +gen_key_points_comp!(integer, compute_u64_key_points, u64); +gen_key_points_comp!(integer, compute_i128_key_points, i128); +gen_key_points_comp!(integer, compute_u128_key_points, u128); +gen_key_points_comp!(integer, compute_isize_key_points, isize); +gen_key_points_comp!(integer, compute_usize_key_points, usize); + +make_numeric_coord!( + f32, + RangedCoordf32, + compute_f32_key_points, + "The ranged coordinate for type f32", + NoDefaultFormatting +); +impl_reverse_mapping_trait!(f32, RangedCoordf32); +impl ValueFormatter<f32> for RangedCoordf32 { + fn format(value: &f32) -> String { + crate::data::float::FloatPrettyPrinter { + allow_scientific: false, + min_decimal: 1, + max_decimal: 5, + } + .print(*value as f64) + } +} +make_numeric_coord!( + f64, + RangedCoordf64, + compute_f64_key_points, + "The ranged coordinate for type f64", + NoDefaultFormatting +); +impl_reverse_mapping_trait!(f64, RangedCoordf64); +impl ValueFormatter<f64> for RangedCoordf64 { + fn format(value: &f64) -> String { + crate::data::float::FloatPrettyPrinter { + allow_scientific: false, + min_decimal: 1, + max_decimal: 5, + } + .print(*value) + } +} +make_numeric_coord!( + u32, + RangedCoordu32, + compute_u32_key_points, + "The ranged coordinate for type u32" +); +make_numeric_coord!( + i32, + RangedCoordi32, + compute_i32_key_points, + "The ranged coordinate for type i32" +); +make_numeric_coord!( + u64, + RangedCoordu64, + compute_u64_key_points, + "The ranged coordinate for type u64" +); +make_numeric_coord!( + i64, + RangedCoordi64, + compute_i64_key_points, + "The ranged coordinate for type i64" +); +make_numeric_coord!( + u128, + RangedCoordu128, + compute_u128_key_points, + "The ranged coordinate for type u128" +); +make_numeric_coord!( + i128, + RangedCoordi128, + compute_i128_key_points, + "The ranged coordinate for type i128" +); +make_numeric_coord!( + usize, + RangedCoordusize, + compute_usize_key_points, + "The ranged coordinate for type usize" +); +make_numeric_coord!( + isize, + RangedCoordisize, + compute_isize_key_points, + "The ranged coordinate for type isize" +); + +impl_discrete_trait!(RangedCoordu32); +impl_discrete_trait!(RangedCoordi32); +impl_discrete_trait!(RangedCoordu64); +impl_discrete_trait!(RangedCoordi64); +impl_discrete_trait!(RangedCoordu128); +impl_discrete_trait!(RangedCoordi128); +impl_discrete_trait!(RangedCoordusize); +impl_discrete_trait!(RangedCoordisize); + +impl_ranged_type_trait!(f32, RangedCoordf32); +impl_ranged_type_trait!(f64, RangedCoordf64); +impl_ranged_type_trait!(i32, RangedCoordi32); +impl_ranged_type_trait!(u32, RangedCoordu32); +impl_ranged_type_trait!(i64, RangedCoordi64); +impl_ranged_type_trait!(u64, RangedCoordu64); +impl_ranged_type_trait!(i128, RangedCoordi128); +impl_ranged_type_trait!(u128, RangedCoordu128); +impl_ranged_type_trait!(isize, RangedCoordisize); +impl_ranged_type_trait!(usize, RangedCoordusize); + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_key_points() { + let kp = compute_i32_key_points((0, 999), 28); + + assert!(kp.len() > 0); + assert!(kp.len() <= 28); + + let kp = compute_f64_key_points((-1.2, 1.2), 1); + assert!(kp.len() == 1); + + let kp = compute_f64_key_points((-1.2, 1.2), 0); + assert!(kp.len() == 0); + } + + #[test] + fn test_linear_coord_map() { + let coord: RangedCoordu32 = (0..20).into(); + assert_eq!(coord.key_points(11).len(), 11); + assert_eq!(coord.key_points(11)[0], 0); + assert_eq!(coord.key_points(11)[10], 20); + assert_eq!(coord.map(&5, (0, 100)), 25); + + let coord: RangedCoordf32 = (0f32..20f32).into(); + assert_eq!(coord.map(&5.0, (0, 100)), 25); + } + + #[test] + fn test_linear_coord_system() { + let _coord = + crate::coord::ranged2d::cartesian::Cartesian2d::<RangedCoordu32, RangedCoordu32>::new( + 0..10, + 0..10, + (0..1024, 0..768), + ); + } + + #[test] + fn test_coord_unmap() { + let coord: RangedCoordu32 = (0..20).into(); + let pos = coord.map(&5, (1000, 2000)); + let value = coord.unmap(pos, (1000, 2000)); + assert_eq!(value, Some(5)); + } + + #[test] + fn regression_test_issue_253_zero_sized_coord_not_hang() { + let coord: RangedCoordf32 = (0.0..0.0).into(); + let _points = coord.key_points(10); + } + + #[test] + fn test_small_coord() { + let coord: RangedCoordf64 = (0.0..1e-25).into(); + let points = coord.key_points(10); + assert!(points.len() > 0); + } + + #[test] + fn regression_test_issue_255_reverse_f32_coord_no_hang() { + let coord: RangedCoordf32 = (10.0..0.0).into(); + let _points = coord.key_points(10); + } + + #[test] + fn regession_test_issue_358_key_points_no_hang() { + let coord: RangedCoordf64 = (-200.0..801.0).into(); + let points = coord.key_points(500); + assert!(points.len() <= 500); + } + + #[test] + fn regression_test_issue_358_key_points_no_hang_2() { + let coord: RangedCoordf64 = (10000000000001f64..10000000000002f64).into(); + let points = coord.key_points(500); + assert!(points.len() <= 500); + } + + #[test] + fn test_coord_follows_hint() { + let coord: RangedCoordf64 = (1.0..2.0).into(); + let points = coord.key_points(6); + assert_eq!(points.len(), 6); + assert_eq!(points[0], 1.0); + let coord: RangedCoordf64 = (1.0..125.0).into(); + let points = coord.key_points(12); + assert_eq!(points.len(), 12); + let coord: RangedCoordf64 = (0.9995..1.0005).into(); + let points = coord.key_points(11); + assert_eq!(points.len(), 11); + let coord: RangedCoordf64 = (0.9995..1.0005).into(); + let points = coord.key_points(2); + assert!(points.len() <= 2); + } + + #[test] + fn regression_test_issue_304_intmax_keypoint_no_panic() { + let coord: RangedCoordu32 = (0..u32::MAX).into(); + let p = coord.key_points(10); + assert!(p.len() > 0 && p.len() <= 10); + } +} diff --git a/vendor/plotters/src/coord/ranged1d/types/slice.rs b/vendor/plotters/src/coord/ranged1d/types/slice.rs new file mode 100644 index 000000000..13be3d7f4 --- /dev/null +++ b/vendor/plotters/src/coord/ranged1d/types/slice.rs @@ -0,0 +1,100 @@ +use crate::coord::ranged1d::{ + AsRangedCoord, DefaultFormatting, DiscreteRanged, KeyPointHint, Ranged, +}; +use std::ops::Range; + +/// A range that is defined by a slice of values. +/// +/// Please note: the behavior of constructing an empty range may cause panic +#[derive(Clone)] +pub struct RangedSlice<'a, T: PartialEq>(&'a [T]); + +impl<'a, T: PartialEq> Ranged for RangedSlice<'a, T> { + type FormatOption = DefaultFormatting; + type ValueType = &'a T; + + fn range(&self) -> Range<&'a T> { + // If inner slice is empty, we should always panic + &self.0[0]..&self.0[self.0.len() - 1] + } + + fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { + match self.0.iter().position(|x| &x == value) { + Some(pos) => { + let pixel_span = limit.1 - limit.0; + let value_span = self.0.len() - 1; + (f64::from(limit.0) + + f64::from(pixel_span) + * (f64::from(pos as u32) / f64::from(value_span as u32))) + .round() as i32 + } + None => limit.0, + } + } + + fn key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<Self::ValueType> { + let max_points = hint.max_num_points(); + let mut ret = vec![]; + let intervals = (self.0.len() - 1) as f64; + let step = (intervals / max_points as f64 + 1.0) as usize; + for idx in (0..self.0.len()).step_by(step) { + ret.push(&self.0[idx]); + } + ret + } +} + +impl<'a, T: PartialEq> DiscreteRanged for RangedSlice<'a, T> { + fn size(&self) -> usize { + self.0.len() + } + + fn index_of(&self, value: &&'a T) -> Option<usize> { + self.0.iter().position(|x| &x == value) + } + + fn from_index(&self, index: usize) -> Option<&'a T> { + if self.0.len() <= index { + return None; + } + Some(&self.0[index]) + } +} + +impl<'a, T: PartialEq> From<&'a [T]> for RangedSlice<'a, T> { + fn from(range: &'a [T]) -> Self { + RangedSlice(range) + } +} + +impl<'a, T: PartialEq> AsRangedCoord for &'a [T] { + type CoordDescType = RangedSlice<'a, T>; + type Value = &'a T; +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_slice_range() { + let my_slice = [1, 2, 3, 0, -1, -2]; + let slice_range: RangedSlice<i32> = my_slice[..].into(); + + assert_eq!(slice_range.range(), &1..&-2); + assert_eq!( + slice_range.key_points(6), + my_slice.iter().collect::<Vec<_>>() + ); + assert_eq!(slice_range.map(&&0, (0, 50)), 30); + } + + #[test] + fn test_slice_range_discrete() { + let my_slice = [1, 2, 3, 0, -1, -2]; + let slice_range: RangedSlice<i32> = my_slice[..].into(); + + assert_eq!(slice_range.size(), 6); + assert_eq!(slice_range.index_of(&&3), Some(2)); + assert_eq!(slice_range.from_index(2), Some(&3)); + } +} diff --git a/vendor/plotters/src/coord/ranged2d/cartesian.rs b/vendor/plotters/src/coord/ranged2d/cartesian.rs new file mode 100644 index 000000000..5052a62f1 --- /dev/null +++ b/vendor/plotters/src/coord/ranged2d/cartesian.rs @@ -0,0 +1,154 @@ +/*! + The 2-dimensional cartesian coordinate system. + + This module provides the 2D cartesian coordinate system, which is composed by two independent + ranged 1D coordinate sepcification. + + This types of coordinate system is used by the chart constructed with [ChartBuilder::build_cartesian_2d](../../chart/ChartBuilder.html#method.build_cartesian_2d). +*/ + +use crate::coord::ranged1d::{KeyPointHint, Ranged, ReversibleRanged}; +use crate::coord::{CoordTranslate, ReverseCoordTranslate}; + +use crate::style::ShapeStyle; +use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; + +use std::ops::Range; + +/// A 2D Cartesian coordinate system described by two 1D ranged coordinate specs. +#[derive(Clone)] +pub struct Cartesian2d<X: Ranged, Y: Ranged> { + logic_x: X, + logic_y: Y, + back_x: (i32, i32), + back_y: (i32, i32), +} + +impl<X: Ranged, Y: Ranged> Cartesian2d<X, Y> { + /// Create a new 2D cartesian coordinate system + /// - `logic_x` and `logic_y` : The description for the 1D coordinate system + /// - `actual`: The pixel range on the screen for this coordinate system + pub fn new<IntoX: Into<X>, IntoY: Into<Y>>( + logic_x: IntoX, + logic_y: IntoY, + actual: (Range<i32>, Range<i32>), + ) -> Self { + Self { + logic_x: logic_x.into(), + logic_y: logic_y.into(), + back_x: (actual.0.start, actual.0.end), + back_y: (actual.1.start, actual.1.end), + } + } + + /// Draw the mesh for the coordinate system + pub fn draw_mesh< + E, + DrawMesh: FnMut(MeshLine<X, Y>) -> Result<(), E>, + XH: KeyPointHint, + YH: KeyPointHint, + >( + &self, + h_limit: YH, + v_limit: XH, + mut draw_mesh: DrawMesh, + ) -> Result<(), E> { + let (xkp, ykp) = ( + self.logic_x.key_points(v_limit), + self.logic_y.key_points(h_limit), + ); + + for logic_x in xkp { + let x = self.logic_x.map(&logic_x, self.back_x); + draw_mesh(MeshLine::XMesh( + (x, self.back_y.0), + (x, self.back_y.1), + &logic_x, + ))?; + } + + for logic_y in ykp { + let y = self.logic_y.map(&logic_y, self.back_y); + draw_mesh(MeshLine::YMesh( + (self.back_x.0, y), + (self.back_x.1, y), + &logic_y, + ))?; + } + + Ok(()) + } + + /// Get the range of X axis + pub fn get_x_range(&self) -> Range<X::ValueType> { + self.logic_x.range() + } + + /// Get the range of Y axis + pub fn get_y_range(&self) -> Range<Y::ValueType> { + self.logic_y.range() + } + + /// Get the horizental backend coordinate range where X axis should be drawn + pub fn get_x_axis_pixel_range(&self) -> Range<i32> { + self.logic_x.axis_pixel_range(self.back_x) + } + + /// Get the vertical backend coordinate range where Y axis should be drawn + pub fn get_y_axis_pixel_range(&self) -> Range<i32> { + self.logic_y.axis_pixel_range(self.back_y) + } + + /// Get the 1D coordinate spec for X axis + pub fn x_spec(&self) -> &X { + &self.logic_x + } + + /// Get the 1D coordinate spec for Y axis + pub fn y_spec(&self) -> &Y { + &self.logic_y + } +} + +impl<X: Ranged, Y: Ranged> CoordTranslate for Cartesian2d<X, Y> { + type From = (X::ValueType, Y::ValueType); + + fn translate(&self, from: &Self::From) -> BackendCoord { + ( + self.logic_x.map(&from.0, self.back_x), + self.logic_y.map(&from.1, self.back_y), + ) + } +} + +impl<X: ReversibleRanged, Y: ReversibleRanged> ReverseCoordTranslate for Cartesian2d<X, Y> { + fn reverse_translate(&self, input: BackendCoord) -> Option<Self::From> { + Some(( + self.logic_x.unmap(input.0, self.back_x)?, + self.logic_y.unmap(input.1, self.back_y)?, + )) + } +} + +/// Represent a coordinate mesh for the two ranged value coordinate system +pub enum MeshLine<'a, X: Ranged, Y: Ranged> { + /// Used to plot the horizontal lines of the mesh + XMesh(BackendCoord, BackendCoord, &'a X::ValueType), + /// Used to plot the vertical lines of the mesh + YMesh(BackendCoord, BackendCoord, &'a Y::ValueType), +} + +impl<'a, X: Ranged, Y: Ranged> MeshLine<'a, X, Y> { + /// Draw a single mesh line onto the backend + pub fn draw<DB: DrawingBackend>( + &self, + backend: &mut DB, + style: &ShapeStyle, + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + let (&left, &right) = match self { + MeshLine::XMesh(a, b, _) => (a, b), + MeshLine::YMesh(a, b, _) => (a, b), + }; + backend.draw_line(left, right, style) + } +} diff --git a/vendor/plotters/src/coord/ranged2d/mod.rs b/vendor/plotters/src/coord/ranged2d/mod.rs new file mode 100644 index 000000000..eae9425c2 --- /dev/null +++ b/vendor/plotters/src/coord/ranged2d/mod.rs @@ -0,0 +1 @@ +pub mod cartesian; diff --git a/vendor/plotters/src/coord/ranged3d/cartesian3d.rs b/vendor/plotters/src/coord/ranged3d/cartesian3d.rs new file mode 100644 index 000000000..8719680c5 --- /dev/null +++ b/vendor/plotters/src/coord/ranged3d/cartesian3d.rs @@ -0,0 +1,131 @@ +use super::{ProjectionMatrix, ProjectionMatrixBuilder}; +use crate::coord::ranged1d::Ranged; +use crate::coord::CoordTranslate; +use plotters_backend::BackendCoord; + +use std::ops::Range; + +/// A 3D cartesian coordinate system +#[derive(Clone)] +pub struct Cartesian3d<X: Ranged, Y: Ranged, Z: Ranged> { + pub(crate) logic_x: X, + pub(crate) logic_y: Y, + pub(crate) logic_z: Z, + coord_size: (i32, i32, i32), + projection: ProjectionMatrix, +} + +impl<X: Ranged, Y: Ranged, Z: Ranged> Cartesian3d<X, Y, Z> { + fn compute_default_size(actual_x: Range<i32>, actual_y: Range<i32>) -> i32 { + (actual_x.end - actual_x.start).min(actual_y.end - actual_y.start) * 4 / 5 + } + fn create_projection<F: FnOnce(ProjectionMatrixBuilder) -> ProjectionMatrix>( + actual_x: Range<i32>, + actual_y: Range<i32>, + coord_size: (i32, i32, i32), + f: F, + ) -> ProjectionMatrix { + let center_3d = (coord_size.0 / 2, coord_size.1 / 2, coord_size.2 / 2); + let center_2d = ( + (actual_x.end + actual_x.start) / 2, + (actual_y.end + actual_y.start) / 2, + ); + let mut pb = ProjectionMatrixBuilder::new(); + pb.set_pivot(center_3d, center_2d); + f(pb) + } + /// Creates a Cartesian3d object with the given projection. + pub fn with_projection< + SX: Into<X>, + SY: Into<Y>, + SZ: Into<Z>, + F: FnOnce(ProjectionMatrixBuilder) -> ProjectionMatrix, + >( + logic_x: SX, + logic_y: SY, + logic_z: SZ, + (actual_x, actual_y): (Range<i32>, Range<i32>), + build_projection_matrix: F, + ) -> Self { + let default_size = Self::compute_default_size(actual_x.clone(), actual_y.clone()); + let coord_size = (default_size, default_size, default_size); + Self { + logic_x: logic_x.into(), + logic_y: logic_y.into(), + logic_z: logic_z.into(), + coord_size, + projection: Self::create_projection( + actual_x, + actual_y, + coord_size, + build_projection_matrix, + ), + } + } + + /// Sets the pixel sizes and projections according to the given ranges. + pub fn set_coord_pixel_range( + &mut self, + actual_x: Range<i32>, + actual_y: Range<i32>, + coord_size: (i32, i32, i32), + ) -> &mut Self { + self.coord_size = coord_size; + self.projection = + Self::create_projection(actual_x, actual_y, coord_size, |pb| pb.into_matrix()); + self + } + + /// Set the projection matrix + pub fn set_projection<F: FnOnce(ProjectionMatrixBuilder) -> ProjectionMatrix>( + &mut self, + actual_x: Range<i32>, + actual_y: Range<i32>, + f: F, + ) -> &mut Self { + self.projection = Self::create_projection(actual_x, actual_y, self.coord_size, f); + self + } + + /// Create a new coordinate + pub fn new<SX: Into<X>, SY: Into<Y>, SZ: Into<Z>>( + logic_x: SX, + logic_y: SY, + logic_z: SZ, + (actual_x, actual_y): (Range<i32>, Range<i32>), + ) -> Self { + Self::with_projection(logic_x, logic_y, logic_z, (actual_x, actual_y), |pb| { + pb.into_matrix() + }) + } + /// Get the projection matrix + pub fn projection(&self) -> &ProjectionMatrix { + &self.projection + } + + /// Do not project, only transform the guest coordinate system + pub fn map_3d(&self, x: &X::ValueType, y: &Y::ValueType, z: &Z::ValueType) -> (i32, i32, i32) { + ( + self.logic_x.map(x, (0, self.coord_size.0)), + self.logic_y.map(y, (0, self.coord_size.1)), + self.logic_z.map(z, (0, self.coord_size.2)), + ) + } + + /// Get the depth of the projection + pub fn projected_depth(&self, x: &X::ValueType, y: &Y::ValueType, z: &Z::ValueType) -> i32 { + self.projection.projected_depth(self.map_3d(x, y, z)) + } +} + +impl<X: Ranged, Y: Ranged, Z: Ranged> CoordTranslate for Cartesian3d<X, Y, Z> { + type From = (X::ValueType, Y::ValueType, Z::ValueType); + fn translate(&self, coord: &Self::From) -> BackendCoord { + let pixel_coord_3d = self.map_3d(&coord.0, &coord.1, &coord.2); + self.projection * pixel_coord_3d + } + + fn depth(&self, coord: &Self::From) -> i32 { + self.projected_depth(&coord.0, &coord.1, &coord.2) + } +} diff --git a/vendor/plotters/src/coord/ranged3d/mod.rs b/vendor/plotters/src/coord/ranged3d/mod.rs new file mode 100644 index 000000000..274a70d8e --- /dev/null +++ b/vendor/plotters/src/coord/ranged3d/mod.rs @@ -0,0 +1,5 @@ +mod projection; +pub use projection::{ProjectionMatrix, ProjectionMatrixBuilder}; + +mod cartesian3d; +pub use cartesian3d::Cartesian3d; diff --git a/vendor/plotters/src/coord/ranged3d/projection.rs b/vendor/plotters/src/coord/ranged3d/projection.rs new file mode 100644 index 000000000..a9c57c1b3 --- /dev/null +++ b/vendor/plotters/src/coord/ranged3d/projection.rs @@ -0,0 +1,209 @@ +use std::f64::consts::PI; +use std::ops::Mul; + +/// The projection matrix which is used to project the 3D space to the 2D display panel +#[derive(Clone, Debug, Copy)] +pub struct ProjectionMatrix([[f64; 4]; 4]); + +impl AsRef<[[f64; 4]; 4]> for ProjectionMatrix { + fn as_ref(&self) -> &[[f64; 4]; 4] { + &self.0 + } +} + +impl AsMut<[[f64; 4]; 4]> for ProjectionMatrix { + fn as_mut(&mut self) -> &mut [[f64; 4]; 4] { + &mut self.0 + } +} + +impl From<[[f64; 4]; 4]> for ProjectionMatrix { + fn from(data: [[f64; 4]; 4]) -> Self { + ProjectionMatrix(data) + } +} + +impl Default for ProjectionMatrix { + fn default() -> Self { + ProjectionMatrix::rotate(PI, 0.0, 0.0) + } +} + +impl Mul<ProjectionMatrix> for ProjectionMatrix { + type Output = ProjectionMatrix; + fn mul(self, other: ProjectionMatrix) -> ProjectionMatrix { + let mut ret = ProjectionMatrix::zero(); + for r in 0..4 { + for c in 0..4 { + for k in 0..4 { + ret.0[r][c] += other.0[r][k] * self.0[k][c]; + } + } + } + ret.normalize(); + ret + } +} + +impl Mul<(i32, i32, i32)> for ProjectionMatrix { + type Output = (i32, i32); + fn mul(self, (x, y, z): (i32, i32, i32)) -> (i32, i32) { + let (x, y, z) = (x as f64, y as f64, z as f64); + let m = self.0; + ( + (x * m[0][0] + y * m[0][1] + z * m[0][2] + m[0][3]) as i32, + (x * m[1][0] + y * m[1][1] + z * m[1][2] + m[1][3]) as i32, + ) + } +} + +impl Mul<(f64, f64, f64)> for ProjectionMatrix { + type Output = (i32, i32); + fn mul(self, (x, y, z): (f64, f64, f64)) -> (i32, i32) { + let m = self.0; + ( + (x * m[0][0] + y * m[0][1] + z * m[0][2] + m[0][3]) as i32, + (x * m[1][0] + y * m[1][1] + z * m[1][2] + m[1][3]) as i32, + ) + } +} + +impl ProjectionMatrix { + /// Returns the identity matrix + pub fn one() -> Self { + ProjectionMatrix([ + [1.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + ]) + } + /// Returns the zero maxtrix + pub fn zero() -> Self { + ProjectionMatrix([[0.0; 4]; 4]) + } + /// Returns the matrix which shift the coordinate + pub fn shift(x: f64, y: f64, z: f64) -> Self { + ProjectionMatrix([ + [1.0, 0.0, 0.0, x], + [0.0, 1.0, 0.0, y], + [0.0, 0.0, 1.0, z], + [0.0, 0.0, 0.0, 1.0], + ]) + } + /// Returns the matrix which rotates the coordinate + #[allow(clippy::many_single_char_names)] + pub fn rotate(x: f64, y: f64, z: f64) -> Self { + let (c, b, a) = (x, y, z); + ProjectionMatrix([ + [ + a.cos() * b.cos(), + a.cos() * b.sin() * c.sin() - a.sin() * c.cos(), + a.cos() * b.sin() * c.cos() + a.sin() * c.sin(), + 0.0, + ], + [ + a.sin() * b.cos(), + a.sin() * b.sin() * c.sin() + a.cos() * c.cos(), + a.sin() * b.sin() * c.cos() - a.cos() * c.sin(), + 0.0, + ], + [-b.sin(), b.cos() * c.sin(), b.cos() * c.cos(), 0.0], + [0.0, 0.0, 0.0, 1.0], + ]) + } + /// Returns the matrix that applies a scale factor + pub fn scale(factor: f64) -> Self { + ProjectionMatrix([ + [1.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0 / factor], + ]) + } + /// Normalize the matrix, this will make the metric unit to 1 + pub fn normalize(&mut self) { + if self.0[3][3] > 1e-20 { + for r in 0..4 { + for c in 0..4 { + self.0[r][c] /= self.0[3][3]; + } + } + } + } + + /// Get the distance of the point in guest coordinate from the screen in pixels + pub fn projected_depth(&self, (x, y, z): (i32, i32, i32)) -> i32 { + let r = &self.0[2]; + (r[0] * x as f64 + r[1] * y as f64 + r[2] * z as f64 + r[3]) as i32 + } +} + +/// The helper struct to build a projection matrix +#[derive(Copy, Clone)] +pub struct ProjectionMatrixBuilder { + /// Specifies the yaw of the 3D coordinate system + pub yaw: f64, + /// Specifies the pitch of the 3D coordinate system + pub pitch: f64, + /// Specifies the scale of the 3D coordinate system + pub scale: f64, + pivot_before: (i32, i32, i32), + pivot_after: (i32, i32), +} + +impl Default for ProjectionMatrixBuilder { + fn default() -> Self { + Self { + yaw: 0.5, + pitch: 0.15, + scale: 1.0, + pivot_after: (0, 0), + pivot_before: (0, 0, 0), + } + } +} + +impl ProjectionMatrixBuilder { + /// Creates a new, default projection matrix builder object. + pub fn new() -> Self { + Self::default() + } + + /// Set the pivot point, which means the 3D coordinate "before" should be mapped into + /// the 2D coordinatet "after" + pub fn set_pivot(&mut self, before: (i32, i32, i32), after: (i32, i32)) -> &mut Self { + self.pivot_before = before; + self.pivot_after = after; + self + } + + /// Build the matrix based on the configuration + pub fn into_matrix(self) -> ProjectionMatrix { + let mut ret = if self.pivot_before == (0, 0, 0) { + ProjectionMatrix::default() + } else { + let (x, y, z) = self.pivot_before; + ProjectionMatrix::shift(-x as f64, -y as f64, -z as f64) * ProjectionMatrix::default() + }; + + if self.yaw.abs() > 1e-20 { + ret = ret * ProjectionMatrix::rotate(0.0, self.yaw, 0.0); + } + + if self.pitch.abs() > 1e-20 { + ret = ret * ProjectionMatrix::rotate(self.pitch, 0.0, 0.0); + } + + if (self.scale - 1.0).abs() > 1e-20 { + ret = ret * ProjectionMatrix::scale(self.scale); + } + + if self.pivot_after != (0, 0) { + let (x, y) = self.pivot_after; + ret = ret * ProjectionMatrix::shift(x as f64, y as f64, 0.0); + } + + ret + } +} diff --git a/vendor/plotters/src/coord/translate.rs b/vendor/plotters/src/coord/translate.rs new file mode 100644 index 000000000..222f948ac --- /dev/null +++ b/vendor/plotters/src/coord/translate.rs @@ -0,0 +1,38 @@ +use plotters_backend::BackendCoord; +use std::ops::Deref; + +/// The trait that translates some customized object to the backend coordinate +pub trait CoordTranslate { + /// Specifies the object to be translated from + type From; + + /// Translate the guest coordinate to the guest coordinate + fn translate(&self, from: &Self::From) -> BackendCoord; + + /// Get the Z-value of current coordinate + fn depth(&self, _from: &Self::From) -> i32 { + 0 + } +} + +impl<C, T> CoordTranslate for T +where + C: CoordTranslate, + T: Deref<Target = C>, +{ + type From = C::From; + fn translate(&self, from: &Self::From) -> BackendCoord { + self.deref().translate(from) + } +} + +/// The trait indicates that the coordinate system supports reverse transform +/// This is useful when we need an interactive plot, thus we need to map the event +/// from the backend coordinate to the logical coordinate +pub trait ReverseCoordTranslate: CoordTranslate { + /// Reverse translate the coordinate from the drawing coordinate to the + /// logic coordinate. + /// Note: the return value is an option, because it's possible that the drawing + /// coordinate isn't able to be represented in te guest coordinate system + fn reverse_translate(&self, input: BackendCoord) -> Option<Self::From>; +} |