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