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/**
20Presents data in a histogram. Input data can be raw or aggregated.
21
22# Examples
23
24```
25use plotters::prelude::*;
26let data = [1, 1, 2, 2, 1, 3, 3, 2, 2, 1, 1, 2, 2, 2, 3, 3, 1, 2, 3];
27let drawing_area = SVGBackend::new("histogram_vertical.svg", (300, 200)).into_drawing_area();
28drawing_area.fill(&WHITE).unwrap();
29let mut chart_builder = ChartBuilder::on(&drawing_area);
30chart_builder.margin(5).set_left_and_bottom_label_area_size(20);
31let mut chart_context = chart_builder.build_cartesian_2d((1..3).into_segmented(), 0..9).unwrap();
32chart_context.configure_mesh().draw().unwrap();
33chart_context.draw_series(Histogram::vertical(&chart_context).style(BLUE.filled()).margin(10)
34 .data(data.map(|x| (x, 1)))).unwrap();
35```
36
37The 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
41Here 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
46The spacing between histogram bars is adjusted with [`Histogram::margin()`].
47Here is a version of the figure where `.margin(10)` has been replaced by `.margin(20)`;
48the 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
53are centered on each data value. Here is another variation with `(1..3).into_segmented()`
54replaced 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
62The 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()})`.
64The 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)`
69to 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
73The 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})`
75to the right of `.margin(10)`. The lower portion of the bars are removed; the removed portion is taller
76to the right:
77
78![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@a617d37/apidoc/histogram_baseline_func.svg)
79*/
80pub struct Histogram<'a, BR, A, Tag = Vertical>
81where
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
94impl<'a, BR, A, Tag> Histogram<'a, BR, A, Tag>
95where
96 BR: DiscreteRanged + Clone,
97 A: AddAssign<A> + Default + 'a,
98 Tag: HistogramType,
99{
100 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 */
115 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 */
128 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 */
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 */
156 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 */
166 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 */
176 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
191impl<'a, BR, A> Histogram<'a, BR, A, Vertical>
192where
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 */
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
213impl<'a, BR, A> Histogram<'a, BR, A, Horizontal>
214where
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 */
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
234impl<'a, BR, A> Iterator for Histogram<'a, BR, A, Vertical>
235where
236 BR: DiscreteRanged,
237 A: AddAssign<A> + Default,
238{
239 type Item = Rectangle<(BR::ValueType, A)>;
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
258impl<'a, BR, A> Iterator for Histogram<'a, BR, A, Horizontal>
259where
260 BR: DiscreteRanged,
261 A: AddAssign<A> + Default,
262{
263 type Item = Rectangle<(A, BR::ValueType)>;
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