1use super::ChartContext;
2use crate::coord::CoordTranslate;
3use crate::drawing::DrawingAreaErrorKind;
4use crate::element::{DynElement, EmptyElement, IntoDynElement, MultiLineText, Rectangle};
5use crate::style::{IntoFont, IntoTextStyle, ShapeStyle, SizeDesc, TextStyle, TRANSPARENT};
6
7use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
8
9type 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.
14pub struct SeriesAnno<'a, DB: DrawingBackend> {
15 label: Option<String>,
16 draw_func: Option<Box<SeriesAnnoDrawFn<'a, DB>>>,
17}
18
19impl<'a, DB: DrawingBackend> SeriesAnno<'a, DB> {
20 #[allow(clippy::option_as_ref_deref)]
21 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
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
30 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 */
42 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 */
61 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/**
71Useful to specify the position of the series label.
72
73See [`ChartContext::configure_series_labels()`] for more information and examples.
74*/
75pub 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
98impl SeriesLabelPosition {
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
123pub 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
133impl<'a, 'b, DB: DrawingBackend + 'a, CT: CoordTranslate> SeriesLabelStyle<'a, 'b, DB, CT> {
134 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 */
153 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 */
165 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 */
179 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 */
194 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 */
206 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 */
218 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 */
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(draw_func.unwrap_or(&|p: BackendCoord| EmptyElement::at(p).into_dyn()));
254 label_element.push_line(label_text);
255 }
256
257 let (mut w, mut h) = label_element.estimate_dimension().map_err(|e| {
258 DrawingAreaErrorKind::BackendError(DrawingErrorKind::FontError(Box::new(e)))
259 })?;
260
261 let margin = self.margin as i32;
262
263 w += self.legend_area_size as i32 + margin * 2;
264 h += margin * 2;
265
266 let (area_w, area_h) = drawing_area.dim_in_pixel();
267
268 let (label_x, label_y) = self.position.layout_label_area((w, h), (area_w, area_h));
269
270 label_element.relocate((
271 label_x + self.legend_area_size as i32 + margin,
272 label_y + margin,
273 ));
274
275 drawing_area.draw(&Rectangle::new(
276 [(label_x, label_y), (label_x + w, label_y + h)],
277 self.background.filled(),
278 ))?;
279 drawing_area.draw(&Rectangle::new(
280 [(label_x, label_y), (label_x + w, label_y + h)],
281 self.border_style,
282 ))?;
283 drawing_area.draw(&label_element)?;
284
285 for (((_, y0), (_, y1)), make_elem) in label_element
286 .compute_line_layout()
287 .map_err(|e| {
288 DrawingAreaErrorKind::BackendError(DrawingErrorKind::FontError(Box::new(e)))
289 })?
290 .into_iter()
291 .zip(funcs.into_iter())
292 {
293 let legend_element = make_elem((label_x + margin, (y0 + y1) / 2));
294 drawing_area.draw(&legend_element)?;
295 }
296
297 Ok(())
298 }
299}
300