• 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(
123                 vec![(x0, y0), (x1, y1)],
124                 *axis_style,
125             ))?;
126         }
127 
128         Ok(axis_range)
129     }
130 
131     // TODO: consider make this function less complicated
132     #[allow(clippy::too_many_arguments)]
133     #[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>>134     fn draw_axis_and_labels(
135         &self,
136         area: Option<&DrawingArea<DB, Shift>>,
137         axis_style: Option<&ShapeStyle>,
138         labels: &[(i32, String)],
139         label_style: &TextStyle,
140         label_offset: i32,
141         orientation: (i16, i16),
142         axis_desc: Option<(&str, &TextStyle)>,
143         tick_size: i32,
144     ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> {
145         let area = if let Some(target) = area {
146             target
147         } else {
148             return Ok(());
149         };
150 
151         let (x0, y0) = self.drawing_area.get_base_pixel();
152         let (tw, th) = area.dim_in_pixel();
153 
154         /* This is the minimal distance from the axis to the box of the labels */
155         let label_dist = tick_size.abs() * 2;
156 
157         /* Draw the axis and get the axis range so that we can do further label
158          * and tick mark drawing */
159         let axis_range = self.draw_axis(area, axis_style, orientation, tick_size < 0)?;
160 
161         /* To make the right label area looks nice, it's a little bit tricky, since for a that is
162          * very long, we actually prefer left alignment instead of right alignment.
163          * Otherwise, the right alignment looks better. So we estimate the max and min label width
164          * So that we are able decide if we should apply right alignment for the text. */
165         let label_width: Vec<_> = labels
166             .iter()
167             .map(|(_, text)| {
168                 if orientation.0 > 0 && orientation.1 == 0 && tick_size >= 0 {
169                     self.drawing_area
170                         .estimate_text_size(text, label_style)
171                         .map(|(w, _)| w)
172                         .unwrap_or(0) as i32
173                 } else {
174                     // Don't ever do the layout estimationfor the drawing area that is either not
175                     // the right one or the tick mark is inward.
176                     0
177                 }
178             })
179             .collect();
180 
181         let min_width = *label_width.iter().min().unwrap_or(&1).max(&1);
182         let max_width = *label_width
183             .iter()
184             .filter(|&&x| x < min_width * 2)
185             .max()
186             .unwrap_or(&min_width);
187         let right_align_width = (min_width * 2).min(max_width);
188 
189         /* Then we need to draw the tick mark and the label */
190         for ((p, t), w) in labels.iter().zip(label_width.into_iter()) {
191             /* Make sure we are actually in the visible range */
192             let rp = if orientation.0 == 0 { *p - x0 } else { *p - y0 };
193 
194             if rp < axis_range.start.min(axis_range.end)
195                 || axis_range.end.max(axis_range.start) < rp
196             {
197                 continue;
198             }
199 
200             let (cx, cy, h_pos, v_pos) = if tick_size >= 0 {
201                 match orientation {
202                     // Right
203                     (dx, dy) if dx > 0 && dy == 0 => {
204                         if w >= right_align_width {
205                             (label_dist, *p - y0, HPos::Left, VPos::Center)
206                         } else {
207                             (
208                                 label_dist + right_align_width,
209                                 *p - y0,
210                                 HPos::Right,
211                                 VPos::Center,
212                             )
213                         }
214                     }
215                     // Left
216                     (dx, dy) if dx < 0 && dy == 0 => {
217                         (tw as i32 - label_dist, *p - y0, HPos::Right, VPos::Center)
218                     }
219                     // Bottom
220                     (dx, dy) if dx == 0 && dy > 0 => (*p - x0, label_dist, HPos::Center, VPos::Top),
221                     // Top
222                     (dx, dy) if dx == 0 && dy < 0 => {
223                         (*p - x0, th as i32 - label_dist, HPos::Center, VPos::Bottom)
224                     }
225                     _ => panic!("Bug: Invalid orientation specification"),
226                 }
227             } else {
228                 match orientation {
229                     // Right
230                     (dx, dy) if dx > 0 && dy == 0 => {
231                         (tw as i32 - label_dist, *p - y0, HPos::Right, VPos::Center)
232                     }
233                     // Left
234                     (dx, dy) if dx < 0 && dy == 0 => {
235                         (label_dist, *p - y0, HPos::Left, VPos::Center)
236                     }
237                     // Bottom
238                     (dx, dy) if dx == 0 && dy > 0 => {
239                         (*p - x0, th as i32 - label_dist, HPos::Center, VPos::Bottom)
240                     }
241                     // Top
242                     (dx, dy) if dx == 0 && dy < 0 => (*p - x0, label_dist, HPos::Center, VPos::Top),
243                     _ => panic!("Bug: Invalid orientation specification"),
244                 }
245             };
246 
247             let (text_x, text_y) = if orientation.0 == 0 {
248                 (cx + label_offset, cy)
249             } else {
250                 (cx, cy + label_offset)
251             };
252 
253             let label_style = &label_style.pos(Pos::new(h_pos, v_pos));
254             area.draw_text(t, label_style, (text_x, text_y))?;
255 
256             if tick_size != 0 {
257                 if let Some(style) = axis_style {
258                     let xmax = tw as i32 - 1;
259                     let ymax = th as i32 - 1;
260                     let (kx0, ky0, kx1, ky1) = if tick_size > 0 {
261                         match orientation {
262                             (dx, dy) if dx > 0 && dy == 0 => (0, *p - y0, tick_size, *p - y0),
263                             (dx, dy) if dx < 0 && dy == 0 => {
264                                 (xmax - tick_size, *p - y0, xmax, *p - y0)
265                             }
266                             (dx, dy) if dx == 0 && dy > 0 => (*p - x0, 0, *p - x0, tick_size),
267                             (dx, dy) if dx == 0 && dy < 0 => {
268                                 (*p - x0, ymax - tick_size, *p - x0, ymax)
269                             }
270                             _ => panic!("Bug: Invalid orientation specification"),
271                         }
272                     } else {
273                         match orientation {
274                             (dx, dy) if dx > 0 && dy == 0 => {
275                                 (xmax, *p - y0, xmax + tick_size, *p - y0)
276                             }
277                             (dx, dy) if dx < 0 && dy == 0 => (0, *p - y0, -tick_size, *p - y0),
278                             (dx, dy) if dx == 0 && dy > 0 => {
279                                 (*p - x0, ymax, *p - x0, ymax + tick_size)
280                             }
281                             (dx, dy) if dx == 0 && dy < 0 => (*p - x0, 0, *p - x0, -tick_size),
282                             _ => panic!("Bug: Invalid orientation specification"),
283                         }
284                     };
285                     let line = PathElement::new(vec![(kx0, ky0), (kx1, ky1)], *style);
286                     area.draw(&line)?;
287                 }
288             }
289         }
290 
291         if let Some((text, style)) = axis_desc {
292             let actual_style = if orientation.0 == 0 {
293                 style.clone()
294             } else if orientation.0 == -1 {
295                 style.transform(FontTransform::Rotate270)
296             } else {
297                 style.transform(FontTransform::Rotate90)
298             };
299 
300             let (x0, y0, h_pos, v_pos) = match orientation {
301                 // Right
302                 (dx, dy) if dx > 0 && dy == 0 => (tw, th / 2, HPos::Center, VPos::Top),
303                 // Left
304                 (dx, dy) if dx < 0 && dy == 0 => (0, th / 2, HPos::Center, VPos::Top),
305                 // Bottom
306                 (dx, dy) if dx == 0 && dy > 0 => (tw / 2, th, HPos::Center, VPos::Bottom),
307                 // Top
308                 (dx, dy) if dx == 0 && dy < 0 => (tw / 2, 0, HPos::Center, VPos::Top),
309                 _ => panic!("Bug: Invalid orientation specification"),
310             };
311 
312             let actual_style = &actual_style.pos(Pos::new(h_pos, v_pos));
313             area.draw_text(text, actual_style, (x0 as i32, y0 as i32))?;
314         }
315 
316         Ok(())
317     }
318 
319     #[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>,320     pub(crate) fn draw_mesh<FmtLabel, YH: KeyPointHint, XH: KeyPointHint>(
321         &mut self,
322         (r, c): (YH, XH),
323         mesh_line_style: &ShapeStyle,
324         x_label_style: &TextStyle,
325         y_label_style: &TextStyle,
326         fmt_label: FmtLabel,
327         x_mesh: bool,
328         y_mesh: bool,
329         x_label_offset: i32,
330         y_label_offset: i32,
331         x_axis: bool,
332         y_axis: bool,
333         axis_style: &ShapeStyle,
334         axis_desc_style: &TextStyle,
335         x_desc: Option<String>,
336         y_desc: Option<String>,
337         x_tick_size: [i32; 2],
338         y_tick_size: [i32; 2],
339     ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>>
340     where
341         FmtLabel: FnMut(&X, &Y, &MeshLine<X, Y>) -> Option<String>,
342     {
343         let (x_labels, y_labels) =
344             self.draw_mesh_lines((r, c), (x_mesh, y_mesh), mesh_line_style, fmt_label)?;
345 
346         for idx in 0..2 {
347             self.draw_axis_and_labels(
348                 self.x_label_area[idx].as_ref(),
349                 if x_axis { Some(axis_style) } else { None },
350                 &x_labels[..],
351                 x_label_style,
352                 x_label_offset,
353                 (0, -1 + idx as i16 * 2),
354                 x_desc.as_ref().map(|desc| (&desc[..], axis_desc_style)),
355                 x_tick_size[idx],
356             )?;
357 
358             self.draw_axis_and_labels(
359                 self.y_label_area[idx].as_ref(),
360                 if y_axis { Some(axis_style) } else { None },
361                 &y_labels[..],
362                 y_label_style,
363                 y_label_offset,
364                 (-1 + idx as i16 * 2, 0),
365                 y_desc.as_ref().map(|desc| (&desc[..], axis_desc_style)),
366                 y_tick_size[idx],
367             )?;
368         }
369 
370         Ok(())
371     }
372 }
373