1 use std::collections::{hash_map::IntoIter as HashMapIter, HashMap};
2 use std::marker::PhantomData;
3 use std::ops::AddAssign;
4 
5 use crate::chart::ChartContext;
6 use crate::coord::cartesian::Cartesian2d;
7 use crate::coord::ranged1d::{DiscreteRanged, Ranged};
8 use crate::element::Rectangle;
9 use crate::style::{Color, ShapeStyle, GREEN};
10 use plotters_backend::DrawingBackend;
11 
12 pub trait HistogramType {}
13 pub struct Vertical;
14 pub struct Horizontal;
15 
16 impl HistogramType for Vertical {}
17 impl HistogramType for Horizontal {}
18 
19 /**
20 Presents data in a histogram. Input data can be raw or aggregated.
21 
22 # Examples
23 
24 ```
25 use plotters::prelude::*;
26 let data = [1, 1, 2, 2, 1, 3, 3, 2, 2, 1, 1, 2, 2, 2, 3, 3, 1, 2, 3];
27 let drawing_area = SVGBackend::new("histogram_vertical.svg", (300, 200)).into_drawing_area();
28 drawing_area.fill(&WHITE).unwrap();
29 let mut chart_builder = ChartBuilder::on(&drawing_area);
30 chart_builder.margin(5).set_left_and_bottom_label_area_size(20);
31 let mut chart_context = chart_builder.build_cartesian_2d((1..3).into_segmented(), 0..9).unwrap();
32 chart_context.configure_mesh().draw().unwrap();
33 chart_context.draw_series(Histogram::vertical(&chart_context).style(BLUE.filled()).margin(10)
34     .data(data.map(|x| (x, 1)))).unwrap();
35 ```
36 
37 The result is a histogram counting the occurrences of 1, 2, and 3 in `data`:
38 
39 ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@a617d37/apidoc/histogram_vertical.svg)
40 
41 Here is a variation with [`Histogram::horizontal()`], replacing `(1..3).into_segmented(), 0..9` with
42 `0..9, (1..3).into_segmented()`:
43 
44 ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@a617d37/apidoc/histogram_horizontal.svg)
45 
46 The spacing between histogram bars is adjusted with [`Histogram::margin()`].
47 Here is a version of the figure where `.margin(10)` has been replaced by `.margin(20)`;
48 the resulting bars are narrow and more spaced:
49 
50 ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@a617d37/apidoc/histogram_margin20.svg)
51 
52 [`crate::coord::ranged1d::IntoSegmentedCoord::into_segmented()`] is useful for discrete data; it makes sure the histogram bars
53 are centered on each data value. Here is another variation with `(1..3).into_segmented()`
54 replaced by `1..4`:
55 
56 ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@a617d37/apidoc/histogram_not_segmented.svg)
57 
58 [`Histogram::style()`] sets the style of the bars. Here is a histogram without `.filled()`:
59 
60 ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@a617d37/apidoc/histogram_hollow.svg)
61 
62 The following version uses [`Histogram::style_func()`] for finer control. Let's replace `.style(BLUE.filled())` with
63 `.style_func(|x, _bar_height| if let SegmentValue::Exact(v) = x {[BLACK, RED, GREEN, BLUE][*v as usize].filled()} else {BLACK.filled()})`.
64 The resulting bars come in different colors:
65 
66 ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@a617d37/apidoc/histogram_style_func.svg)
67 
68 [`Histogram::baseline()`] adjusts the base of the bars. The following figure adds `.baseline(1)`
69 to the right of `.margin(10)`. The lower portion of the bars are removed:
70 
71 ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@a617d37/apidoc/histogram_baseline.svg)
72 
73 The following figure uses [`Histogram::baseline_func()`] for finer control. Let's add
74 `.baseline_func(|x| if let SegmentValue::Exact(v) = x {*v as i32} else {0})`
75 to the right of `.margin(10)`. The lower portion of the bars are removed; the removed portion is taller
76 to the right:
77 
78 ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@a617d37/apidoc/histogram_baseline_func.svg)
79 */
80 pub struct Histogram<'a, BR, A, Tag = Vertical>
81 where
82     BR: DiscreteRanged,
83     A: AddAssign<A> + Default,
84     Tag: HistogramType,
85 {
86     style: Box<dyn Fn(&BR::ValueType, &A) -> ShapeStyle + 'a>,
87     margin: u32,
88     iter: HashMapIter<usize, A>,
89     baseline: Box<dyn Fn(&BR::ValueType) -> A + 'a>,
90     br: BR,
91     _p: PhantomData<Tag>,
92 }
93 
94 impl<'a, BR, A, Tag> Histogram<'a, BR, A, Tag>
95 where
96     BR: DiscreteRanged + Clone,
97     A: AddAssign<A> + Default + 'a,
98     Tag: HistogramType,
99 {
empty(br: &BR) -> Self100     fn empty(br: &BR) -> Self {
101         Self {
102             style: Box::new(|_, _| GREEN.filled()),
103             margin: 5,
104             iter: HashMap::new().into_iter(),
105             baseline: Box::new(|_| A::default()),
106             br: br.clone(),
107             _p: PhantomData,
108         }
109     }
110     /**
111     Sets the style of the histogram bars.
112 
113     See [`Histogram`] for more information and examples.
114     */
style<S: Into<ShapeStyle>>(mut self, style: S) -> Self115     pub fn style<S: Into<ShapeStyle>>(mut self, style: S) -> Self {
116         let style = style.into();
117         self.style = Box::new(move |_, _| style);
118         self
119     }
120 
121     /**
122     Sets the style of histogram using a closure.
123 
124     The closure takes the position of the bar in guest coordinates as argument.
125     The argument may need some processing if the data range has been transformed by
126     [`crate::coord::ranged1d::IntoSegmentedCoord::into_segmented()`] as shown in the [`Histogram`] example.
127     */
style_func( mut self, style_func: impl Fn(&BR::ValueType, &A) -> ShapeStyle + 'a, ) -> Self128     pub fn style_func(
129         mut self,
130         style_func: impl Fn(&BR::ValueType, &A) -> ShapeStyle + 'a,
131     ) -> Self {
132         self.style = Box::new(style_func);
133         self
134     }
135 
136     /**
137     Sets the baseline of the histogram.
138 
139     See [`Histogram`] for more information and examples.
140     */
baseline(mut self, baseline: A) -> Self where A: Clone,141     pub fn baseline(mut self, baseline: A) -> Self
142     where
143         A: Clone,
144     {
145         self.baseline = Box::new(move |_| baseline.clone());
146         self
147     }
148 
149     /**
150     Sets the histogram bar baselines using a closure.
151 
152     The closure takes the bar position and height as argument.
153     The argument may need some processing if the data range has been transformed by
154     [`crate::coord::ranged1d::IntoSegmentedCoord::into_segmented()`] as shown in the [`Histogram`] example.
155     */
baseline_func(mut self, func: impl Fn(&BR::ValueType) -> A + 'a) -> Self156     pub fn baseline_func(mut self, func: impl Fn(&BR::ValueType) -> A + 'a) -> Self {
157         self.baseline = Box::new(func);
158         self
159     }
160 
161     /**
162     Sets the margin for each bar, in backend pixels.
163 
164     See [`Histogram`] for more information and examples.
165     */
margin(mut self, value: u32) -> Self166     pub fn margin(mut self, value: u32) -> Self {
167         self.margin = value;
168         self
169     }
170 
171     /**
172     Specifies the input data for the histogram through an appropriate data iterator.
173 
174     See [`Histogram`] for more information and examples.
175     */
data<TB: Into<BR::ValueType>, I: IntoIterator<Item = (TB, A)>>( mut self, iter: I, ) -> Self176     pub fn data<TB: Into<BR::ValueType>, I: IntoIterator<Item = (TB, A)>>(
177         mut self,
178         iter: I,
179     ) -> Self {
180         let mut buffer = HashMap::<usize, A>::new();
181         for (x, y) in iter.into_iter() {
182             if let Some(x) = self.br.index_of(&x.into()) {
183                 *buffer.entry(x).or_insert_with(Default::default) += y;
184             }
185         }
186         self.iter = buffer.into_iter();
187         self
188     }
189 }
190 
191 impl<'a, BR, A> Histogram<'a, BR, A, Vertical>
192 where
193     BR: DiscreteRanged + Clone,
194     A: AddAssign<A> + Default + 'a,
195 {
196     /**
197     Creates a vertical histogram.
198 
199     See [`Histogram`] for more information and examples.
200     */
vertical<ACoord, DB: DrawingBackend + 'a>( parent: &ChartContext<DB, Cartesian2d<BR, ACoord>>, ) -> Self where ACoord: Ranged<ValueType = A>,201     pub fn vertical<ACoord, DB: DrawingBackend + 'a>(
202         parent: &ChartContext<DB, Cartesian2d<BR, ACoord>>,
203     ) -> Self
204     where
205         ACoord: Ranged<ValueType = A>,
206     {
207         let dp = parent.as_coord_spec().x_spec();
208 
209         Self::empty(dp)
210     }
211 }
212 
213 impl<'a, BR, A> Histogram<'a, BR, A, Horizontal>
214 where
215     BR: DiscreteRanged + Clone,
216     A: AddAssign<A> + Default + 'a,
217 {
218     /**
219     Creates a horizontal histogram.
220 
221     See [`Histogram`] for more information and examples.
222     */
horizontal<ACoord, DB: DrawingBackend>( parent: &ChartContext<DB, Cartesian2d<ACoord, BR>>, ) -> Self where ACoord: Ranged<ValueType = A>,223     pub fn horizontal<ACoord, DB: DrawingBackend>(
224         parent: &ChartContext<DB, Cartesian2d<ACoord, BR>>,
225     ) -> Self
226     where
227         ACoord: Ranged<ValueType = A>,
228     {
229         let dp = parent.as_coord_spec().y_spec();
230         Self::empty(dp)
231     }
232 }
233 
234 impl<'a, BR, A> Iterator for Histogram<'a, BR, A, Vertical>
235 where
236     BR: DiscreteRanged,
237     A: AddAssign<A> + Default,
238 {
239     type Item = Rectangle<(BR::ValueType, A)>;
next(&mut self) -> Option<Self::Item>240     fn next(&mut self) -> Option<Self::Item> {
241         while let Some((x, y)) = self.iter.next() {
242             if let Some((x, Some(nx))) = self
243                 .br
244                 .from_index(x)
245                 .map(|v| (v, self.br.from_index(x + 1)))
246             {
247                 let base = (self.baseline)(&x);
248                 let style = (self.style)(&x, &y);
249                 let mut rect = Rectangle::new([(x, y), (nx, base)], style);
250                 rect.set_margin(0, 0, self.margin, self.margin);
251                 return Some(rect);
252             }
253         }
254         None
255     }
256 }
257 
258 impl<'a, BR, A> Iterator for Histogram<'a, BR, A, Horizontal>
259 where
260     BR: DiscreteRanged,
261     A: AddAssign<A> + Default,
262 {
263     type Item = Rectangle<(A, BR::ValueType)>;
next(&mut self) -> Option<Self::Item>264     fn next(&mut self) -> Option<Self::Item> {
265         while let Some((y, x)) = self.iter.next() {
266             if let Some((y, Some(ny))) = self
267                 .br
268                 .from_index(y)
269                 .map(|v| (v, self.br.from_index(y + 1)))
270             {
271                 let base = (self.baseline)(&y);
272                 let style = (self.style)(&y, &x);
273                 let mut rect = Rectangle::new([(x, y), (base, ny)], style);
274                 rect.set_margin(self.margin, self.margin, 0, 0);
275                 return Some(rect);
276             }
277         }
278         None
279     }
280 }
281