From 9835e2ae736235810b4ea1c162ca5e65c547e770 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 18 May 2024 04:49:50 +0200 Subject: Merging upstream version 1.71.1+dfsg1. Signed-off-by: Daniel Baumann --- .../src/coord/ranged1d/combinators/ckps.rs | 268 +++++ .../src/coord/ranged1d/combinators/group_by.rs | 119 ++ .../src/coord/ranged1d/combinators/linspace.rs | 433 ++++++++ .../src/coord/ranged1d/combinators/logarithmic.rs | 284 +++++ .../plotters/src/coord/ranged1d/combinators/mod.rs | 20 + .../src/coord/ranged1d/combinators/nested.rs | 205 ++++ .../src/coord/ranged1d/combinators/partial_axis.rs | 113 ++ vendor/plotters/src/coord/ranged1d/discrete.rs | 273 +++++ vendor/plotters/src/coord/ranged1d/mod.rs | 243 ++++ .../plotters/src/coord/ranged1d/types/datetime.rs | 1171 ++++++++++++++++++++ vendor/plotters/src/coord/ranged1d/types/mod.rs | 15 + .../plotters/src/coord/ranged1d/types/numeric.rs | 453 ++++++++ vendor/plotters/src/coord/ranged1d/types/slice.rs | 100 ++ 13 files changed, 3697 insertions(+) create mode 100644 vendor/plotters/src/coord/ranged1d/combinators/ckps.rs create mode 100644 vendor/plotters/src/coord/ranged1d/combinators/group_by.rs create mode 100644 vendor/plotters/src/coord/ranged1d/combinators/linspace.rs create mode 100644 vendor/plotters/src/coord/ranged1d/combinators/logarithmic.rs create mode 100644 vendor/plotters/src/coord/ranged1d/combinators/mod.rs create mode 100644 vendor/plotters/src/coord/ranged1d/combinators/nested.rs create mode 100644 vendor/plotters/src/coord/ranged1d/combinators/partial_axis.rs create mode 100644 vendor/plotters/src/coord/ranged1d/discrete.rs create mode 100644 vendor/plotters/src/coord/ranged1d/mod.rs create mode 100644 vendor/plotters/src/coord/ranged1d/types/datetime.rs create mode 100644 vendor/plotters/src/coord/ranged1d/types/mod.rs create mode 100644 vendor/plotters/src/coord/ranged1d/types/numeric.rs create mode 100644 vendor/plotters/src/coord/ranged1d/types/slice.rs (limited to 'vendor/plotters/src/coord/ranged1d') 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: Inner, + bold_points: Vec, + light_points: Vec, +} + +impl WithKeyPoints { + /// Specify the light key points, which is used to render the light mesh line + pub fn with_light_points>(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 Ranged for WithKeyPoints +where + R::ValueType: Clone, +{ + type ValueType = R::ValueType; + type FormatOption = R::FormatOption; + + fn range(&self) -> Range { + self.inner.range() + } + + fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { + self.inner.map(value, limit) + } + + fn key_points(&self, hint: Hint) -> Vec { + if hint.weight().allow_light_points() { + self.light_points.clone() + } else { + self.bold_points.clone() + } + } + + fn axis_pixel_range(&self, limit: (i32, i32)) -> Range { + self.inner.axis_pixel_range(limit) + } +} + +impl DiscreteRanged for WithKeyPoints +where + R::ValueType: Clone, +{ + fn size(&self) -> usize { + self.inner.size() + } + fn index_of(&self, value: &Self::ValueType) -> Option { + self.inner.index_of(value) + } + fn from_index(&self, index: usize) -> Option { + 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) -> WithKeyPoints { + WithKeyPoints { + inner: self.into(), + bold_points: points, + light_points: vec![], + } + } +} + +impl 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 { + inner: R, + bold_func: Box Vec>, + light_func: Box Vec>, +} + +/// 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 Vec + 'static>( + self, + func: F, + ) -> WithKeyPointMethod { + WithKeyPointMethod { + inner: self.into(), + bold_func: Box::new(func), + light_func: Box::new(|_| Vec::new()), + } + } +} + +impl BindKeyPointMethod for T {} + +impl WithKeyPointMethod { + /// Define the light key point algorithm, by default this returns an empty set + pub fn with_light_point_func Vec + 'static>( + mut self, + func: F, + ) -> Self { + self.light_func = Box::new(func); + self + } +} + +impl Ranged for WithKeyPointMethod { + type ValueType = R::ValueType; + type FormatOption = R::FormatOption; + + fn range(&self) -> Range { + self.inner.range() + } + + fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { + self.inner.map(value, limit) + } + + fn key_points(&self, hint: Hint) -> Vec { + 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 { + self.inner.axis_pixel_range(limit) + } +} + +impl DiscreteRanged for WithKeyPointMethod { + fn size(&self) -> usize { + self.inner.size() + } + fn index_of(&self, value: &Self::ValueType) -> Option { + self.inner.index_of(value) + } + fn from_index(&self, index: usize) -> Option { + 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::>() + ); + + 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::>() + ); + + 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, 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<::CoordDescType> { + GroupBy(self.into(), value) + } +} + +impl ToGroupByRange for T where T::CoordDescType: DiscreteRanged {} + +impl DiscreteRanged for GroupBy { + fn size(&self) -> usize { + (self.0.size() + self.1 - 1) / self.1 + } + fn index_of(&self, value: &Self::ValueType) -> Option { + self.0.index_of(value).map(|idx| idx / self.1) + } + fn from_index(&self, index: usize) -> Option { + self.0.from_index(index * self.1) + } +} + +impl + ValueFormatter> ValueFormatter for GroupBy { + fn format(value: &T) -> String { + R::format(value) + } +} + +impl Ranged for GroupBy { + 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 { + self.0.range() + } + // TODO: See issue issue #88 + fn key_points(&self, hint: HintType) -> Vec { + 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 { + /// 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; +} + +/// 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(PhantomData); + +impl LinspaceRoundingMethod for Exact { + fn search(values: &[V], target: &V) -> Option { + 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(PhantomData); + +impl LinspaceRoundingMethod for Ceil { + fn search(values: &[V], target: &V) -> Option { + 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(PhantomData); + +impl LinspaceRoundingMethod for Floor { + fn search(values: &[V], target: &V) -> Option { + 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(PhantomData<(V, S)>); + +impl LinspaceRoundingMethod for Round +where + V: Add + PartialOrd + Sub + Clone, + S: PartialOrd + Clone, +{ + fn search(values: &[V], target: &V) -> Option { + 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> +where + T::ValueType: Add + PartialOrd + Clone, +{ + step: S, + inner: T, + grid_value: Vec, + _phatom: PhantomData, +} + +impl> Linspace +where + T::ValueType: Add + 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> { + 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> { + 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> + where + T::ValueType: Sub, + 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> + where + T::ValueType: Sub, + S: PartialOrd, + { + Linspace { + step: self.step, + inner: self.inner, + grid_value: self.grid_value, + _phatom: PhantomData, + } + } +} + +impl ValueFormatter for Linspace +where + R: Ranged + ValueFormatter, + RM: LinspaceRoundingMethod, + T: Add + PartialOrd + Clone, + S: Clone, +{ + fn format(value: &T) -> String { + R::format(value) + } +} + +impl> Ranged for Linspace +where + T::ValueType: Add + PartialOrd + Clone, +{ + type FormatOption = NoDefaultFormatting; + type ValueType = T::ValueType; + + fn range(&self) -> Range { + self.inner.range() + } + + fn map(&self, value: &T::ValueType, limit: (i32, i32)) -> i32 { + self.inner.map(value, limit) + } + + fn key_points(&self, hint: Hint) -> Vec { + 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> DiscreteRanged + for Linspace +where + T::ValueType: Add + PartialOrd + Clone, +{ + fn size(&self) -> usize { + self.grid_value.len() + } + + fn index_of(&self, value: &T::ValueType) -> Option { + R::search(self.grid_value.as_ref(), value) + } + + fn from_index(&self, idx: usize) -> Option { + 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(self, val: S) -> Linspace> + where + Self::Value: Add + PartialOrd + Clone, + { + let mut ret = Linspace { + step: val, + inner: self.into(), + grid_value: vec![], + _phatom: PhantomData, + }; + + ret.compute_grid_values(); + + ret + } +} + +impl 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; +} + +impl IntoLogRange for Range { + type ValueType = T; + fn log_scale(self) -> LogRangeExt { + 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 { + range: Range, + zero: f64, + base: f64, +} + +impl LogRangeExt { + /// 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 From> for LogCoord { + fn from(spec: LogRangeExt) -> LogCoord { + 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 AsRangedCoord for LogRangeExt { + type CoordDescType = LogCoord; + type Value = V; +} + +/// A log scaled coordinate axis +pub struct LogCoord { + linear: RangedCoordf64, + logic: Range, + normalized: Range, + base: f64, + zero_point: f64, + negative: bool, + marker: PhantomData, +} + +impl LogCoord { + 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 Ranged for LogCoord { + 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(&self, hint: Hint) -> Vec { + 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 { + 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(pub Range); + +#[allow(deprecated)] +impl AsRangedCoord for LogRange { + type CoordDescType = LogCoord; + type Value = V; +} + +#[allow(deprecated)] +impl From> for LogCoord { + fn from(range: LogRange) -> LogCoord { + range.0.log_scale().into() + } +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn regression_test_issue_143() { + let range: LogCoord = (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 { + /// Category value + Category(C), + /// One exact nested value + Value(C, V), +} + +impl NestedValue { + /// 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 From<(C, V)> for NestedValue { + fn from((cat, val): (C, V)) -> NestedValue { + NestedValue::Value(cat, val) + } +} + +impl From for NestedValue { + fn from(cat: C) -> NestedValue { + 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: Primary, + secondary: Vec, +} + +impl ValueFormatter> for NestedRange +where + P: Ranged + DiscreteRanged, + S: Ranged, + P: ValueFormatter, + S: ValueFormatter, +{ + fn format(value: &NestedValue) -> String { + match value { + NestedValue::Category(cat) => P::format(cat), + NestedValue::Value(_, val) => S::format(val), + } + } +} + +impl Ranged for NestedRange { + type FormatOption = NoDefaultFormatting; + type ValueType = NestedValue; + + fn range(&self) -> Range { + 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(&self, hint: Hint) -> Vec { + 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 DiscreteRanged for NestedRange { + fn size(&self) -> usize { + self.secondary.iter().map(|x| x.size()).sum::() + } + + fn index_of(&self, value: &Self::ValueType) -> Option { + 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::(), + ) + } + + fn from_index(&self, mut index: usize) -> Option { + 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( + self, + builder: impl Fn(::ValueType) -> S, + ) -> NestedRange { + let primary: Self::CoordDescType = self.into(); + assert!(primary.size() > 0); + + let secondary = primary + .values() + .map(|value| builder(value).into()) + .collect(); + + NestedRange { primary, secondary } + } +} + +impl 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, Range); + +/// 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<::ValueType>, + ) -> PartialAxis { + PartialAxis(self.into(), axis_range) + } +} + +impl IntoPartialAxis for R {} + +impl Ranged for PartialAxis +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(&self, hint: Hint) -> Vec { + self.0.key_points(hint) + } + + fn range(&self) -> Range { + self.0.range() + } + + fn axis_pixel_range(&self, limit: (i32, i32)) -> Range { + let left = self.map(&self.1.start, limit); + let right = self.map(&self.1.end, limit); + + left.min(right)..left.max(right) + } +} + +impl DiscreteRanged for PartialAxis +where + R: Ranged, + ::ValueType: Eq + Clone, +{ + fn size(&self) -> usize { + self.0.size() + } + + fn index_of(&self, value: &R::ValueType) -> Option { + self.0.index_of(value) + } + + fn from_index(&self, index: usize) -> Option { + 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( + axis_range: Range, + part: Range, +) -> Option as AsRangedCoord>::CoordDescType>> +where + Range: 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 = num_traits::cast(full_left)?..num_traits::cast(full_right)?; + + let axis_range: 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; + + /// 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; + + /// 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 { + 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 { + 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); + +/// 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 { + SegmentedCoord(self.into()) + } +} + +impl IntoSegmentedCoord for R where R::CoordDescType: DiscreteRanged {} + +/// The value that used by the segmented coordinate. +#[derive(Clone, Debug)] +pub enum SegmentValue { + /// 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> ValueFormatter> + for SegmentedCoord +where + D: ValueFormatter, +{ + fn format(value: &SegmentValue) -> String { + match value { + SegmentValue::Exact(ref value) => D::format(value), + SegmentValue::CenterOf(ref value) => D::format(value), + _ => "".to_string(), + } + } +} + +impl Ranged for SegmentedCoord { + type FormatOption = NoDefaultFormatting; + type ValueType = SegmentValue; + + 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(&self, hint: HintType) -> Vec { + self.0 + .key_points(hint) + .into_iter() + .map(SegmentValue::CenterOf) + .collect() + } + + fn range(&self) -> Range { + let range = self.0.range(); + SegmentValue::Exact(range.start)..SegmentValue::Exact(range.end) + } +} + +impl DiscreteRanged for SegmentedCoord { + fn size(&self) -> usize { + self.0.size() + 1 + } + + fn index_of(&self, value: &Self::ValueType) -> Option { + 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 { + 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 From for SegmentValue { + fn from(this: T) -> SegmentValue { + SegmentValue::Exact(this) + } +} + +impl ReversibleRanged for DC { + fn unmap(&self, input: i32, limit: (i32, i32)) -> Option { + 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 { + 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(&self, hint: Hint) -> Vec { + if hint.max_num_points() < 3 { + vec![] + } else { + vec![0.0, 0.5, 1.0] + } + } + + fn range(&self) -> Range { + 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 { + /// 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> ValueFormatter 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(&self, hint: Hint) -> Vec; + + /// Get the range of this value + fn range(&self) -> Range; + + /// This function provides the on-axis part of its range + #[allow(clippy::range_plus_one)] + fn axis_pixel_range(&self, limit: (i32, i32)) -> Range { + 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; +} + +/// 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 + From; + /// Type for values in the given coordinate system + type Value; +} + +impl 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 TimeValue for Date { + type DateType = Date; + fn date_floor(&self) -> Date { + self.clone() + } + fn date_ceil(&self) -> Date { + self.clone() + } + fn earliest_after_date(date: Date) -> Self { + date + } + fn subtract(&self, other: &Date) -> 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 TimeValue for DateTime { + type DateType = Date; + fn date_floor(&self) -> Date { + self.date() + } + fn date_ceil(&self) -> Date { + if self.time().num_seconds_from_midnight() > 0 { + self.date() + Duration::days(1) + } else { + self.date() + } + } + fn earliest_after_date(date: Date) -> DateTime { + date.and_hms(0, 0, 0) + } + + fn subtract(&self, other: &DateTime) -> 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, D); + +impl From> for RangedDate { + fn from(range: Range) -> Self { + Self(range.start, range.end) + } +} + +impl Ranged for RangedDate +where + D: Datelike + TimeValue + Sub + Add + Clone, +{ + type FormatOption = DefaultFormatting; + type ValueType = D; + + fn range(&self) -> Range { + 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(&self, hint: HintType) -> Vec { + 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 DiscreteRanged for RangedDate +where + D: Datelike + TimeValue + Sub + Add + 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 { + 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 { + Some(self.0.clone() + Duration::days(index as i64)) + } +} + +impl AsRangedCoord for Range> { + type CoordDescType = RangedDate>; + type Value = Date; +} + +impl AsRangedCoord for Range { + type CoordDescType = RangedDate; + 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(Range); + +impl ValueFormatter for Monthly { + fn format(value: &T) -> String { + format!("{}-{}", value.year(), value.month()) + } +} + +impl Monthly { + fn bold_key_points(&self, hint: &H) -> Vec { + 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( + mut start_year: i32, + mut start_month: i32, + end_year: i32, + end_month: i32, + step: u32, + builder: &T, + ) -> Vec { + 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 Ranged for Monthly +where + Range: AsRangedCoord, +{ + type FormatOption = NoDefaultFormatting; + type ValueType = T; + + fn range(&self) -> Range { + 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(&self, hint: HintType) -> Vec { + if hint.weight().allow_light_points() && self.size() <= hint.bold_points() * 2 { + let coord: as AsRangedCoord>::CoordDescType = self.0.clone().into(); + let normal = coord.key_points(hint.max_num_points()); + return normal; + } + self.bold_key_points(&hint) + } +} + +impl DiscreteRanged for Monthly +where + Range: AsRangedCoord, +{ + 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 { + 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 { + 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(Range); + +fn generate_yearly_keypoints( + max_points: usize, + mut start_year: i32, + start_month: u32, + mut end_year: i32, + end_month: u32, + builder: &T, +) -> Vec { + 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 ValueFormatter for Yearly { + fn format(value: &T) -> String { + format!("{}-{}", value.year(), value.month()) + } +} + +impl Ranged for Yearly +where + Range: AsRangedCoord, +{ + type FormatOption = NoDefaultFormatting; + type ValueType = T; + + fn range(&self) -> Range { + 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(&self, hint: HintType) -> Vec { + 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 DiscreteRanged for Yearly +where + Range: AsRangedCoord, +{ + 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 { + 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 { + 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 { + /// Converts a normal date coord into a monthly one + fn monthly(self) -> Monthly; +} + +/// The trait that converts a normal date coord into a yearly one +pub trait IntoYearly { + /// Converts a normal date coord into a yearly one + fn yearly(self) -> Yearly; +} + +impl IntoMonthly for Range { + fn monthly(self) -> Monthly { + Monthly(self) + } +} + +impl IntoYearly for Range { + fn yearly(self) -> Yearly { + Yearly(self) + } +} + +/// The ranged coordinate for the date and time +#[derive(Clone)] +pub struct RangedDateTime(DT, DT); + +impl AsRangedCoord for Range> { + type CoordDescType = RangedDateTime>; + type Value = DateTime; +} + +impl From>> for RangedDateTime> { + fn from(range: Range>) -> Self { + Self(range.start, range.end) + } +} + +impl From> for RangedDateTime { + fn from(range: Range) -> Self { + Self(range.start, range.end) + } +} + +impl
Ranged for RangedDateTime
+where + DT: Datelike + Timelike + TimeValue + Clone + PartialOrd, + DT: Add, + DT: Sub, + RangedDate: Ranged, +{ + type FormatOption = DefaultFormatting; + type ValueType = DT; + + fn range(&self) -> Range
{ + 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(&self, hint: HintType) -> Vec { + 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 { + type CoordDescType = RangedDuration; + type Value = Duration; +} + +impl From> for RangedDuration { + fn from(range: Range) -> Self { + Self(range.start, range.end) + } +} + +impl Ranged for RangedDuration { + type FormatOption = DefaultFormatting; + type ValueType = Duration; + + fn range(&self) -> Range { + 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(&self, hint: HintType) -> Vec { + 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 { + 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::>::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::>::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> = (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 { + if value < &self.0 { + return None; + } + let ret = value - self.0; + Some(ret as usize) + } + + fn from_index(&self, index: usize) -> Option { + 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> 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(&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 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 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::::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(&self, hint: Hint) -> Vec { + 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 { + 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 = my_slice[..].into(); + + assert_eq!(slice_range.range(), &1..&-2); + assert_eq!( + slice_range.key_points(6), + my_slice.iter().collect::>() + ); + 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 = 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)); + } +} -- cgit v1.2.3