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 /** 38 Sets the series label for the current series. 39 40 See [`ChartContext::configure_series_labels()`] for more information and examples. 41 */ label<L: Into<String>>(&mut self, label: L) -> &mut Self42 pub fn label<L: Into<String>>(&mut self, label: L) -> &mut Self { 43 self.label = Some(label.into()); 44 self 45 } 46 47 /** 48 Sets the legend element creator function. 49 50 - `func`: The function use to create the element 51 52 # Note 53 54 The creation function uses a shifted pixel-based coordinate system, where the 55 point (0,0) is defined to the mid-right point of the shape. 56 57 # See also 58 59 See [`ChartContext::configure_series_labels()`] for more information and examples. 60 */ legend<E: IntoDynElement<'a, DB, BackendCoord>, T: Fn(BackendCoord) -> E + 'a>( &mut self, func: T, ) -> &mut Self61 pub fn legend<E: IntoDynElement<'a, DB, BackendCoord>, T: Fn(BackendCoord) -> E + 'a>( 62 &mut self, 63 func: T, 64 ) -> &mut Self { 65 self.draw_func = Some(Box::new(move |p| func(p).into_dyn())); 66 self 67 } 68 } 69 70 /** 71 Useful to specify the position of the series label. 72 73 See [`ChartContext::configure_series_labels()`] for more information and examples. 74 */ 75 pub enum SeriesLabelPosition { 76 /// Places the series label at the upper left 77 UpperLeft, 78 /// Places the series label at the middle left 79 MiddleLeft, 80 /// Places the series label at the lower left 81 LowerLeft, 82 /// Places the series label at the upper middle 83 UpperMiddle, 84 /// Places the series label at the middle middle 85 MiddleMiddle, 86 /// Places the series label at the lower middle 87 LowerMiddle, 88 /// Places the series label at the upper right 89 UpperRight, 90 /// Places the series label at the middle right 91 MiddleRight, 92 /// Places the series label at the lower right 93 LowerRight, 94 /// Places the series label at the specific location in backend coordinates 95 Coordinate(i32, i32), 96 } 97 98 impl SeriesLabelPosition { layout_label_area(&self, label_dim: (i32, i32), area_dim: (u32, u32)) -> (i32, i32)99 fn layout_label_area(&self, label_dim: (i32, i32), area_dim: (u32, u32)) -> (i32, i32) { 100 use SeriesLabelPosition::*; 101 ( 102 match self { 103 UpperLeft | MiddleLeft | LowerLeft => 5, 104 UpperMiddle | MiddleMiddle | LowerMiddle => { 105 (area_dim.0 as i32 - label_dim.0 as i32) / 2 106 } 107 UpperRight | MiddleRight | LowerRight => area_dim.0 as i32 - label_dim.0 as i32 - 5, 108 Coordinate(x, _) => *x, 109 }, 110 match self { 111 UpperLeft | UpperMiddle | UpperRight => 5, 112 MiddleLeft | MiddleMiddle | MiddleRight => { 113 (area_dim.1 as i32 - label_dim.1 as i32) / 2 114 } 115 LowerLeft | LowerMiddle | LowerRight => area_dim.1 as i32 - label_dim.1 as i32 - 5, 116 Coordinate(_, y) => *y, 117 }, 118 ) 119 } 120 } 121 122 /// The struct to specify the series label of a target chart context 123 pub struct SeriesLabelStyle<'a, 'b, DB: DrawingBackend, CT: CoordTranslate> { 124 target: &'b mut ChartContext<'a, DB, CT>, 125 position: SeriesLabelPosition, 126 legend_area_size: u32, 127 border_style: ShapeStyle, 128 background: ShapeStyle, 129 label_font: Option<TextStyle<'b>>, 130 margin: u32, 131 } 132 133 impl<'a, 'b, DB: DrawingBackend + 'a, CT: CoordTranslate> SeriesLabelStyle<'a, 'b, DB, CT> { new(target: &'b mut ChartContext<'a, DB, CT>) -> Self134 pub(super) fn new(target: &'b mut ChartContext<'a, DB, CT>) -> Self { 135 Self { 136 target, 137 position: SeriesLabelPosition::MiddleRight, 138 legend_area_size: 30, 139 border_style: (&TRANSPARENT).into(), 140 background: (&TRANSPARENT).into(), 141 label_font: None, 142 margin: 10, 143 } 144 } 145 146 /** 147 Sets the series label positioning style 148 149 `pos` - The positioning style 150 151 See [`ChartContext::configure_series_labels()`] for more information and examples. 152 */ position(&mut self, pos: SeriesLabelPosition) -> &mut Self153 pub fn position(&mut self, pos: SeriesLabelPosition) -> &mut Self { 154 self.position = pos; 155 self 156 } 157 158 /** 159 Sets the margin of the series label drawing area. 160 161 - `value`: The size specification in backend units (pixels) 162 163 See [`ChartContext::configure_series_labels()`] for more information and examples. 164 */ margin<S: SizeDesc>(&mut self, value: S) -> &mut Self165 pub fn margin<S: SizeDesc>(&mut self, value: S) -> &mut Self { 166 self.margin = value 167 .in_pixels(&self.target.plotting_area().dim_in_pixel()) 168 .max(0) as u32; 169 self 170 } 171 172 /** 173 Sets the size of the legend area. 174 175 `size` - The size of legend area in backend units (pixels) 176 177 See [`ChartContext::configure_series_labels()`] for more information and examples. 178 */ legend_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self179 pub fn legend_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self { 180 let size = size 181 .in_pixels(&self.target.plotting_area().dim_in_pixel()) 182 .max(0) as u32; 183 self.legend_area_size = size; 184 self 185 } 186 187 /** 188 Sets the style of the label series area. 189 190 `style` - The style of the border 191 192 See [`ChartContext::configure_series_labels()`] for more information and examples. 193 */ border_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self194 pub fn border_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self { 195 self.border_style = style.into(); 196 self 197 } 198 199 /** 200 Sets the background style of the label series area. 201 202 `style` - The style of the border 203 204 See [`ChartContext::configure_series_labels()`] for more information and examples. 205 */ background_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self206 pub fn background_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self { 207 self.background = style.into(); 208 self 209 } 210 211 /** 212 Sets the font for series labels. 213 214 `font` - Desired font 215 216 See [`ChartContext::configure_series_labels()`] for more information and examples. 217 */ label_font<F: IntoTextStyle<'b>>(&mut self, font: F) -> &mut Self218 pub fn label_font<F: IntoTextStyle<'b>>(&mut self, font: F) -> &mut Self { 219 self.label_font = Some(font.into_text_style(&self.target.plotting_area().dim_in_pixel())); 220 self 221 } 222 223 /** 224 Draws the series label area. 225 226 See [`ChartContext::configure_series_labels()`] for more information and examples. 227 */ draw(&mut self) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>>228 pub fn draw(&mut self) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> { 229 let drawing_area = self.target.plotting_area().strip_coord_spec(); 230 231 // TODO: Issue #68 Currently generic font family doesn't load on OSX, change this after the issue 232 // resolved 233 let default_font = ("sans-serif", 12).into_font(); 234 let default_style: TextStyle = default_font.into(); 235 236 let font = { 237 let mut temp = None; 238 std::mem::swap(&mut self.label_font, &mut temp); 239 temp.unwrap_or(default_style) 240 }; 241 242 let mut label_element = MultiLineText::<_, &str>::new((0, 0), &font); 243 let mut funcs = vec![]; 244 245 for anno in self.target.series_anno.iter() { 246 let label_text = anno.get_label(); 247 let draw_func = anno.get_draw_func(); 248 249 if label_text.is_empty() && draw_func.is_none() { 250 continue; 251 } 252 253 funcs.push( 254 draw_func.unwrap_or(&|p: BackendCoord| EmptyElement::at(p).into_dyn()), 255 ); 256 label_element.push_line(label_text); 257 } 258 259 let (mut w, mut h) = label_element.estimate_dimension().map_err(|e| { 260 DrawingAreaErrorKind::BackendError(DrawingErrorKind::FontError(Box::new(e))) 261 })?; 262 263 let margin = self.margin as i32; 264 265 w += self.legend_area_size as i32 + margin * 2; 266 h += margin * 2; 267 268 let (area_w, area_h) = drawing_area.dim_in_pixel(); 269 270 let (label_x, label_y) = self.position.layout_label_area((w, h), (area_w, area_h)); 271 272 label_element.relocate(( 273 label_x + self.legend_area_size as i32 + margin, 274 label_y + margin, 275 )); 276 277 drawing_area.draw(&Rectangle::new( 278 [(label_x, label_y), (label_x + w, label_y + h)], 279 self.background.filled(), 280 ))?; 281 drawing_area.draw(&Rectangle::new( 282 [(label_x, label_y), (label_x + w, label_y + h)], 283 self.border_style, 284 ))?; 285 drawing_area.draw(&label_element)?; 286 287 for (((_, y0), (_, y1)), make_elem) in label_element 288 .compute_line_layout() 289 .map_err(|e| { 290 DrawingAreaErrorKind::BackendError(DrawingErrorKind::FontError(Box::new(e))) 291 })? 292 .into_iter() 293 .zip(funcs.into_iter()) 294 { 295 let legend_element = make_elem((label_x + margin, (y0 + y1) / 2)); 296 drawing_area.draw(&legend_element)?; 297 } 298 299 Ok(()) 300 } 301 } 302