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  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  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  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  57 58 [`Histogram::style()`] sets the style of the bars. Here is a histogram without `.filled()`: 59 60  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  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  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  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