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 | 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 { |
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> { |
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 | |