1use std::marker::PhantomData;
2
3use super::builder::LabelAreaPosition;
4use super::context::ChartContext;
5use crate::coord::cartesian::{Cartesian2d, MeshLine};
6use crate::coord::ranged1d::{BoldPoints, LightPoints, Ranged, ValueFormatter};
7use crate::drawing::DrawingAreaErrorKind;
8use crate::style::{
9 AsRelative, Color, FontDesc, FontFamily, FontStyle, IntoTextStyle, RGBColor, ShapeStyle,
10 SizeDesc, TextStyle,
11};
12
13use plotters_backend::DrawingBackend;
14
15/// The style used to describe the mesh and axis for a secondary coordinate system.
16pub struct SecondaryMeshStyle<'a, 'b, X: Ranged, Y: Ranged, DB: DrawingBackend> {
17 style: MeshStyle<'a, 'b, X, Y, DB>,
18}
19
20impl<'a, 'b, XT, YT, X: Ranged<ValueType = XT>, Y: Ranged<ValueType = YT>, DB: DrawingBackend>
21 SecondaryMeshStyle<'a, 'b, X, Y, DB>
22where
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
145pub 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
173impl<'a, 'b, X, Y, XT, YT, DB> MeshStyle<'a, 'b, X, Y, DB>
174where
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
224impl<'a, 'b, X, Y, DB> MeshStyle<'a, 'b, X, Y, DB>
225where
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