plotters_unstable/series/
histogram.rs

1use std::collections::{hash_map::IntoIter as HashMapIter, HashMap};
2use std::marker::PhantomData;
3use std::ops::AddAssign;
4
5use crate::chart::ChartContext;
6use crate::coord::cartesian::Cartesian2d;
7use crate::coord::ranged1d::{DiscreteRanged, Ranged};
8use crate::element::Rectangle;
9use crate::style::{Color, ShapeStyle, GREEN};
10use plotters_backend::DrawingBackend;
11
12pub trait HistogramType {}
13pub struct Vertical;
14pub struct Horizontal;
15
16impl HistogramType for Vertical {}
17impl HistogramType for Horizontal {}
18
19/// The series that aggregate data into a histogram
20pub struct Histogram<'a, BR, A, Tag = Vertical>
21where
22    BR: DiscreteRanged,
23    A: AddAssign<A> + Default,
24    Tag: HistogramType,
25{
26    style: Box<dyn Fn(&BR::ValueType, &A) -> ShapeStyle + 'a>,
27    margin: u32,
28    iter: HashMapIter<usize, A>,
29    baseline: Box<dyn Fn(&BR::ValueType) -> A + 'a>,
30    br: BR,
31    _p: PhantomData<Tag>,
32}
33
34impl<'a, BR, A, Tag> Histogram<'a, BR, A, Tag>
35where
36    BR: DiscreteRanged + Clone,
37    A: AddAssign<A> + Default + 'a,
38    Tag: HistogramType,
39{
40    fn empty(br: &BR) -> Self {
41        Self {
42            style: Box::new(|_, _| GREEN.filled()),
43            margin: 5,
44            iter: HashMap::new().into_iter(),
45            baseline: Box::new(|_| A::default()),
46            br: br.clone(),
47            _p: PhantomData,
48        }
49    }
50    /// Set the style of the histogram
51    pub fn style<S: Into<ShapeStyle>>(mut self, style: S) -> Self {
52        let style = style.into();
53        self.style = Box::new(move |_, _| style.clone());
54        self
55    }
56
57    /// Set the style of histogram using a lambda function
58    pub fn style_func(
59        mut self,
60        style_func: impl Fn(&BR::ValueType, &A) -> ShapeStyle + 'a,
61    ) -> Self {
62        self.style = Box::new(style_func);
63        self
64    }
65
66    /// Set the baseline of the histogram
67    pub fn baseline(mut self, baseline: A) -> Self
68    where
69        A: Clone,
70    {
71        self.baseline = Box::new(move |_| baseline.clone());
72        self
73    }
74
75    /// Set a function that defines variant baseline
76    pub fn baseline_func(mut self, func: impl Fn(&BR::ValueType) -> A + 'a) -> Self {
77        self.baseline = Box::new(func);
78        self
79    }
80
81    /// Set the margin for each bar
82    pub fn margin(mut self, value: u32) -> Self {
83        self.margin = value;
84        self
85    }
86
87    /// Set the data iterator
88    pub fn data<TB: Into<BR::ValueType>, I: IntoIterator<Item = (TB, A)>>(
89        mut self,
90        iter: I,
91    ) -> Self {
92        let mut buffer = HashMap::<usize, A>::new();
93        for (x, y) in iter.into_iter() {
94            if let Some(x) = self.br.index_of(&x.into()) {
95                *buffer.entry(x).or_insert_with(Default::default) += y;
96            }
97        }
98        self.iter = buffer.into_iter();
99        self
100    }
101}
102
103impl<'a, BR, A> Histogram<'a, BR, A, Vertical>
104where
105    BR: DiscreteRanged + Clone,
106    A: AddAssign<A> + Default + 'a,
107{
108    pub fn vertical<ACoord, DB: DrawingBackend + 'a>(
109        parent: &ChartContext<DB, Cartesian2d<BR, ACoord>>,
110    ) -> Self
111    where
112        ACoord: Ranged<ValueType = A>,
113    {
114        let dp = parent.as_coord_spec().x_spec();
115
116        Self::empty(dp)
117    }
118}
119
120impl<'a, BR, A> Histogram<'a, BR, A, Horizontal>
121where
122    BR: DiscreteRanged + Clone,
123    A: AddAssign<A> + Default + 'a,
124{
125    pub fn horizontal<ACoord, DB: DrawingBackend>(
126        parent: &ChartContext<DB, Cartesian2d<ACoord, BR>>,
127    ) -> Self
128    where
129        ACoord: Ranged<ValueType = A>,
130    {
131        let dp = parent.as_coord_spec().y_spec();
132        Self::empty(dp)
133    }
134}
135
136impl<'a, BR, A> Iterator for Histogram<'a, BR, A, Vertical>
137where
138    BR: DiscreteRanged,
139    A: AddAssign<A> + Default,
140{
141    type Item = Rectangle<(BR::ValueType, A)>;
142    fn next(&mut self) -> Option<Self::Item> {
143        while let Some((x, y)) = self.iter.next() {
144            if let Some((x, Some(nx))) = self
145                .br
146                .from_index(x)
147                .map(|v| (v, self.br.from_index(x + 1)))
148            {
149                let base = (self.baseline)(&x);
150                let style = (self.style)(&x, &y);
151                let mut rect = Rectangle::new([(x, y), (nx, base)], style);
152                rect.set_margin(0, 0, self.margin, self.margin);
153                return Some(rect);
154            }
155        }
156        None
157    }
158}
159
160impl<'a, BR, A> Iterator for Histogram<'a, BR, A, Horizontal>
161where
162    BR: DiscreteRanged,
163    A: AddAssign<A> + Default,
164{
165    type Item = Rectangle<(A, BR::ValueType)>;
166    fn next(&mut self) -> Option<Self::Item> {
167        while let Some((y, x)) = self.iter.next() {
168            if let Some((y, Some(ny))) = self
169                .br
170                .from_index(y)
171                .map(|v| (v, self.br.from_index(y + 1)))
172            {
173                let base = (self.baseline)(&y);
174                let style = (self.style)(&y, &x);
175                let mut rect = Rectangle::new([(x, y), (base, ny)], style);
176                rect.set_margin(0, 0, self.margin, self.margin);
177                return Some(rect);
178            }
179        }
180        None
181    }
182}