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)] |
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 | /** |
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 { |
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: &i32, _) => *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: &i32) => *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> { |
131 | 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 | */ |
150 | 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 | */ |
162 | 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 | */ |
176 | 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 | */ |
191 | 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 | */ |
203 | 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 | */ |
215 | 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 | */ |
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 | |