• 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     /**
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