diff options
Diffstat (limited to 'src/chart/series.rs')
-rw-r--r-- | src/chart/series.rs | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/src/chart/series.rs b/src/chart/series.rs new file mode 100644 index 0000000..51fe97d --- /dev/null +++ b/src/chart/series.rs @@ -0,0 +1,191 @@ +use super::ChartContext; +use crate::coord::CoordTranslate; +use crate::drawing::backend::{BackendCoord, DrawingErrorKind}; +use crate::drawing::{DrawingAreaErrorKind, DrawingBackend}; +use crate::element::{EmptyElement, IntoDynElement, MultiLineText, Rectangle}; +use crate::style::{IntoFont, IntoTextStyle, ShapeStyle, SizeDesc, TextStyle, TRANSPARENT}; + +/// Describes where we want to put the series label +pub enum SeriesLabelPosition { + UpperLeft, + MiddleLeft, + LowerLeft, + UpperMiddle, + MiddleMiddle, + LowerMiddle, + UpperRight, + MiddleRight, + LowerRight, + /// Force the series label drawn at the specific location + Coordinate(i32, i32), +} + +impl SeriesLabelPosition { + fn layout_label_area(&self, label_dim: (i32, i32), area_dim: (u32, u32)) -> (i32, i32) { + use SeriesLabelPosition::*; + ( + match self { + UpperLeft | MiddleLeft | LowerLeft => 5, + UpperMiddle | MiddleMiddle | LowerMiddle => { + (area_dim.0 as i32 - label_dim.0 as i32) / 2 + } + UpperRight | MiddleRight | LowerRight => area_dim.0 as i32 - label_dim.0 as i32 - 5, + Coordinate(x, _) => *x, + }, + match self { + UpperLeft | UpperMiddle | UpperRight => 5, + MiddleLeft | MiddleMiddle | MiddleRight => { + (area_dim.1 as i32 - label_dim.1 as i32) / 2 + } + LowerLeft | LowerMiddle | LowerRight => area_dim.1 as i32 - label_dim.1 as i32 - 5, + Coordinate(_, y) => *y, + }, + ) + } +} + +/// The struct to specify the series label of a target chart context +pub struct SeriesLabelStyle<'a, 'b, DB: DrawingBackend, CT: CoordTranslate> { + target: &'b mut ChartContext<'a, DB, CT>, + position: SeriesLabelPosition, + legend_area_size: u32, + border_style: ShapeStyle, + background: ShapeStyle, + label_font: Option<TextStyle<'b>>, + margin: u32, +} + +impl<'a, 'b, DB: DrawingBackend + 'a, CT: CoordTranslate> SeriesLabelStyle<'a, 'b, DB, CT> { + pub(super) fn new(target: &'b mut ChartContext<'a, DB, CT>) -> Self { + Self { + target, + position: SeriesLabelPosition::MiddleRight, + legend_area_size: 30, + border_style: (&TRANSPARENT).into(), + background: (&TRANSPARENT).into(), + label_font: None, + margin: 10, + } + } + + /// Set the series label positioning style + /// `pos` - The positioning style + pub fn position(&mut self, pos: SeriesLabelPosition) -> &mut Self { + self.position = pos; + self + } + + /// Set the margin of the series label drawing are + /// + /// - `value`: The size specification + pub fn margin<S: SizeDesc>(&mut self, value: S) -> &mut Self { + self.margin = value + .in_pixels(&self.target.plotting_area().dim_in_pixel()) + .max(0) as u32; + self + } + + /// Set the size of legend area + /// `size` - The size of legend area in pixel + pub fn legend_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self { + let size = size + .in_pixels(&self.target.plotting_area().dim_in_pixel()) + .max(0) as u32; + self.legend_area_size = size; + self + } + + /// Set the style of the label series area + /// `style` - The style of the border + pub fn border_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self { + self.border_style = style.into(); + self + } + + /// Set the background style + /// `style` - The style of the border + pub fn background_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self { + self.background = style.into(); + self + } + + /// Set the series label font + /// `font` - The font + pub fn label_font<F: IntoTextStyle<'b>>(&mut self, font: F) -> &mut Self { + self.label_font = Some(font.into_text_style(&self.target.plotting_area().dim_in_pixel())); + self + } + + /// Draw the series label area + pub fn draw(&mut self) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> { + let drawing_area = self.target.plotting_area().strip_coord_spec(); + + // TODO: Issue #68 Currently generic font family doesn't load on OSX, change this after the issue + // resolved + let default_font = ("sans-serif", 12).into_font(); + let default_style: TextStyle = default_font.into(); + + let font = { + let mut temp = None; + std::mem::swap(&mut self.label_font, &mut temp); + temp.unwrap_or(default_style) + }; + + let mut label_element = MultiLineText::<_, &str>::new((0, 0), &font); + let mut funcs = vec![]; + + for anno in self.target.series_anno.iter() { + let label_text = anno.get_label(); + let draw_func = anno.get_draw_func(); + + if label_text == "" && draw_func.is_none() { + continue; + } + + funcs.push( + draw_func.unwrap_or_else(|| &|p: BackendCoord| EmptyElement::at(p).into_dyn()), + ); + label_element.push_line(label_text); + } + + let (mut w, mut h) = label_element + .estimate_dimension() + .map_err(|e| DrawingAreaErrorKind::BackendError(DrawingErrorKind::FontError(e)))?; + + let margin = self.margin as i32; + + w += self.legend_area_size as i32 + margin * 2; + h += margin * 2; + + let (area_w, area_h) = drawing_area.dim_in_pixel(); + + let (label_x, label_y) = self.position.layout_label_area((w, h), (area_w, area_h)); + + label_element.relocate(( + label_x + self.legend_area_size as i32 + margin, + label_y + margin, + )); + + drawing_area.draw(&Rectangle::new( + [(label_x, label_y), (label_x + w, label_y + h)], + self.background.filled(), + ))?; + drawing_area.draw(&Rectangle::new( + [(label_x, label_y), (label_x + w, label_y + h)], + self.border_style.clone(), + ))?; + drawing_area.draw(&label_element)?; + + for (((_, y0), (_, y1)), make_elem) in label_element + .compute_line_layout() + .map_err(|e| DrawingAreaErrorKind::BackendError(DrawingErrorKind::FontError(e)))? + .into_iter() + .zip(funcs.into_iter()) + { + let legend_element = make_elem((label_x + margin, (y0 + y1) / 2)); + drawing_area.draw(&legend_element)?; + } + + Ok(()) + } +} |