• 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 #[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