1 use super::ChartContext; 2 use crate::coord::CoordTranslate; 3 use crate::drawing::DrawingAreaErrorKind; 4 use crate::element::{DynElement, EmptyElement, IntoDynElement, MultiLineText, Rectangle}; 5 use crate::style::{IntoFont, IntoTextStyle, ShapeStyle, SizeDesc, TextStyle, TRANSPARENT}; 6 7 use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; 8 9 type SeriesAnnoDrawFn<'a, DB> = dyn Fn(BackendCoord) -> DynElement<'a, DB, BackendCoord> + 'a; 10 11 /// The annotations (such as the label of the series, the legend element, etc) 12 /// When a series is drawn onto a drawing area, an series annotation object 13 /// is created and a mutable reference is returned. 14 pub struct SeriesAnno<'a, DB: DrawingBackend> { 15 label: Option<String>, 16 draw_func: Option<Box<SeriesAnnoDrawFn<'a, DB>>>, 17 } 18 19 impl<'a, DB: DrawingBackend> SeriesAnno<'a, DB> { 20 #[allow(clippy::option_as_ref_deref)] get_label(&self) -> &str21 pub(crate) fn get_label(&self) -> &str { 22 // TODO: Change this when we bump the MSRV 23 self.label.as_ref().map(|x| x.as_str()).unwrap_or("") 24 } 25 get_draw_func(&self) -> Option<&SeriesAnnoDrawFn<'a, DB>>26 pub(crate) fn get_draw_func(&self) -> Option<&SeriesAnnoDrawFn<'a, DB>> { 27 self.draw_func.as_ref().map(|x| x.as_ref()) 28 } 29 new() -> Self30 pub(crate) fn new() -> Self { 31 Self { 32 label: None, 33 draw_func: None, 34 } 35 } 36 37 /// Set the series label 38 /// - `label`: The string would be use as label for current series label<L: Into<String>>(&mut self, label: L) -> &mut Self39 pub fn label<L: Into<String>>(&mut self, label: L) -> &mut Self { 40 self.label = Some(label.into()); 41 self 42 } 43 44 /// Set the legend element creator function 45 /// - `func`: The function use to create the element 46 /// *Note*: The creation function uses a shifted pixel-based coordinate system. And place the 47 /// point (0,0) to the mid-right point of the shape legend<E: IntoDynElement<'a, DB, BackendCoord>, T: Fn(BackendCoord) -> E + 'a>( &mut self, func: T, ) -> &mut Self48 pub fn legend<E: IntoDynElement<'a, DB, BackendCoord>, T: Fn(BackendCoord) -> E + 'a>( 49 &mut self, 50 func: T, 51 ) -> &mut Self { 52 self.draw_func = Some(Box::new(move |p| func(p).into_dyn())); 53 self 54 } 55 } 56 57 /// Describes where we want to put the series label 58 pub enum SeriesLabelPosition { 59 UpperLeft, 60 MiddleLeft, 61 LowerLeft, 62 UpperMiddle, 63 MiddleMiddle, 64 LowerMiddle, 65 UpperRight, 66 MiddleRight, 67 LowerRight, 68 /// Force the series label drawn at the specific location 69 Coordinate(i32, i32), 70 } 71 72 impl SeriesLabelPosition { layout_label_area(&self, label_dim: (i32, i32), area_dim: (u32, u32)) -> (i32, i32)73 fn layout_label_area(&self, label_dim: (i32, i32), area_dim: (u32, u32)) -> (i32, i32) { 74 use SeriesLabelPosition::*; 75 ( 76 match self { 77 UpperLeft | MiddleLeft | LowerLeft => 5, 78 UpperMiddle | MiddleMiddle | LowerMiddle => { 79 (area_dim.0 as i32 - label_dim.0 as i32) / 2 80 } 81 UpperRight | MiddleRight | LowerRight => area_dim.0 as i32 - label_dim.0 as i32 - 5, 82 Coordinate(x, _) => *x, 83 }, 84 match self { 85 UpperLeft | UpperMiddle | UpperRight => 5, 86 MiddleLeft | MiddleMiddle | MiddleRight => { 87 (area_dim.1 as i32 - label_dim.1 as i32) / 2 88 } 89 LowerLeft | LowerMiddle | LowerRight => area_dim.1 as i32 - label_dim.1 as i32 - 5, 90 Coordinate(_, y) => *y, 91 }, 92 ) 93 } 94 } 95 96 /// The struct to specify the series label of a target chart context 97 pub struct SeriesLabelStyle<'a, 'b, DB: DrawingBackend, CT: CoordTranslate> { 98 target: &'b mut ChartContext<'a, DB, CT>, 99 position: SeriesLabelPosition, 100 legend_area_size: u32, 101 border_style: ShapeStyle, 102 background: ShapeStyle, 103 label_font: Option<TextStyle<'b>>, 104 margin: u32, 105 } 106 107 impl<'a, 'b, DB: DrawingBackend + 'a, CT: CoordTranslate> SeriesLabelStyle<'a, 'b, DB, CT> { new(target: &'b mut ChartContext<'a, DB, CT>) -> Self108 pub(super) fn new(target: &'b mut ChartContext<'a, DB, CT>) -> Self { 109 Self { 110 target, 111 position: SeriesLabelPosition::MiddleRight, 112 legend_area_size: 30, 113 border_style: (&TRANSPARENT).into(), 114 background: (&TRANSPARENT).into(), 115 label_font: None, 116 margin: 10, 117 } 118 } 119 120 /// Set the series label positioning style 121 /// `pos` - The positioning style position(&mut self, pos: SeriesLabelPosition) -> &mut Self122 pub fn position(&mut self, pos: SeriesLabelPosition) -> &mut Self { 123 self.position = pos; 124 self 125 } 126 127 /// Set the margin of the series label drawing are 128 /// 129 /// - `value`: The size specification margin<S: SizeDesc>(&mut self, value: S) -> &mut Self130 pub fn margin<S: SizeDesc>(&mut self, value: S) -> &mut Self { 131 self.margin = value 132 .in_pixels(&self.target.plotting_area().dim_in_pixel()) 133 .max(0) as u32; 134 self 135 } 136 137 /// Set the size of legend area 138 /// `size` - The size of legend area in pixel legend_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self139 pub fn legend_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self { 140 let size = size 141 .in_pixels(&self.target.plotting_area().dim_in_pixel()) 142 .max(0) as u32; 143 self.legend_area_size = size; 144 self 145 } 146 147 /// Set the style of the label series area 148 /// `style` - The style of the border border_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self149 pub fn border_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self { 150 self.border_style = style.into(); 151 self 152 } 153 154 /// Set the background style 155 /// `style` - The style of the border background_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self156 pub fn background_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self { 157 self.background = style.into(); 158 self 159 } 160 161 /// Set the series label font 162 /// `font` - The font label_font<F: IntoTextStyle<'b>>(&mut self, font: F) -> &mut Self163 pub fn label_font<F: IntoTextStyle<'b>>(&mut self, font: F) -> &mut Self { 164 self.label_font = Some(font.into_text_style(&self.target.plotting_area().dim_in_pixel())); 165 self 166 } 167 168 /// Draw the series label area draw(&mut self) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>>169 pub fn draw(&mut self) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> { 170 let drawing_area = self.target.plotting_area().strip_coord_spec(); 171 172 // TODO: Issue #68 Currently generic font family doesn't load on OSX, change this after the issue 173 // resolved 174 let default_font = ("sans-serif", 12).into_font(); 175 let default_style: TextStyle = default_font.into(); 176 177 let font = { 178 let mut temp = None; 179 std::mem::swap(&mut self.label_font, &mut temp); 180 temp.unwrap_or(default_style) 181 }; 182 183 let mut label_element = MultiLineText::<_, &str>::new((0, 0), &font); 184 let mut funcs = vec![]; 185 186 for anno in self.target.series_anno.iter() { 187 let label_text = anno.get_label(); 188 let draw_func = anno.get_draw_func(); 189 190 if label_text == "" && draw_func.is_none() { 191 continue; 192 } 193 194 funcs.push( 195 draw_func.unwrap_or_else(|| &|p: BackendCoord| EmptyElement::at(p).into_dyn()), 196 ); 197 label_element.push_line(label_text); 198 } 199 200 let (mut w, mut h) = label_element.estimate_dimension().map_err(|e| { 201 DrawingAreaErrorKind::BackendError(DrawingErrorKind::FontError(Box::new(e))) 202 })?; 203 204 let margin = self.margin as i32; 205 206 w += self.legend_area_size as i32 + margin * 2; 207 h += margin * 2; 208 209 let (area_w, area_h) = drawing_area.dim_in_pixel(); 210 211 let (label_x, label_y) = self.position.layout_label_area((w, h), (area_w, area_h)); 212 213 label_element.relocate(( 214 label_x + self.legend_area_size as i32 + margin, 215 label_y + margin, 216 )); 217 218 drawing_area.draw(&Rectangle::new( 219 [(label_x, label_y), (label_x + w, label_y + h)], 220 self.background.filled(), 221 ))?; 222 drawing_area.draw(&Rectangle::new( 223 [(label_x, label_y), (label_x + w, label_y + h)], 224 self.border_style.clone(), 225 ))?; 226 drawing_area.draw(&label_element)?; 227 228 for (((_, y0), (_, y1)), make_elem) in label_element 229 .compute_line_layout() 230 .map_err(|e| { 231 DrawingAreaErrorKind::BackendError(DrawingErrorKind::FontError(Box::new(e))) 232 })? 233 .into_iter() 234 .zip(funcs.into_iter()) 235 { 236 let legend_element = make_elem((label_x + margin, (y0 + y1) / 2)); 237 drawing_area.draw(&legend_element)?; 238 } 239 240 Ok(()) 241 } 242 } 243