• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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