| use std::collections::{hash_map::IntoIter as HashMapIter, HashMap}; |
| use std::marker::PhantomData; |
| use std::ops::AddAssign; |
| |
| use crate::chart::ChartContext; |
| use crate::coord::cartesian::Cartesian2d; |
| use crate::coord::ranged1d::{DiscreteRanged, Ranged}; |
| use crate::element::Rectangle; |
| use crate::style::{Color, ShapeStyle, GREEN}; |
| use plotters_backend::DrawingBackend; |
| |
| pub trait HistogramType {} |
| pub struct Vertical; |
| pub struct Horizontal; |
| |
| impl HistogramType for Vertical {} |
| impl HistogramType for Horizontal {} |
| |
| /// The series that aggregate data into a histogram |
| pub struct Histogram<'a, BR, A, Tag = Vertical> |
| where |
| BR: DiscreteRanged, |
| A: AddAssign<A> + Default, |
| Tag: HistogramType, |
| { |
| style: Box<dyn Fn(&BR::ValueType, &A) -> ShapeStyle + 'a>, |
| margin: u32, |
| iter: HashMapIter<usize, A>, |
| baseline: Box<dyn Fn(&BR::ValueType) -> A + 'a>, |
| br: BR, |
| _p: PhantomData<Tag>, |
| } |
| |
| impl<'a, BR, A, Tag> Histogram<'a, BR, A, Tag> |
| where |
| BR: DiscreteRanged + Clone, |
| A: AddAssign<A> + Default + 'a, |
| Tag: HistogramType, |
| { |
| fn empty(br: &BR) -> Self { |
| Self { |
| style: Box::new(|_, _| GREEN.filled()), |
| margin: 5, |
| iter: HashMap::new().into_iter(), |
| baseline: Box::new(|_| A::default()), |
| br: br.clone(), |
| _p: PhantomData, |
| } |
| } |
| /// Set the style of the histogram |
| pub fn style<S: Into<ShapeStyle>>(mut self, style: S) -> Self { |
| let style = style.into(); |
| self.style = Box::new(move |_, _| style.clone()); |
| self |
| } |
| |
| /// Set the style of histogram using a lambda function |
| pub fn style_func( |
| mut self, |
| style_func: impl Fn(&BR::ValueType, &A) -> ShapeStyle + 'a, |
| ) -> Self { |
| self.style = Box::new(style_func); |
| self |
| } |
| |
| /// Set the baseline of the histogram |
| pub fn baseline(mut self, baseline: A) -> Self |
| where |
| A: Clone, |
| { |
| self.baseline = Box::new(move |_| baseline.clone()); |
| self |
| } |
| |
| /// Set a function that defines variant baseline |
| pub fn baseline_func(mut self, func: impl Fn(&BR::ValueType) -> A + 'a) -> Self { |
| self.baseline = Box::new(func); |
| self |
| } |
| |
| /// Set the margin for each bar |
| pub fn margin(mut self, value: u32) -> Self { |
| self.margin = value; |
| self |
| } |
| |
| /// Set the data iterator |
| pub fn data<TB: Into<BR::ValueType>, I: IntoIterator<Item = (TB, A)>>( |
| mut self, |
| iter: I, |
| ) -> Self { |
| let mut buffer = HashMap::<usize, A>::new(); |
| for (x, y) in iter.into_iter() { |
| if let Some(x) = self.br.index_of(&x.into()) { |
| *buffer.entry(x).or_insert_with(Default::default) += y; |
| } |
| } |
| self.iter = buffer.into_iter(); |
| self |
| } |
| } |
| |
| impl<'a, BR, A> Histogram<'a, BR, A, Vertical> |
| where |
| BR: DiscreteRanged + Clone, |
| A: AddAssign<A> + Default + 'a, |
| { |
| pub fn vertical<ACoord, DB: DrawingBackend + 'a>( |
| parent: &ChartContext<DB, Cartesian2d<BR, ACoord>>, |
| ) -> Self |
| where |
| ACoord: Ranged<ValueType = A>, |
| { |
| let dp = parent.as_coord_spec().x_spec(); |
| |
| Self::empty(dp) |
| } |
| } |
| |
| impl<'a, BR, A> Histogram<'a, BR, A, Horizontal> |
| where |
| BR: DiscreteRanged + Clone, |
| A: AddAssign<A> + Default + 'a, |
| { |
| pub fn horizontal<ACoord, DB: DrawingBackend>( |
| parent: &ChartContext<DB, Cartesian2d<ACoord, BR>>, |
| ) -> Self |
| where |
| ACoord: Ranged<ValueType = A>, |
| { |
| let dp = parent.as_coord_spec().y_spec(); |
| Self::empty(dp) |
| } |
| } |
| |
| impl<'a, BR, A> Iterator for Histogram<'a, BR, A, Vertical> |
| where |
| BR: DiscreteRanged, |
| A: AddAssign<A> + Default, |
| { |
| type Item = Rectangle<(BR::ValueType, A)>; |
| fn next(&mut self) -> Option<Self::Item> { |
| while let Some((x, y)) = self.iter.next() { |
| if let Some((x, Some(nx))) = self |
| .br |
| .from_index(x) |
| .map(|v| (v, self.br.from_index(x + 1))) |
| { |
| let base = (self.baseline)(&x); |
| let style = (self.style)(&x, &y); |
| let mut rect = Rectangle::new([(x, y), (nx, base)], style); |
| rect.set_margin(0, 0, self.margin, self.margin); |
| return Some(rect); |
| } |
| } |
| None |
| } |
| } |
| |
| impl<'a, BR, A> Iterator for Histogram<'a, BR, A, Horizontal> |
| where |
| BR: DiscreteRanged, |
| A: AddAssign<A> + Default, |
| { |
| type Item = Rectangle<(A, BR::ValueType)>; |
| fn next(&mut self) -> Option<Self::Item> { |
| while let Some((y, x)) = self.iter.next() { |
| if let Some((y, Some(ny))) = self |
| .br |
| .from_index(y) |
| .map(|v| (v, self.br.from_index(y + 1))) |
| { |
| let base = (self.baseline)(&y); |
| let style = (self.style)(&y, &x); |
| let mut rect = Rectangle::new([(x, y), (base, ny)], style); |
| rect.set_margin(0, 0, self.margin, self.margin); |
| return Some(rect); |
| } |
| } |
| None |
| } |
| } |