diff options
Diffstat (limited to 'src/coord/ranged1d/combinators/group_by.rs')
-rw-r--r-- | src/coord/ranged1d/combinators/group_by.rs | 120 |
1 files changed, 120 insertions, 0 deletions
diff --git a/src/coord/ranged1d/combinators/group_by.rs b/src/coord/ranged1d/combinators/group_by.rs new file mode 100644 index 0000000..0b4b508 --- /dev/null +++ b/src/coord/ranged1d/combinators/group_by.rs @@ -0,0 +1,120 @@ +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_ranged((0..100).group_by(7), 0..100) +/// .unwrap(); +///``` +/// +/// To apply this combinator, call [ToGroupByRange::group_by](trait.ToGroupByRange.html#tymethod.group_by) method on any discrete coordinate spec. +#[derive(Clone)] +pub struct GroupBy<T: DiscreteRanged>(T, usize); + +/// The trait that provides method `Self::group_by` function which creates a +/// `GroupBy` decorated ranged value. +pub trait ToGroupByRange: AsRangedCoord + Sized +where + Self::CoordDescType: DiscreteRanged, +{ + /// Make a grouping ranged value, see the documentation for `GroupBy` for details. + /// + /// - `value`: The number of values we want to group it + /// - **return**: The newly created grouping range specification + fn group_by(self, value: usize) -> GroupBy<<Self as AsRangedCoord>::CoordDescType> { + GroupBy(self.into(), value) + } +} + +impl<T: AsRangedCoord + Sized> ToGroupByRange for T where T::CoordDescType: DiscreteRanged {} + +impl<T: DiscreteRanged> DiscreteRanged for GroupBy<T> { + fn size(&self) -> usize { + (self.0.size() + self.1 - 1) / self.1 + } + fn index_of(&self, value: &Self::ValueType) -> Option<usize> { + self.0.index_of(value).map(|idx| idx / self.1) + } + fn from_index(&self, index: usize) -> Option<Self::ValueType> { + self.0.from_index(index * self.1) + } +} + +impl<T, R: DiscreteRanged<ValueType = T> + ValueFormatter<T>> ValueFormatter<T> for GroupBy<R> { + fn format(value: &T) -> String { + R::format(value) + } +} + +impl<T: DiscreteRanged> Ranged for GroupBy<T> { + type FormatOption = NoDefaultFormatting; + type ValueType = T::ValueType; + fn map(&self, value: &T::ValueType, limit: (i32, i32)) -> i32 { + self.0.map(value, limit) + } + fn range(&self) -> Range<T::ValueType> { + self.0.range() + } + // TODO: See issue issue #88 + fn key_points<HintType: KeyPointHint>(&self, hint: HintType) -> Vec<T::ValueType> { + let range = 0..(self.0.size() + self.1) / self.1; + //let logic_range: RangedCoordusize = range.into(); + + let interval = + ((range.end - range.start + hint.bold_points() - 1) / hint.bold_points()).max(1); + let count = (range.end - range.start) / interval; + + let idx_iter = (0..hint.bold_points()).map(|x| x * interval); + + if hint.weight().allow_light_points() && count < hint.bold_points() * 2 { + let outter_ticks = idx_iter; + let outter_tick_size = interval * self.1; + let inner_ticks_per_group = hint.max_num_points() / outter_ticks.len(); + let inner_ticks = + (outter_tick_size + inner_ticks_per_group - 1) / inner_ticks_per_group; + let inner_ticks: Vec<_> = (0..(outter_tick_size / inner_ticks)) + .map(move |x| x * inner_ticks) + .collect(); + let size = self.0.size(); + return outter_ticks + .map(|base| inner_ticks.iter().map(move |&ofs| base * self.1 + ofs)) + .flatten() + .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)); + } + } +} |