plotters/chart/
mesh.rs

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 [CentricDiscreteRanged coord decorator](../coord/trait.IntoCentric.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 [CentricDiscreteRanged coord decorator](../coord/trait.IntoCentric.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 mark 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
128    pub fn set_tick_mark_size<S: SizeDesc>(
129        &mut self,
130        pos: LabelAreaPosition,
131        value: S,
132    ) -> &mut Self {
133        *match pos {
134            LabelAreaPosition::Top => &mut self.style.x_tick_size[0],
135            LabelAreaPosition::Bottom => &mut self.style.x_tick_size[1],
136            LabelAreaPosition::Left => &mut self.style.y_tick_size[0],
137            LabelAreaPosition::Right => &mut self.style.y_tick_size[1],
138        } = value.in_pixels(&self.style.parent_size);
139        self
140    }
141}
142
143/// The struct that is used for tracking the configuration of a mesh of any chart
144pub struct MeshStyle<'a, 'b, X: Ranged, Y: Ranged, DB: DrawingBackend> {
145    pub(super) parent_size: (u32, u32),
146    pub(super) draw_x_mesh: bool,
147    pub(super) draw_y_mesh: bool,
148    pub(super) draw_x_axis: bool,
149    pub(super) draw_y_axis: bool,
150    pub(super) x_label_offset: i32,
151    pub(super) y_label_offset: i32,
152    pub(super) n_x_labels: usize,
153    pub(super) n_y_labels: usize,
154    pub(super) axis_desc_style: Option<TextStyle<'b>>,
155    pub(super) x_desc: Option<String>,
156    pub(super) y_desc: Option<String>,
157    pub(super) bold_line_style: Option<ShapeStyle>,
158    pub(super) light_line_style: Option<ShapeStyle>,
159    pub(super) axis_style: Option<ShapeStyle>,
160    pub(super) x_label_style: Option<TextStyle<'b>>,
161    pub(super) y_label_style: Option<TextStyle<'b>>,
162    pub(super) format_x: &'b dyn Fn(&X::ValueType) -> String,
163    pub(super) format_y: &'b dyn Fn(&Y::ValueType) -> String,
164    pub(super) target: Option<&'b mut ChartContext<'a, DB, Cartesian2d<X, Y>>>,
165    pub(super) _phantom_data: PhantomData<(X, Y)>,
166    pub(super) x_tick_size: [i32; 2],
167    pub(super) y_tick_size: [i32; 2],
168}
169
170impl<'a, 'b, X, Y, XT, YT, DB> MeshStyle<'a, 'b, X, Y, DB>
171where
172    X: Ranged<ValueType = XT> + ValueFormatter<XT>,
173    Y: Ranged<ValueType = YT> + ValueFormatter<YT>,
174    DB: DrawingBackend,
175{
176    pub(crate) fn new(chart: &'b mut ChartContext<'a, DB, Cartesian2d<X, Y>>) -> Self {
177        let base_tick_size = (5u32).percent().max(5).in_pixels(chart.plotting_area());
178
179        let mut x_tick_size = [base_tick_size, base_tick_size];
180        let mut y_tick_size = [base_tick_size, base_tick_size];
181
182        for idx in 0..2 {
183            if chart.is_overlapping_drawing_area(chart.x_label_area[idx].as_ref()) {
184                x_tick_size[idx] = -x_tick_size[idx];
185            }
186            if chart.is_overlapping_drawing_area(chart.y_label_area[idx].as_ref()) {
187                y_tick_size[idx] = -y_tick_size[idx];
188            }
189        }
190
191        MeshStyle {
192            parent_size: chart.drawing_area.dim_in_pixel(),
193            axis_style: None,
194            x_label_offset: 0,
195            y_label_offset: 0,
196            draw_x_mesh: true,
197            draw_y_mesh: true,
198            draw_x_axis: true,
199            draw_y_axis: true,
200            n_x_labels: 10,
201            n_y_labels: 10,
202            bold_line_style: None,
203            light_line_style: None,
204            x_label_style: None,
205            y_label_style: None,
206            format_x: &X::format,
207            format_y: &Y::format,
208            target: Some(chart),
209            _phantom_data: PhantomData,
210            x_desc: None,
211            y_desc: None,
212            axis_desc_style: None,
213            x_tick_size,
214            y_tick_size,
215        }
216    }
217}
218
219impl<'a, 'b, X, Y, DB> MeshStyle<'a, 'b, X, Y, DB>
220where
221    X: Ranged,
222    Y: Ranged,
223    DB: DrawingBackend,
224{
225    /// Set all the tick mark to the same size
226    /// `value`: The new size
227    pub fn set_all_tick_mark_size<S: SizeDesc>(&mut self, value: S) -> &mut Self {
228        let size = value.in_pixels(&self.parent_size);
229        self.x_tick_size = [size, size];
230        self.y_tick_size = [size, size];
231        self
232    }
233
234    /// Set the tick mark size on the axes. When this is set to negative, the axis value label will
235    /// become inward.
236    ///
237    /// - `pos`: The which label area we want to set
238    /// - `value`: The size specification
239    pub fn set_tick_mark_size<S: SizeDesc>(
240        &mut self,
241        pos: LabelAreaPosition,
242        value: S,
243    ) -> &mut Self {
244        *match pos {
245            LabelAreaPosition::Top => &mut self.x_tick_size[0],
246            LabelAreaPosition::Bottom => &mut self.x_tick_size[1],
247            LabelAreaPosition::Left => &mut self.y_tick_size[0],
248            LabelAreaPosition::Right => &mut self.y_tick_size[1],
249        } = value.in_pixels(&self.parent_size);
250        self
251    }
252
253    /// The offset of x labels. This is used when we want to place the label in the middle of
254    /// the grid. This is used to adjust label position for histograms, but since plotters 0.3, this
255    /// use case is deprecated, see [CentricDiscreteRanged coord decorator](../coord/trait.IntoCentric.html) for more details
256    /// - `value`: The offset in pixel
257    pub fn x_label_offset<S: SizeDesc>(&mut self, value: S) -> &mut Self {
258        self.x_label_offset = value.in_pixels(&self.parent_size);
259        self
260    }
261
262    /// The offset of y labels. This is used when we want to place the label in the middle of
263    /// the grid. This is used to adjust label position for histograms, but since plotters 0.3, this
264    /// use case is deprecated, see [CentricDiscreteRanged coord decorator](../coord/trait.IntoCentric.html) for more details
265    /// - `value`: The offset in pixel
266    pub fn y_label_offset<S: SizeDesc>(&mut self, value: S) -> &mut Self {
267        self.y_label_offset = value.in_pixels(&self.parent_size);
268        self
269    }
270
271    /// Disable the mesh for the x axis.
272    pub fn disable_x_mesh(&mut self) -> &mut Self {
273        self.draw_x_mesh = false;
274        self
275    }
276
277    /// Disable the mesh for the y axis
278    pub fn disable_y_mesh(&mut self) -> &mut Self {
279        self.draw_y_mesh = false;
280        self
281    }
282
283    /// Disable drawing the X axis
284    pub fn disable_x_axis(&mut self) -> &mut Self {
285        self.draw_x_axis = false;
286        self
287    }
288
289    /// Disable drawing the Y axis
290    pub fn disable_y_axis(&mut self) -> &mut Self {
291        self.draw_y_axis = false;
292        self
293    }
294
295    /// Disable drawing all meshes
296    pub fn disable_mesh(&mut self) -> &mut Self {
297        self.disable_x_mesh().disable_y_mesh()
298    }
299
300    /// Disable drawing all axes
301    pub fn disable_axes(&mut self) -> &mut Self {
302        self.disable_x_axis().disable_y_axis()
303    }
304
305    /// Set the style definition for the axis
306    /// - `style`: The style for the axis
307    pub fn axis_style<T: Into<ShapeStyle>>(&mut self, style: T) -> &mut Self {
308        self.axis_style = Some(style.into());
309        self
310    }
311    /// Set how many labels for the X axis at most
312    /// - `value`: The maximum desired number of labels in the X axis
313    pub fn x_labels(&mut self, value: usize) -> &mut Self {
314        self.n_x_labels = value;
315        self
316    }
317
318    /// Set how many label for the Y axis at most
319    /// - `value`: The maximum desired number of labels in the Y axis
320    pub fn y_labels(&mut self, value: usize) -> &mut Self {
321        self.n_y_labels = value;
322        self
323    }
324
325    /// Set the style for the coarse grind grid
326    /// - `style`: This is the coarse grind grid style
327    pub fn bold_line_style<T: Into<ShapeStyle>>(&mut self, style: T) -> &mut Self {
328        self.bold_line_style = Some(style.into());
329        self
330    }
331
332    /// Set the style for the fine grind grid
333    /// - `style`: The fine grind grid style
334    pub fn light_line_style<T: Into<ShapeStyle>>(&mut self, style: T) -> &mut Self {
335        self.light_line_style = Some(style.into());
336        self
337    }
338
339    /// Set the style of the label text
340    /// - `style`: The text style that would be applied to the labels
341    pub fn label_style<T: IntoTextStyle<'b>>(&mut self, style: T) -> &mut Self {
342        let style = style.into_text_style(&self.parent_size);
343        self.x_label_style = Some(style.clone());
344        self.y_label_style = Some(style);
345        self
346    }
347
348    /// Set the style of the label X axis text
349    /// - `style`: The text style that would be applied to the labels
350    pub fn x_label_style<T: IntoTextStyle<'b>>(&mut self, style: T) -> &mut Self {
351        self.x_label_style = Some(style.into_text_style(&self.parent_size));
352        self
353    }
354
355    /// Set the style of the label Y axis text
356    /// - `style`: The text style that would be applied to the labels
357    pub fn y_label_style<T: IntoTextStyle<'b>>(&mut self, style: T) -> &mut Self {
358        self.y_label_style = Some(style.into_text_style(&self.parent_size));
359        self
360    }
361
362    /// Set the formatter function for the X label text
363    /// - `fmt`: The formatter function
364    pub fn x_label_formatter(&mut self, fmt: &'b dyn Fn(&X::ValueType) -> String) -> &mut Self {
365        self.format_x = fmt;
366        self
367    }
368
369    /// Set the formatter function for the Y label text
370    /// - `fmt`: The formatter function
371    pub fn y_label_formatter(&mut self, fmt: &'b dyn Fn(&Y::ValueType) -> String) -> &mut Self {
372        self.format_y = fmt;
373        self
374    }
375
376    /// Set the axis description's style. If not given, use label style instead.
377    /// - `style`: The text style that would be applied to descriptions
378    pub fn axis_desc_style<T: IntoTextStyle<'b>>(&mut self, style: T) -> &mut Self {
379        self.axis_desc_style = Some(style.into_text_style(&self.parent_size));
380        self
381    }
382
383    /// Set the X axis's description
384    /// - `desc`: The description of the X axis
385    pub fn x_desc<T: Into<String>>(&mut self, desc: T) -> &mut Self {
386        self.x_desc = Some(desc.into());
387        self
388    }
389
390    /// Set the Y axis's description
391    /// - `desc`: The description of the Y axis
392    pub fn y_desc<T: Into<String>>(&mut self, desc: T) -> &mut Self {
393        self.y_desc = Some(desc.into());
394        self
395    }
396
397    /// Draw the configured mesh on the target plot
398    pub fn draw(&mut self) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> {
399        let target = self.target.take().unwrap();
400
401        let default_mesh_color_1 = RGBColor(0, 0, 0).mix(0.2);
402        let default_mesh_color_2 = RGBColor(0, 0, 0).mix(0.1);
403        let default_axis_color = RGBColor(0, 0, 0);
404        let default_label_font = FontDesc::new(
405            FontFamily::SansSerif,
406            f64::from((12i32).percent().max(12).in_pixels(&self.parent_size)),
407            FontStyle::Normal,
408        );
409
410        let bold_style = self
411            .bold_line_style
412            .clone()
413            .unwrap_or_else(|| (&default_mesh_color_1).into());
414        let light_style = self
415            .light_line_style
416            .clone()
417            .unwrap_or_else(|| (&default_mesh_color_2).into());
418        let axis_style = self
419            .axis_style
420            .clone()
421            .unwrap_or_else(|| (&default_axis_color).into());
422
423        let x_label_style = self
424            .x_label_style
425            .clone()
426            .unwrap_or_else(|| default_label_font.clone().into());
427
428        let y_label_style = self
429            .y_label_style
430            .clone()
431            .unwrap_or_else(|| default_label_font.into());
432
433        let axis_desc_style = self
434            .axis_desc_style
435            .clone()
436            .unwrap_or_else(|| x_label_style.clone());
437
438        target.draw_mesh(
439            (
440                LightPoints::new(self.n_y_labels, self.n_y_labels * 10),
441                LightPoints::new(self.n_x_labels, self.n_x_labels * 10),
442            ),
443            &light_style,
444            &x_label_style,
445            &y_label_style,
446            |_| None,
447            self.draw_x_mesh,
448            self.draw_y_mesh,
449            self.x_label_offset,
450            self.y_label_offset,
451            false,
452            false,
453            &axis_style,
454            &axis_desc_style,
455            self.x_desc.clone(),
456            self.y_desc.clone(),
457            self.x_tick_size,
458            self.y_tick_size,
459        )?;
460
461        target.draw_mesh(
462            (BoldPoints(self.n_y_labels), BoldPoints(self.n_x_labels)),
463            &bold_style,
464            &x_label_style,
465            &y_label_style,
466            |m| match m {
467                MeshLine::XMesh(_, _, v) => {
468                    if self.draw_x_axis {
469                        Some((self.format_x)(v))
470                    } else {
471                        None
472                    }
473                }
474                MeshLine::YMesh(_, _, v) => {
475                    if self.draw_y_axis {
476                        Some((self.format_y)(v))
477                    } else {
478                        None
479                    }
480                }
481            },
482            self.draw_x_mesh,
483            self.draw_y_mesh,
484            self.x_label_offset,
485            self.y_label_offset,
486            self.draw_x_axis,
487            self.draw_y_axis,
488            &axis_style,
489            &axis_desc_style,
490            None,
491            None,
492            self.x_tick_size,
493            self.y_tick_size,
494        )
495    }
496}