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 |

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> { |

47 | fn size(&self) -> usize { |

48 | (self.0.size() + self.1 - 1) / self.1 |

49 | } |

50 | fn index_of(&self, value: &Self::ValueType) -> Option<usize> { |

51 | self.0.index_of(value).map(|idx| idx / self.1) |

52 | } |

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> { |

59 | 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; |

67 | fn map(&self, value: &T::ValueType, limit: (i32, i32)) -> i32 { |

68 | self.0.map(value, limit) |

69 | } |

70 | fn range(&self) -> Range<T::ValueType> { |

71 | self.0.range() |

72 | } |

73 | // TODO: See issue issue #88 |

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] |

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 |