1 | use std::marker::PhantomData; |
2 | |
3 | use super::builder::LabelAreaPosition; |
4 | use super::context::ChartContext; |
5 | use crate::coord::cartesian::{Cartesian2d, MeshLine}; |
6 | use crate::coord::ranged1d::{BoldPoints, LightPoints, Ranged, ValueFormatter}; |
7 | use crate::drawing::DrawingAreaErrorKind; |
8 | use crate::style::{ |
9 | AsRelative, Color, FontDesc, FontFamily, FontStyle, IntoTextStyle, RGBColor, ShapeStyle, |
10 | SizeDesc, TextStyle, |
11 | }; |
12 | |
13 | use plotters_backend::DrawingBackend; |
14 | |
15 | /// The style used to describe the mesh and axis for a secondary coordinate system. |
16 | pub struct SecondaryMeshStyle<'a, 'b, X: Ranged, Y: Ranged, DB: DrawingBackend> { |
17 | style: MeshStyle<'a, 'b, X, Y, DB>, |
18 | } |
19 | |
20 | impl<'a, 'b, XT, YT, X: Ranged<ValueType = XT>, Y: Ranged<ValueType = YT>, DB: DrawingBackend> |
21 | SecondaryMeshStyle<'a, 'b, X, Y, DB> |
22 | where |
23 | X: ValueFormatter<XT>, |
24 | Y: ValueFormatter<YT>, |
25 | { |
26 | pub(super) fn new(target: &'b mut ChartContext<'a, DB, Cartesian2d<X, Y>>) -> Self { |
27 | let mut style = target.configure_mesh(); |
28 | style.draw_x_mesh = false; |
29 | style.draw_y_mesh = false; |
30 | Self { style } |
31 | } |
32 | |
33 | /// Set the style definition for the axis |
34 | /// - `style`: The style for the axis |
35 | pub fn axis_style<T: Into<ShapeStyle>>(&mut self, style: T) -> &mut Self { |
36 | self.style.axis_style(style); |
37 | self |
38 | } |
39 | |
40 | /// The offset of x labels. This is used when we want to place the label in the middle of |
41 | /// the grid. This is used to adjust label position for histograms, but since plotters 0.3, this |
42 | /// use case is deprecated, see [SegmentedCoord coord decorator](../coord/ranged1d/trait.IntoSegmentedCoord.html) for more details |
43 | /// - `value`: The offset in pixel |
44 | pub fn x_label_offset<S: SizeDesc>(&mut self, value: S) -> &mut Self { |
45 | self.style.x_label_offset(value); |
46 | self |
47 | } |
48 | |
49 | /// The offset of y labels. This is used when we want to place the label in the middle of |
50 | /// the grid. This is used to adjust label position for histograms, but since plotters 0.3, this |
51 | /// use case is deprecated, see [SegmentedCoord coord decorator](../coord/ranged1d/trait.IntoSegmentedCoord.html) for more details |
52 | /// - `value`: The offset in pixel |
53 | pub fn y_label_offset<S: SizeDesc>(&mut self, value: S) -> &mut Self { |
54 | self.style.y_label_offset(value); |
55 | self |
56 | } |
57 | |
58 | /// Set how many labels for the X axis at most |
59 | /// - `value`: The maximum desired number of labels in the X axis |
60 | pub fn x_labels(&mut self, value: usize) -> &mut Self { |
61 | self.style.x_labels(value); |
62 | self |
63 | } |
64 | |
65 | /// Set how many label for the Y axis at most |
66 | /// - `value`: The maximum desired number of labels in the Y axis |
67 | pub fn y_labels(&mut self, value: usize) -> &mut Self { |
68 | self.style.y_labels(value); |
69 | self |
70 | } |
71 | |
72 | /// Set the formatter function for the X label text |
73 | /// - `fmt`: The formatter function |
74 | pub fn x_label_formatter(&mut self, fmt: &'b dyn Fn(&X::ValueType) -> String) -> &mut Self { |
75 | self.style.x_label_formatter(fmt); |
76 | self |
77 | } |
78 | |
79 | /// Set the formatter function for the Y label text |
80 | /// - `fmt`: The formatter function |
81 | pub fn y_label_formatter(&mut self, fmt: &'b dyn Fn(&Y::ValueType) -> String) -> &mut Self { |
82 | self.style.y_label_formatter(fmt); |
83 | self |
84 | } |
85 | |
86 | /// Set the axis description's style. If not given, use label style instead. |
87 | /// - `style`: The text style that would be applied to descriptions |
88 | pub fn axis_desc_style<T: IntoTextStyle<'b>>(&mut self, style: T) -> &mut Self { |
89 | self.style |
90 | .axis_desc_style(style.into_text_style(&self.style.parent_size)); |
91 | self |
92 | } |
93 | |
94 | /// Set the X axis's description |
95 | /// - `desc`: The description of the X axis |
96 | pub fn x_desc<T: Into<String>>(&mut self, desc: T) -> &mut Self { |
97 | self.style.x_desc(desc); |
98 | self |
99 | } |
100 | |
101 | /// Set the Y axis's description |
102 | /// - `desc`: The description of the Y axis |
103 | pub fn y_desc<T: Into<String>>(&mut self, desc: T) -> &mut Self { |
104 | self.style.y_desc(desc); |
105 | self |
106 | } |
107 | |
108 | /// Draw the axes for the secondary coordinate system |
109 | pub fn draw(&mut self) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> { |
110 | self.style.draw() |
111 | } |
112 | |
113 | /// Set the label style for the secondary axis |
114 | pub fn label_style<T: IntoTextStyle<'b>>(&mut self, style: T) -> &mut Self { |
115 | self.style.label_style(style); |
116 | self |
117 | } |
118 | |
119 | /// Set all the tick marks to the same size |
120 | /// `value`: The new size |
121 | pub fn set_all_tick_mark_size<S: SizeDesc>(&mut self, value: S) -> &mut Self { |
122 | let size = value.in_pixels(&self.style.parent_size); |
123 | self.style.x_tick_size = [size, size]; |
124 | self.style.y_tick_size = [size, size]; |
125 | self |
126 | } |
127 | /// Sets the tick mark size for a given label area position. |
128 | /// `value`: The new size |
129 | pub fn set_tick_mark_size<S: SizeDesc>( |
130 | &mut self, |
131 | pos: LabelAreaPosition, |
132 | value: S, |
133 | ) -> &mut Self { |
134 | *match pos { |
135 | LabelAreaPosition::Top => &mut self.style.x_tick_size[0], |
136 | LabelAreaPosition::Bottom => &mut self.style.x_tick_size[1], |
137 | LabelAreaPosition::Left => &mut self.style.y_tick_size[0], |
138 | LabelAreaPosition::Right => &mut self.style.y_tick_size[1], |
139 | } = value.in_pixels(&self.style.parent_size); |
140 | self |
141 | } |
142 | } |
143 | |
144 | /// The struct that is used for tracking the configuration of a mesh of any chart |
145 | pub struct MeshStyle<'a, 'b, X: Ranged, Y: Ranged, DB: DrawingBackend> { |
146 | pub(super) parent_size: (u32, u32), |
147 | pub(super) draw_x_mesh: bool, |
148 | pub(super) draw_y_mesh: bool, |
149 | pub(super) draw_x_axis: bool, |
150 | pub(super) draw_y_axis: bool, |
151 | pub(super) x_label_offset: i32, |
152 | pub(super) y_label_offset: i32, |
153 | pub(super) x_light_lines_limit: usize, |
154 | pub(super) y_light_lines_limit: usize, |
155 | pub(super) n_x_labels: usize, |
156 | pub(super) n_y_labels: usize, |
157 | pub(super) axis_desc_style: Option<TextStyle<'b>>, |
158 | pub(super) x_desc: Option<String>, |
159 | pub(super) y_desc: Option<String>, |
160 | pub(super) bold_line_style: Option<ShapeStyle>, |
161 | pub(super) light_line_style: Option<ShapeStyle>, |
162 | pub(super) axis_style: Option<ShapeStyle>, |
163 | pub(super) x_label_style: Option<TextStyle<'b>>, |
164 | pub(super) y_label_style: Option<TextStyle<'b>>, |
165 | pub(super) format_x: Option<&'b dyn Fn(&X::ValueType) -> String>, |
166 | pub(super) format_y: Option<&'b dyn Fn(&Y::ValueType) -> String>, |
167 | pub(super) target: Option<&'b mut ChartContext<'a, DB, Cartesian2d<X, Y>>>, |
168 | pub(super) _phantom_data: PhantomData<(X, Y)>, |
169 | pub(super) x_tick_size: [i32; 2], |
170 | pub(super) y_tick_size: [i32; 2], |
171 | } |
172 | |
173 | impl<'a, 'b, X, Y, XT, YT, DB> MeshStyle<'a, 'b, X, Y, DB> |
174 | where |
175 | X: Ranged<ValueType = XT> + ValueFormatter<XT>, |
176 | Y: Ranged<ValueType = YT> + ValueFormatter<YT>, |
177 | DB: DrawingBackend, |
178 | { |
179 | pub(crate) fn new(chart: &'b mut ChartContext<'a, DB, Cartesian2d<X, Y>>) -> Self { |
180 | let base_tick_size = (5u32).percent().max(5).in_pixels(chart.plotting_area()); |
181 | |
182 | let mut x_tick_size = [base_tick_size, base_tick_size]; |
183 | let mut y_tick_size = [base_tick_size, base_tick_size]; |
184 | |
185 | for idx in 0..2 { |
186 | if chart.is_overlapping_drawing_area(chart.x_label_area[idx].as_ref()) { |
187 | x_tick_size[idx] = -x_tick_size[idx]; |
188 | } |
189 | if chart.is_overlapping_drawing_area(chart.y_label_area[idx].as_ref()) { |
190 | y_tick_size[idx] = -y_tick_size[idx]; |
191 | } |
192 | } |
193 | |
194 | MeshStyle { |
195 | parent_size: chart.drawing_area.dim_in_pixel(), |
196 | axis_style: None, |
197 | x_label_offset: 0, |
198 | y_label_offset: 0, |
199 | draw_x_mesh: true, |
200 | draw_y_mesh: true, |
201 | draw_x_axis: true, |
202 | draw_y_axis: true, |
203 | x_light_lines_limit: 10, |
204 | y_light_lines_limit: 10, |
205 | n_x_labels: 11, |
206 | n_y_labels: 11, |
207 | bold_line_style: None, |
208 | light_line_style: None, |
209 | x_label_style: None, |
210 | y_label_style: None, |
211 | format_x: None, |
212 | format_y: None, |
213 | target: Some(chart), |
214 | _phantom_data: PhantomData, |
215 | x_desc: None, |
216 | y_desc: None, |
217 | axis_desc_style: None, |
218 | x_tick_size, |
219 | y_tick_size, |
220 | } |
221 | } |
222 | } |
223 | |
224 | impl<'a, 'b, X, Y, DB> MeshStyle<'a, 'b, X, Y, DB> |
225 | where |
226 | X: Ranged, |
227 | Y: Ranged, |
228 | DB: DrawingBackend, |
229 | { |
230 | /// Set all the tick mark to the same size |
231 | /// `value`: The new size |
232 | pub fn set_all_tick_mark_size<S: SizeDesc>(&mut self, value: S) -> &mut Self { |
233 | let size = value.in_pixels(&self.parent_size); |
234 | self.x_tick_size = [size, size]; |
235 | self.y_tick_size = [size, size]; |
236 | self |
237 | } |
238 | |
239 | /// Set the tick mark size on the axes. When this is set to negative, the axis value label will |
240 | /// become inward. |
241 | /// |
242 | /// - `pos`: The which label area we want to set |
243 | /// - `value`: The size specification |
244 | pub fn set_tick_mark_size<S: SizeDesc>( |
245 | &mut self, |
246 | pos: LabelAreaPosition, |
247 | value: S, |
248 | ) -> &mut Self { |
249 | *match pos { |
250 | LabelAreaPosition::Top => &mut self.x_tick_size[0], |
251 | LabelAreaPosition::Bottom => &mut self.x_tick_size[1], |
252 | LabelAreaPosition::Left => &mut self.y_tick_size[0], |
253 | LabelAreaPosition::Right => &mut self.y_tick_size[1], |
254 | } = value.in_pixels(&self.parent_size); |
255 | self |
256 | } |
257 | |
258 | /// The offset of x labels. This is used when we want to place the label in the middle of |
259 | /// the grid. This is used to adjust label position for histograms, but since plotters 0.3, this |
260 | /// use case is deprecated, see [SegmentedCoord coord decorator](../coord/ranged1d/trait.IntoSegmentedCoord.html) for more details |
261 | /// - `value`: The offset in pixel |
262 | pub fn x_label_offset<S: SizeDesc>(&mut self, value: S) -> &mut Self { |
263 | self.x_label_offset = value.in_pixels(&self.parent_size); |
264 | self |
265 | } |
266 | |
267 | /// The offset of y labels. This is used when we want to place the label in the middle of |
268 | /// the grid. This is used to adjust label position for histograms, but since plotters 0.3, this |
269 | /// use case is deprecated, see [SegmentedCoord coord decorator](../coord/ranged1d/trait.IntoSegmentedCoord.html) for more details |
270 | /// - `value`: The offset in pixel |
271 | pub fn y_label_offset<S: SizeDesc>(&mut self, value: S) -> &mut Self { |
272 | self.y_label_offset = value.in_pixels(&self.parent_size); |
273 | self |
274 | } |
275 | |
276 | /// Disable the mesh for the x axis. |
277 | pub fn disable_x_mesh(&mut self) -> &mut Self { |
278 | self.draw_x_mesh = false; |
279 | self |
280 | } |
281 | |
282 | /// Disable the mesh for the y axis |
283 | pub fn disable_y_mesh(&mut self) -> &mut Self { |
284 | self.draw_y_mesh = false; |
285 | self |
286 | } |
287 | |
288 | /// Disable drawing the X axis |
289 | pub fn disable_x_axis(&mut self) -> &mut Self { |
290 | self.draw_x_axis = false; |
291 | self |
292 | } |
293 | |
294 | /// Disable drawing the Y axis |
295 | pub fn disable_y_axis(&mut self) -> &mut Self { |
296 | self.draw_y_axis = false; |
297 | self |
298 | } |
299 | |
300 | /// Disable drawing all meshes |
301 | pub fn disable_mesh(&mut self) -> &mut Self { |
302 | self.disable_x_mesh().disable_y_mesh() |
303 | } |
304 | |
305 | /// Disable drawing all axes |
306 | pub fn disable_axes(&mut self) -> &mut Self { |
307 | self.disable_x_axis().disable_y_axis() |
308 | } |
309 | |
310 | /// Set the style definition for the axis |
311 | /// - `style`: The style for the axis |
312 | pub fn axis_style<T: Into<ShapeStyle>>(&mut self, style: T) -> &mut Self { |
313 | self.axis_style = Some(style.into()); |
314 | self |
315 | } |
316 | |
317 | /// Set the maximum number of divisions for the minor grid |
318 | /// - `value`: Maximum desired divisions between two consecutive X labels |
319 | pub fn x_max_light_lines(&mut self, value: usize) -> &mut Self { |
320 | self.x_light_lines_limit = value; |
321 | self |
322 | } |
323 | |
324 | /// Set the maximum number of divisions for the minor grid |
325 | /// - `value`: Maximum desired divisions between two consecutive Y labels |
326 | pub fn y_max_light_lines(&mut self, value: usize) -> &mut Self { |
327 | self.y_light_lines_limit = value; |
328 | self |
329 | } |
330 | |
331 | /// Set the maximum number of divisions for the minor grid |
332 | /// - `value`: Maximum desired divisions between two consecutive labels in X and Y |
333 | pub fn max_light_lines(&mut self, value: usize) -> &mut Self { |
334 | self.x_light_lines_limit = value; |
335 | self.y_light_lines_limit = value; |
336 | self |
337 | } |
338 | |
339 | /// Set how many labels for the X axis at most |
340 | /// - `value`: The maximum desired number of labels in the X axis |
341 | pub fn x_labels(&mut self, value: usize) -> &mut Self { |
342 | self.n_x_labels = value; |
343 | self |
344 | } |
345 | |
346 | /// Set how many label for the Y axis at most |
347 | /// - `value`: The maximum desired number of labels in the Y axis |
348 | pub fn y_labels(&mut self, value: usize) -> &mut Self { |
349 | self.n_y_labels = value; |
350 | self |
351 | } |
352 | |
353 | /// Set the style for the coarse grind grid |
354 | /// - `style`: This is the coarse grind grid style |
355 | pub fn bold_line_style<T: Into<ShapeStyle>>(&mut self, style: T) -> &mut Self { |
356 | self.bold_line_style = Some(style.into()); |
357 | self |
358 | } |
359 | |
360 | /// Set the style for the fine grind grid |
361 | /// - `style`: The fine grind grid style |
362 | pub fn light_line_style<T: Into<ShapeStyle>>(&mut self, style: T) -> &mut Self { |
363 | self.light_line_style = Some(style.into()); |
364 | self |
365 | } |
366 | |
367 | /// Set the style of the label text |
368 | /// - `style`: The text style that would be applied to the labels |
369 | pub fn label_style<T: IntoTextStyle<'b>>(&mut self, style: T) -> &mut Self { |
370 | let style = style.into_text_style(&self.parent_size); |
371 | self.x_label_style = Some(style.clone()); |
372 | self.y_label_style = Some(style); |
373 | self |
374 | } |
375 | |
376 | /// Set the style of the label X axis text |
377 | /// - `style`: The text style that would be applied to the labels |
378 | pub fn x_label_style<T: IntoTextStyle<'b>>(&mut self, style: T) -> &mut Self { |
379 | self.x_label_style = Some(style.into_text_style(&self.parent_size)); |
380 | self |
381 | } |
382 | |
383 | /// Set the style of the label Y axis text |
384 | /// - `style`: The text style that would be applied to the labels |
385 | pub fn y_label_style<T: IntoTextStyle<'b>>(&mut self, style: T) -> &mut Self { |
386 | self.y_label_style = Some(style.into_text_style(&self.parent_size)); |
387 | self |
388 | } |
389 | |
390 | /// Set the formatter function for the X label text |
391 | /// - `fmt`: The formatter function |
392 | pub fn x_label_formatter(&mut self, fmt: &'b dyn Fn(&X::ValueType) -> String) -> &mut Self { |
393 | self.format_x = Some(fmt); |
394 | self |
395 | } |
396 | |
397 | /// Set the formatter function for the Y label text |
398 | /// - `fmt`: The formatter function |
399 | pub fn y_label_formatter(&mut self, fmt: &'b dyn Fn(&Y::ValueType) -> String) -> &mut Self { |
400 | self.format_y = Some(fmt); |
401 | self |
402 | } |
403 | |
404 | /// Set the axis description's style. If not given, use label style instead. |
405 | /// - `style`: The text style that would be applied to descriptions |
406 | pub fn axis_desc_style<T: IntoTextStyle<'b>>(&mut self, style: T) -> &mut Self { |
407 | self.axis_desc_style = Some(style.into_text_style(&self.parent_size)); |
408 | self |
409 | } |
410 | |
411 | /// Set the X axis's description |
412 | /// - `desc`: The description of the X axis |
413 | pub fn x_desc<T: Into<String>>(&mut self, desc: T) -> &mut Self { |
414 | self.x_desc = Some(desc.into()); |
415 | self |
416 | } |
417 | |
418 | /// Set the Y axis's description |
419 | /// - `desc`: The description of the Y axis |
420 | pub fn y_desc<T: Into<String>>(&mut self, desc: T) -> &mut Self { |
421 | self.y_desc = Some(desc.into()); |
422 | self |
423 | } |
424 | |
425 | /// Draw the configured mesh on the target plot |
426 | pub fn draw(&mut self) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> |
427 | where |
428 | X: ValueFormatter<<X as Ranged>::ValueType>, |
429 | Y: ValueFormatter<<Y as Ranged>::ValueType>, |
430 | { |
431 | let target = self.target.take().unwrap(); |
432 | |
433 | let default_mesh_color_1 = RGBColor(0, 0, 0).mix(0.2); |
434 | let default_mesh_color_2 = RGBColor(0, 0, 0).mix(0.1); |
435 | let default_axis_color = RGBColor(0, 0, 0); |
436 | let default_label_font = FontDesc::new( |
437 | FontFamily::SansSerif, |
438 | f64::from((12i32).percent().max(12).in_pixels(&self.parent_size)), |
439 | FontStyle::Normal, |
440 | ); |
441 | |
442 | let bold_style = self |
443 | .bold_line_style |
444 | .unwrap_or_else(|| (&default_mesh_color_1).into()); |
445 | let light_style = self |
446 | .light_line_style |
447 | .unwrap_or_else(|| (&default_mesh_color_2).into()); |
448 | let axis_style = self |
449 | .axis_style |
450 | .unwrap_or_else(|| (&default_axis_color).into()); |
451 | |
452 | let x_label_style = self |
453 | .x_label_style |
454 | .clone() |
455 | .unwrap_or_else(|| default_label_font.clone().into()); |
456 | |
457 | let y_label_style = self |
458 | .y_label_style |
459 | .clone() |
460 | .unwrap_or_else(|| default_label_font.into()); |
461 | |
462 | let axis_desc_style = self |
463 | .axis_desc_style |
464 | .clone() |
465 | .unwrap_or_else(|| x_label_style.clone()); |
466 | |
467 | target.draw_mesh( |
468 | ( |
469 | LightPoints::new(self.n_y_labels, self.n_y_labels * self.y_light_lines_limit), |
470 | LightPoints::new(self.n_x_labels, self.n_x_labels * self.x_light_lines_limit), |
471 | ), |
472 | &light_style, |
473 | &x_label_style, |
474 | &y_label_style, |
475 | |_, _, _| None, |
476 | self.draw_x_mesh, |
477 | self.draw_y_mesh, |
478 | self.x_label_offset, |
479 | self.y_label_offset, |
480 | false, |
481 | false, |
482 | &axis_style, |
483 | &axis_desc_style, |
484 | self.x_desc.clone(), |
485 | self.y_desc.clone(), |
486 | self.x_tick_size, |
487 | self.y_tick_size, |
488 | )?; |
489 | |
490 | target.draw_mesh( |
491 | (BoldPoints(self.n_y_labels), BoldPoints(self.n_x_labels)), |
492 | &bold_style, |
493 | &x_label_style, |
494 | &y_label_style, |
495 | |xr, yr, m| match m { |
496 | MeshLine::XMesh(_, _, v) => { |
497 | if self.draw_x_axis { |
498 | if let Some(fmt_func) = self.format_x { |
499 | Some(fmt_func(v)) |
500 | } else { |
501 | Some(xr.format_ext(v)) |
502 | } |
503 | } else { |
504 | None |
505 | } |
506 | } |
507 | MeshLine::YMesh(_, _, v) => { |
508 | if self.draw_y_axis { |
509 | if let Some(fmt_func) = self.format_y { |
510 | Some(fmt_func(v)) |
511 | } else { |
512 | Some(yr.format_ext(v)) |
513 | } |
514 | } else { |
515 | None |
516 | } |
517 | } |
518 | }, |
519 | self.draw_x_mesh, |
520 | self.draw_y_mesh, |
521 | self.x_label_offset, |
522 | self.y_label_offset, |
523 | self.draw_x_axis, |
524 | self.draw_y_axis, |
525 | &axis_style, |
526 | &axis_desc_style, |
527 | None, |
528 | None, |
529 | self.x_tick_size, |
530 | self.y_tick_size, |
531 | ) |
532 | } |
533 | } |
534 | |