• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use std::ops::Range;
2 
3 use plotters_backend::DrawingBackend;
4 
5 use crate::chart::ChartContext;
6 use crate::coord::{
7     cartesian::{Cartesian2d, MeshLine},
8     ranged1d::{KeyPointHint, Ranged},
9     Shift,
10 };
11 use crate::drawing::{DrawingArea, DrawingAreaErrorKind};
12 use crate::element::PathElement;
13 use crate::style::{
14     text_anchor::{HPos, Pos, VPos},
15     FontTransform, ShapeStyle, TextStyle,
16 };
17 
18 impl<'a, DB: DrawingBackend, X: Ranged, Y: Ranged> ChartContext<'a, DB, Cartesian2d<X, Y>> {
19     /// The actual function that draws the mesh lines.
20     /// It also returns the label that suppose to be there.
21     #[allow(clippy::type_complexity)]
draw_mesh_lines<FmtLabel, YH: KeyPointHint, XH: KeyPointHint>( &mut self, (r, c): (YH, XH), (x_mesh, y_mesh): (bool, bool), mesh_line_style: &ShapeStyle, mut fmt_label: FmtLabel, ) -> Result<(Vec<(i32, String)>, Vec<(i32, String)>), DrawingAreaErrorKind<DB::ErrorType>> where FmtLabel: FnMut(&X, &Y, &MeshLine<X, Y>) -> Option<String>,22     fn draw_mesh_lines<FmtLabel, YH: KeyPointHint, XH: KeyPointHint>(
23         &mut self,
24         (r, c): (YH, XH),
25         (x_mesh, y_mesh): (bool, bool),
26         mesh_line_style: &ShapeStyle,
27         mut fmt_label: FmtLabel,
28     ) -> Result<(Vec<(i32, String)>, Vec<(i32, String)>), DrawingAreaErrorKind<DB::ErrorType>>
29     where
30         FmtLabel: FnMut(&X, &Y, &MeshLine<X, Y>) -> Option<String>,
31     {
32         let mut x_labels = vec![];
33         let mut y_labels = vec![];
34         let xr = self.drawing_area.as_coord_spec().x_spec();
35         let yr = self.drawing_area.as_coord_spec().y_spec();
36         self.drawing_area.draw_mesh(
37             |b, l| {
38                 let draw = match l {
39                     MeshLine::XMesh((x, _), _, _) => {
40                         if let Some(label_text) = fmt_label(xr, yr, &l) {
41                             x_labels.push((x, label_text));
42                         }
43                         x_mesh
44                     }
45                     MeshLine::YMesh((_, y), _, _) => {
46                         if let Some(label_text) = fmt_label(xr, yr, &l) {
47                             y_labels.push((y, label_text));
48                         }
49                         y_mesh
50                     }
51                 };
52                 if draw {
53                     l.draw(b, mesh_line_style)
54                 } else {
55                     Ok(())
56                 }
57             },
58             r,
59             c,
60         )?;
61         Ok((x_labels, y_labels))
62     }
63 
draw_axis( &self, area: &DrawingArea<DB, Shift>, axis_style: Option<&ShapeStyle>, orientation: (i16, i16), inward_labels: bool, ) -> Result<Range<i32>, DrawingAreaErrorKind<DB::ErrorType>>64     fn draw_axis(
65         &self,
66         area: &DrawingArea<DB, Shift>,
67         axis_style: Option<&ShapeStyle>,
68         orientation: (i16, i16),
69         inward_labels: bool,
70     ) -> Result<Range<i32>, DrawingAreaErrorKind<DB::ErrorType>> {
71         let (x0, y0) = self.drawing_area.get_base_pixel();
72         let (tw, th) = area.dim_in_pixel();
73 
74         let mut axis_range = if orientation.0 == 0 {
75             self.drawing_area.get_x_axis_pixel_range()
76         } else {
77             self.drawing_area.get_y_axis_pixel_range()
78         };
79 
80         // At this point, the coordinate system tells us the pixel range after the translation.
81         // However, we need to use the logic coordinate system for drawing.
82         if orientation.0 == 0 {
83             axis_range.start -= x0;
84             axis_range.end -= x0;
85         } else {
86             axis_range.start -= y0;
87             axis_range.end -= y0;
88         }
89 
90         if let Some(axis_style) = axis_style {
91             let mut x0 = if orientation.0 > 0 { 0 } else { tw as i32 - 1 };
92             let mut y0 = if orientation.1 > 0 { 0 } else { th as i32 - 1 };
93             let mut x1 = if orientation.0 >= 0 { 0 } else { tw as i32 - 1 };
94             let mut y1 = if orientation.1 >= 0 { 0 } else { th as i32 - 1 };
95 
96             if inward_labels {
97                 if orientation.0 == 0 {
98                     if y0 == 0 {
99                         y0 = th as i32 - 1;
100                         y1 = th as i32 - 1;
101                     } else {
102                         y0 = 0;
103                         y1 = 0;
104                     }
105                 } else if x0 == 0 {
106                     x0 = tw as i32 - 1;
107                     x1 = tw as i32 - 1;
108                 } else {
109                     x0 = 0;
110                     x1 = 0;
111                 }
112             }
113 
114             if orientation.0 == 0 {
115                 x0 = axis_range.start;
116                 x1 = axis_range.end;
117             } else {
118                 y0 = axis_range.start;
119                 y1 = axis_range.end;
120             }
121 
122             area.draw(&PathElement::new(vec![(x0, y0), (x1, y1)], *axis_style))?;
123         }
124 
125         Ok(axis_range)
126     }
127 
128     // TODO: consider make this function less complicated
129     #[allow(clippy::too_many_arguments)]
130     #[allow(clippy::cognitive_complexity)]
draw_axis_and_labels( &self, area: Option<&DrawingArea<DB, Shift>>, axis_style: Option<&ShapeStyle>, labels: &[(i32, String)], label_style: &TextStyle, label_offset: i32, orientation: (i16, i16), axis_desc: Option<(&str, &TextStyle)>, tick_size: i32, ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>>131     fn draw_axis_and_labels(
132         &self,
133         area: Option<&DrawingArea<DB, Shift>>,
134         axis_style: Option<&ShapeStyle>,
135         labels: &[(i32, String)],
136         label_style: &TextStyle,
137         label_offset: i32,
138         orientation: (i16, i16),
139         axis_desc: Option<(&str, &TextStyle)>,
140         tick_size: i32,
141     ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> {
142         let area = if let Some(target) = area {
143             target
144         } else {
145             return Ok(());
146         };
147 
148         let (x0, y0) = self.drawing_area.get_base_pixel();
149         let (tw, th) = area.dim_in_pixel();
150 
151         /* This is the minimal distance from the axis to the box of the labels */
152         let label_dist = tick_size.abs() * 2;
153 
154         /* Draw the axis and get the axis range so that we can do further label
155          * and tick mark drawing */
156         let axis_range = self.draw_axis(area, axis_style, orientation, tick_size < 0)?;
157 
158         /* To make the right label area looks nice, it's a little bit tricky, since for a that is
159          * very long, we actually prefer left alignment instead of right alignment.
160          * Otherwise, the right alignment looks better. So we estimate the max and min label width
161          * So that we are able decide if we should apply right alignment for the text. */
162         let label_width: Vec<_> = labels
163             .iter()
164             .map(|(_, text)| {
165                 if orientation.0 > 0 && orientation.1 == 0 && tick_size >= 0 {
166                     self.drawing_area
167                         .estimate_text_size(text, label_style)
168                         .map(|(w, _)| w)
169                         .unwrap_or(0) as i32
170                 } else {
171                     // Don't ever do the layout estimationfor the drawing area that is either not
172                     // the right one or the tick mark is inward.
173                     0
174                 }
175             })
176             .collect();
177 
178         let min_width = *label_width.iter().min().unwrap_or(&1).max(&1);
179         let max_width = *label_width
180             .iter()
181             .filter(|&&x| x < min_width * 2)
182             .max()
183             .unwrap_or(&min_width);
184         let right_align_width = (min_width * 2).min(max_width);
185 
186         /* Then we need to draw the tick mark and the label */
187         for ((p, t), w) in labels.iter().zip(label_width.into_iter()) {
188             /* Make sure we are actually in the visible range */
189             let rp = if orientation.0 == 0 { *p - x0 } else { *p - y0 };
190 
191             if rp < axis_range.start.min(axis_range.end)
192                 || axis_range.end.max(axis_range.start) < rp
193             {
194                 continue;
195             }
196 
197             let (cx, cy, h_pos, v_pos) = if tick_size >= 0 {
198                 match orientation {
199                     // Right
200                     (dx, dy) if dx > 0 && dy == 0 => {
201                         if w >= right_align_width {
202                             (label_dist, *p - y0, HPos::Left, VPos::Center)
203                         } else {
204                             (
205                                 label_dist + right_align_width,
206                                 *p - y0,
207                                 HPos::Right,
208                                 VPos::Center,
209                             )
210                         }
211                     }
212                     // Left
213                     (dx, dy) if dx < 0 && dy == 0 => {
214                         (tw as i32 - label_dist, *p - y0, HPos::Right, VPos::Center)
215                     }
216                     // Bottom
217                     (dx, dy) if dx == 0 && dy > 0 => (*p - x0, label_dist, HPos::Center, VPos::Top),
218                     // Top
219                     (dx, dy) if dx == 0 && dy < 0 => {
220                         (*p - x0, th as i32 - label_dist, HPos::Center, VPos::Bottom)
221                     }
222                     _ => panic!("Bug: Invalid orientation specification"),
223                 }
224             } else {
225                 match orientation {
226                     // Right
227                     (dx, dy) if dx > 0 && dy == 0 => {
228                         (tw as i32 - label_dist, *p - y0, HPos::Right, VPos::Center)
229                     }
230                     // Left
231                     (dx, dy) if dx < 0 && dy == 0 => {
232                         (label_dist, *p - y0, HPos::Left, VPos::Center)
233                     }
234                     // Bottom
235                     (dx, dy) if dx == 0 && dy > 0 => {
236                         (*p - x0, th as i32 - label_dist, HPos::Center, VPos::Bottom)
237                     }
238                     // Top
239                     (dx, dy) if dx == 0 && dy < 0 => (*p - x0, label_dist, HPos::Center, VPos::Top),
240                     _ => panic!("Bug: Invalid orientation specification"),
241                 }
242             };
243 
244             let (text_x, text_y) = if orientation.0 == 0 {
245                 (cx + label_offset, cy)
246             } else {
247                 (cx, cy + label_offset)
248             };
249 
250             let label_style = &label_style.pos(Pos::new(h_pos, v_pos));
251             area.draw_text(t, label_style, (text_x, text_y))?;
252 
253             if tick_size != 0 {
254                 if let Some(style) = axis_style {
255                     let xmax = tw as i32 - 1;
256                     let ymax = th as i32 - 1;
257                     let (kx0, ky0, kx1, ky1) = if tick_size > 0 {
258                         match orientation {
259                             (dx, dy) if dx > 0 && dy == 0 => (0, *p - y0, tick_size, *p - y0),
260                             (dx, dy) if dx < 0 && dy == 0 => {
261                                 (xmax - tick_size, *p - y0, xmax, *p - y0)
262                             }
263                             (dx, dy) if dx == 0 && dy > 0 => (*p - x0, 0, *p - x0, tick_size),
264                             (dx, dy) if dx == 0 && dy < 0 => {
265                                 (*p - x0, ymax - tick_size, *p - x0, ymax)
266                             }
267                             _ => panic!("Bug: Invalid orientation specification"),
268                         }
269                     } else {
270                         match orientation {
271                             (dx, dy) if dx > 0 && dy == 0 => {
272                                 (xmax, *p - y0, xmax + tick_size, *p - y0)
273                             }
274                             (dx, dy) if dx < 0 && dy == 0 => (0, *p - y0, -tick_size, *p - y0),
275                             (dx, dy) if dx == 0 && dy > 0 => {
276                                 (*p - x0, ymax, *p - x0, ymax + tick_size)
277                             }
278                             (dx, dy) if dx == 0 && dy < 0 => (*p - x0, 0, *p - x0, -tick_size),
279                             _ => panic!("Bug: Invalid orientation specification"),
280                         }
281                     };
282                     let line = PathElement::new(vec![(kx0, ky0), (kx1, ky1)], *style);
283                     area.draw(&line)?;
284                 }
285             }
286         }
287 
288         if let Some((text, style)) = axis_desc {
289             let actual_style = if orientation.0 == 0 {
290                 style.clone()
291             } else if orientation.0 == -1 {
292                 style.transform(FontTransform::Rotate270)
293             } else {
294                 style.transform(FontTransform::Rotate90)
295             };
296 
297             let (x0, y0, h_pos, v_pos) = match orientation {
298                 // Right
299                 (dx, dy) if dx > 0 && dy == 0 => (tw, th / 2, HPos::Center, VPos::Top),
300                 // Left
301                 (dx, dy) if dx < 0 && dy == 0 => (0, th / 2, HPos::Center, VPos::Top),
302                 // Bottom
303                 (dx, dy) if dx == 0 && dy > 0 => (tw / 2, th, HPos::Center, VPos::Bottom),
304                 // Top
305                 (dx, dy) if dx == 0 && dy < 0 => (tw / 2, 0, HPos::Center, VPos::Top),
306                 _ => panic!("Bug: Invalid orientation specification"),
307             };
308 
309             let actual_style = &actual_style.pos(Pos::new(h_pos, v_pos));
310             area.draw_text(text, actual_style, (x0 as i32, y0 as i32))?;
311         }
312 
313         Ok(())
314     }
315 
316     #[allow(clippy::too_many_arguments)]
draw_mesh<FmtLabel, YH: KeyPointHint, XH: KeyPointHint>( &mut self, (r, c): (YH, XH), mesh_line_style: &ShapeStyle, x_label_style: &TextStyle, y_label_style: &TextStyle, fmt_label: FmtLabel, x_mesh: bool, y_mesh: bool, x_label_offset: i32, y_label_offset: i32, x_axis: bool, y_axis: bool, axis_style: &ShapeStyle, axis_desc_style: &TextStyle, x_desc: Option<String>, y_desc: Option<String>, x_tick_size: [i32; 2], y_tick_size: [i32; 2], ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> where FmtLabel: FnMut(&X, &Y, &MeshLine<X, Y>) -> Option<String>,317     pub(crate) fn draw_mesh<FmtLabel, YH: KeyPointHint, XH: KeyPointHint>(
318         &mut self,
319         (r, c): (YH, XH),
320         mesh_line_style: &ShapeStyle,
321         x_label_style: &TextStyle,
322         y_label_style: &TextStyle,
323         fmt_label: FmtLabel,
324         x_mesh: bool,
325         y_mesh: bool,
326         x_label_offset: i32,
327         y_label_offset: i32,
328         x_axis: bool,
329         y_axis: bool,
330         axis_style: &ShapeStyle,
331         axis_desc_style: &TextStyle,
332         x_desc: Option<String>,
333         y_desc: Option<String>,
334         x_tick_size: [i32; 2],
335         y_tick_size: [i32; 2],
336     ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>>
337     where
338         FmtLabel: FnMut(&X, &Y, &MeshLine<X, Y>) -> Option<String>,
339     {
340         let (x_labels, y_labels) =
341             self.draw_mesh_lines((r, c), (x_mesh, y_mesh), mesh_line_style, fmt_label)?;
342 
343         for idx in 0..2 {
344             self.draw_axis_and_labels(
345                 self.x_label_area[idx].as_ref(),
346                 if x_axis { Some(axis_style) } else { None },
347                 &x_labels[..],
348                 x_label_style,
349                 x_label_offset,
350                 (0, -1 + idx as i16 * 2),
351                 x_desc.as_ref().map(|desc| (&desc[..], axis_desc_style)),
352                 x_tick_size[idx],
353             )?;
354 
355             self.draw_axis_and_labels(
356                 self.y_label_area[idx].as_ref(),
357                 if y_axis { Some(axis_style) } else { None },
358                 &y_labels[..],
359                 y_label_style,
360                 y_label_offset,
361                 (-1 + idx as i16 * 2, 0),
362                 y_desc.as_ref().map(|desc| (&desc[..], axis_desc_style)),
363                 y_tick_size[idx],
364             )?;
365         }
366 
367         Ok(())
368     }
369 }
370