1 use crate::coord::ranged1d::{
2     AsRangedCoord, DiscreteRanged, KeyPointHint, NoDefaultFormatting, Ranged, ValueFormatter,
3 };
4 use std::ops::Range;
5 
6 /// Grouping the value in the coordinate specification.
7 ///
8 /// This combinator doesn't change the coordinate mapping behavior. But it changes how
9 /// the key point is generated, this coordinate specification will enforce that only the first value in each group
10 /// can be emitted as the bold key points.
11 ///
12 /// This is useful, for example, when we have an X axis is a integer and denotes days.
13 /// And we are expecting the tick mark denotes weeks, in this way we can make the range
14 /// spec grouping by 7 elements.
15 /// With the help of the GroupBy decorator, this can be archived quite easily:
16 ///```rust
17 ///use plotters::prelude::*;
18 ///let mut buf = vec![0;1024*768*3];
19 ///let area = BitMapBackend::with_buffer(buf.as_mut(), (1024, 768)).into_drawing_area();
20 ///let chart = ChartBuilder::on(&area)
21 ///    .build_cartesian_2d((0..100).group_by(7), 0..100)
22 ///    .unwrap();
23 ///```
24 ///
25 /// To apply this combinator, call [ToGroupByRange::group_by](trait.ToGroupByRange.html#tymethod.group_by) method on any discrete coordinate spec.
26 #[derive(Clone)]
27 pub struct GroupBy<T: DiscreteRanged>(T, usize);
28 
29 /// The trait that provides method `Self::group_by` function which creates a
30 /// `GroupBy` decorated ranged value.
31 pub trait ToGroupByRange: AsRangedCoord + Sized
32 where
33     Self::CoordDescType: DiscreteRanged,
34 {
35     /// Make a grouping ranged value, see the documentation for `GroupBy` for details.
36     ///
37     /// - `value`: The number of values we want to group it
38     /// - **return**: The newly created grouping range specification
group_by(self, value: usize) -> GroupBy<<Self as AsRangedCoord>::CoordDescType>39     fn group_by(self, value: usize) -> GroupBy<<Self as AsRangedCoord>::CoordDescType> {
40         GroupBy(self.into(), value)
41     }
42 }
43 
44 impl<T: AsRangedCoord + Sized> ToGroupByRange for T where T::CoordDescType: DiscreteRanged {}
45 
46 impl<T: DiscreteRanged> DiscreteRanged for GroupBy<T> {
size(&self) -> usize47     fn size(&self) -> usize {
48         (self.0.size() + self.1 - 1) / self.1
49     }
index_of(&self, value: &Self::ValueType) -> Option<usize>50     fn index_of(&self, value: &Self::ValueType) -> Option<usize> {
51         self.0.index_of(value).map(|idx| idx / self.1)
52     }
from_index(&self, index: usize) -> Option<Self::ValueType>53     fn from_index(&self, index: usize) -> Option<Self::ValueType> {
54         self.0.from_index(index * self.1)
55     }
56 }
57 
58 impl<T, R: DiscreteRanged<ValueType = T> + ValueFormatter<T>> ValueFormatter<T> for GroupBy<R> {
format(value: &T) -> String59     fn format(value: &T) -> String {
60         R::format(value)
61     }
62 }
63 
64 impl<T: DiscreteRanged> Ranged for GroupBy<T> {
65     type FormatOption = NoDefaultFormatting;
66     type ValueType = T::ValueType;
map(&self, value: &T::ValueType, limit: (i32, i32)) -> i3267     fn map(&self, value: &T::ValueType, limit: (i32, i32)) -> i32 {
68         self.0.map(value, limit)
69     }
range(&self) -> Range<T::ValueType>70     fn range(&self) -> Range<T::ValueType> {
71         self.0.range()
72     }
73     // TODO: See issue issue #88
key_points<HintType: KeyPointHint>(&self, hint: HintType) -> Vec<T::ValueType>74     fn key_points<HintType: KeyPointHint>(&self, hint: HintType) -> Vec<T::ValueType> {
75         let range = 0..(self.0.size() + self.1) / self.1;
76         //let logic_range: RangedCoordusize = range.into();
77 
78         let interval =
79             ((range.end - range.start + hint.bold_points() - 1) / hint.bold_points()).max(1);
80         let count = (range.end - range.start) / interval;
81 
82         let idx_iter = (0..hint.bold_points()).map(|x| x * interval);
83 
84         if hint.weight().allow_light_points() && count < hint.bold_points() * 2 {
85             let outter_ticks = idx_iter;
86             let outter_tick_size = interval * self.1;
87             let inner_ticks_per_group = hint.max_num_points() / outter_ticks.len();
88             let inner_ticks =
89                 (outter_tick_size + inner_ticks_per_group - 1) / inner_ticks_per_group;
90             let inner_ticks: Vec<_> = (0..(outter_tick_size / inner_ticks))
91                 .map(move |x| x * inner_ticks)
92                 .collect();
93             let size = self.0.size();
94             return outter_ticks
95                 .flat_map(|base| inner_ticks.iter().map(move |&ofs| base * self.1 + ofs))
96                 .take_while(|&idx| idx < size)
97                 .map(|x| self.0.from_index(x).unwrap())
98                 .collect();
99         }
100 
101         idx_iter
102             .map(|x| self.0.from_index(x * self.1).unwrap())
103             .collect()
104     }
105 }
106 
107 #[cfg(test)]
108 mod test {
109     use super::*;
110     #[test]
test_group_by()111     fn test_group_by() {
112         let coord = (0..100).group_by(10);
113         assert_eq!(coord.size(), 11);
114         for (idx, val) in (0..).zip(coord.values()) {
115             assert_eq!(val, idx * 10);
116             assert_eq!(coord.from_index(idx as usize), Some(val));
117         }
118     }
119 }
120