1 use std::borrow::Borrow;
2 use std::ops::Range;
3 
4 use super::axes3d::Axes3dStyle;
5 use super::{DualCoordChartContext, MeshStyle, SeriesAnno, SeriesLabelStyle};
6 
7 use crate::coord::cartesian::{Cartesian2d, Cartesian3d, MeshLine};
8 use crate::coord::ranged1d::{AsRangedCoord, KeyPointHint, Ranged, ValueFormatter};
9 use crate::coord::ranged3d::{ProjectionMatrix, ProjectionMatrixBuilder};
10 use crate::coord::{CoordTranslate, ReverseCoordTranslate, Shift};
11 
12 use crate::drawing::{DrawingArea, DrawingAreaErrorKind};
13 use crate::element::{Drawable, EmptyElement, PathElement, PointCollection, Polygon, Text};
14 use crate::style::text_anchor::{HPos, Pos, VPos};
15 use crate::style::{ShapeStyle, TextStyle};
16 
17 use plotters_backend::{BackendCoord, DrawingBackend, FontTransform};
18 
19 /// The context of the chart. This is the core object of Plotters.
20 /// Any plot/chart is abstracted as this type, and any data series can be placed to the chart
21 /// context.
22 ///
23 /// - To draw a series on a chart context, use [ChartContext::draw_series](struct.ChartContext.html#method.draw_series)
24 /// - To draw a single element to the chart, you may want to use [ChartContext::plotting_area](struct.ChartContext.html#method.plotting_area)
25 ///
26 pub struct ChartContext<'a, DB: DrawingBackend, CT: CoordTranslate> {
27     pub(super) x_label_area: [Option<DrawingArea<DB, Shift>>; 2],
28     pub(super) y_label_area: [Option<DrawingArea<DB, Shift>>; 2],
29     pub(super) drawing_area: DrawingArea<DB, CT>,
30     pub(super) series_anno: Vec<SeriesAnno<'a, DB>>,
31     pub(super) drawing_area_pos: (i32, i32),
32 }
33 
34 impl<'a, DB, XT, YT, X, Y> ChartContext<'a, DB, Cartesian2d<X, Y>>
35 where
36     DB: DrawingBackend,
37     X: Ranged<ValueType = XT> + ValueFormatter<XT>,
38     Y: Ranged<ValueType = YT> + ValueFormatter<YT>,
39 {
is_overlapping_drawing_area( &self, area: Option<&DrawingArea<DB, Shift>>, ) -> bool40     pub(crate) fn is_overlapping_drawing_area(
41         &self,
42         area: Option<&DrawingArea<DB, Shift>>,
43     ) -> bool {
44         if let Some(area) = area {
45             let (x0, y0) = area.get_base_pixel();
46             let (w, h) = area.dim_in_pixel();
47             let (x1, y1) = (x0 + w as i32, y0 + h as i32);
48             let (dx0, dy0) = self.drawing_area.get_base_pixel();
49             let (w, h) = self.drawing_area.dim_in_pixel();
50             let (dx1, dy1) = (dx0 + w as i32, dy0 + h as i32);
51 
52             let (ox0, ox1) = (x0.max(dx0), x1.min(dx1));
53             let (oy0, oy1) = (y0.max(dy0), y1.min(dy1));
54 
55             ox1 > ox0 && oy1 > oy0
56         } else {
57             false
58         }
59     }
60 
61     /// Initialize a mesh configuration object and mesh drawing can be finalized by calling
62     /// the function `MeshStyle::draw`.
configure_mesh(&mut self) -> MeshStyle<'a, '_, X, Y, DB>63     pub fn configure_mesh(&mut self) -> MeshStyle<'a, '_, X, Y, DB> {
64         MeshStyle::new(self)
65     }
66 }
67 
68 impl<'a, DB: DrawingBackend, CT: ReverseCoordTranslate> ChartContext<'a, DB, CT> {
69     /// Convert the chart context into an closure that can be used for coordinate translation
into_coord_trans(self) -> impl Fn(BackendCoord) -> Option<CT::From>70     pub fn into_coord_trans(self) -> impl Fn(BackendCoord) -> Option<CT::From> {
71         let coord_spec = self.drawing_area.into_coord_spec();
72         move |coord| coord_spec.reverse_translate(coord)
73     }
74 }
75 
76 impl<'a, DB: DrawingBackend, CT: CoordTranslate> ChartContext<'a, DB, CT> {
77     /// Configure the styles for drawing series labels in the chart
configure_series_labels<'b>(&'b mut self) -> SeriesLabelStyle<'a, 'b, DB, CT> where DB: 'a,78     pub fn configure_series_labels<'b>(&'b mut self) -> SeriesLabelStyle<'a, 'b, DB, CT>
79     where
80         DB: 'a,
81     {
82         SeriesLabelStyle::new(self)
83     }
84 
85     /// Get a reference of underlying plotting area
plotting_area(&self) -> &DrawingArea<DB, CT>86     pub fn plotting_area(&self) -> &DrawingArea<DB, CT> {
87         &self.drawing_area
88     }
89 
90     /// Cast the reference to a chart context to a reference to underlying coordinate specification.
as_coord_spec(&self) -> &CT91     pub fn as_coord_spec(&self) -> &CT {
92         self.drawing_area.as_coord_spec()
93     }
94 
95     // TODO: All draw_series_impl is overly strict about lifetime, because we don't have stable HKT,
96     //       what we can ensure is for all lifetime 'b the element reference &'b E is a iterator
97     //       of points reference with the same lifetime.
98     //       However, this doesn't work if the coordinate doesn't live longer than the backend,
99     //       this is unnecessarily strict
draw_series_impl<E, R, S>( &mut self, series: S, ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> where for<'b> &'b E: PointCollection<'b, CT::From>, E: Drawable<DB>, R: Borrow<E>, S: IntoIterator<Item = R>,100     pub(super) fn draw_series_impl<E, R, S>(
101         &mut self,
102         series: S,
103     ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>>
104     where
105         for<'b> &'b E: PointCollection<'b, CT::From>,
106         E: Drawable<DB>,
107         R: Borrow<E>,
108         S: IntoIterator<Item = R>,
109     {
110         for element in series {
111             self.drawing_area.draw(element.borrow())?;
112         }
113         Ok(())
114     }
115 
alloc_series_anno(&mut self) -> &mut SeriesAnno<'a, DB>116     pub(super) fn alloc_series_anno(&mut self) -> &mut SeriesAnno<'a, DB> {
117         let idx = self.series_anno.len();
118         self.series_anno.push(SeriesAnno::new());
119         &mut self.series_anno[idx]
120     }
121 
122     /// Draw a data series. A data series in Plotters is abstracted as an iterator of elements
draw_series<E, R, S>( &mut self, series: S, ) -> Result<&mut SeriesAnno<'a, DB>, DrawingAreaErrorKind<DB::ErrorType>> where for<'b> &'b E: PointCollection<'b, CT::From>, E: Drawable<DB>, R: Borrow<E>, S: IntoIterator<Item = R>,123     pub fn draw_series<E, R, S>(
124         &mut self,
125         series: S,
126     ) -> Result<&mut SeriesAnno<'a, DB>, DrawingAreaErrorKind<DB::ErrorType>>
127     where
128         for<'b> &'b E: PointCollection<'b, CT::From>,
129         E: Drawable<DB>,
130         R: Borrow<E>,
131         S: IntoIterator<Item = R>,
132     {
133         self.draw_series_impl(series)?;
134         Ok(self.alloc_series_anno())
135     }
136 }
137 
138 impl<'a, DB: DrawingBackend, X: Ranged, Y: Ranged> ChartContext<'a, DB, Cartesian2d<X, Y>> {
139     /// Get the range of X axis
x_range(&self) -> Range<X::ValueType>140     pub fn x_range(&self) -> Range<X::ValueType> {
141         self.drawing_area.get_x_range()
142     }
143 
144     /// Get range of the Y axis
y_range(&self) -> Range<Y::ValueType>145     pub fn y_range(&self) -> Range<Y::ValueType> {
146         self.drawing_area.get_y_range()
147     }
148 
149     /// Maps the coordinate to the backend coordinate. This is typically used
150     /// with an interactive chart.
backend_coord(&self, coord: &(X::ValueType, Y::ValueType)) -> BackendCoord151     pub fn backend_coord(&self, coord: &(X::ValueType, Y::ValueType)) -> BackendCoord {
152         self.drawing_area.map_coordinate(coord)
153     }
154 
155     /// The actual function that draws the mesh lines.
156     /// It also returns the label that suppose to be there.
157     #[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(&MeshLine<X, Y>) -> Option<String>,158     fn draw_mesh_lines<FmtLabel, YH: KeyPointHint, XH: KeyPointHint>(
159         &mut self,
160         (r, c): (YH, XH),
161         (x_mesh, y_mesh): (bool, bool),
162         mesh_line_style: &ShapeStyle,
163         mut fmt_label: FmtLabel,
164     ) -> Result<(Vec<(i32, String)>, Vec<(i32, String)>), DrawingAreaErrorKind<DB::ErrorType>>
165     where
166         FmtLabel: FnMut(&MeshLine<X, Y>) -> Option<String>,
167     {
168         let mut x_labels = vec![];
169         let mut y_labels = vec![];
170         self.drawing_area.draw_mesh(
171             |b, l| {
172                 let draw;
173                 match l {
174                     MeshLine::XMesh((x, _), _, _) => {
175                         if let Some(label_text) = fmt_label(&l) {
176                             x_labels.push((x, label_text));
177                         }
178                         draw = x_mesh;
179                     }
180                     MeshLine::YMesh((_, y), _, _) => {
181                         if let Some(label_text) = fmt_label(&l) {
182                             y_labels.push((y, label_text));
183                         }
184                         draw = y_mesh;
185                     }
186                 };
187                 if draw {
188                     l.draw(b, mesh_line_style)
189                 } else {
190                     Ok(())
191                 }
192             },
193             r,
194             c,
195         )?;
196         Ok((x_labels, y_labels))
197     }
198 
draw_axis( &self, area: &DrawingArea<DB, Shift>, axis_style: Option<&ShapeStyle>, orientation: (i16, i16), inward_labels: bool, ) -> Result<Range<i32>, DrawingAreaErrorKind<DB::ErrorType>>199     fn draw_axis(
200         &self,
201         area: &DrawingArea<DB, Shift>,
202         axis_style: Option<&ShapeStyle>,
203         orientation: (i16, i16),
204         inward_labels: bool,
205     ) -> Result<Range<i32>, DrawingAreaErrorKind<DB::ErrorType>> {
206         let (x0, y0) = self.drawing_area.get_base_pixel();
207         let (tw, th) = area.dim_in_pixel();
208 
209         let mut axis_range = if orientation.0 == 0 {
210             self.drawing_area.get_x_axis_pixel_range()
211         } else {
212             self.drawing_area.get_y_axis_pixel_range()
213         };
214 
215         /* At this point, the coordinate system tells us the pixel range
216          * after the translation.
217          * However, we need to use the logic coordinate system for drawing. */
218         if orientation.0 == 0 {
219             axis_range.start -= x0;
220             axis_range.end -= x0;
221         } else {
222             axis_range.start -= y0;
223             axis_range.end -= y0;
224         }
225 
226         if let Some(axis_style) = axis_style {
227             let mut x0 = if orientation.0 > 0 { 0 } else { tw as i32 - 1 };
228             let mut y0 = if orientation.1 > 0 { 0 } else { th as i32 - 1 };
229             let mut x1 = if orientation.0 >= 0 { 0 } else { tw as i32 - 1 };
230             let mut y1 = if orientation.1 >= 0 { 0 } else { th as i32 - 1 };
231 
232             if inward_labels {
233                 if orientation.0 == 0 {
234                     if y0 == 0 {
235                         y0 = th as i32 - 1;
236                         y1 = th as i32 - 1;
237                     } else {
238                         y0 = 0;
239                         y1 = 0;
240                     }
241                 } else if x0 == 0 {
242                     x0 = tw as i32 - 1;
243                     x1 = tw as i32 - 1;
244                 } else {
245                     x0 = 0;
246                     x1 = 0;
247                 }
248             }
249 
250             if orientation.0 == 0 {
251                 x0 = axis_range.start;
252                 x1 = axis_range.end;
253             } else {
254                 y0 = axis_range.start;
255                 y1 = axis_range.end;
256             }
257 
258             area.draw(&PathElement::new(
259                 vec![(x0, y0), (x1, y1)],
260                 axis_style.clone(),
261             ))?;
262         }
263 
264         Ok(axis_range)
265     }
266 
267     // TODO: consider make this function less complicated
268     #[allow(clippy::too_many_arguments)]
269     #[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>>270     fn draw_axis_and_labels(
271         &self,
272         area: Option<&DrawingArea<DB, Shift>>,
273         axis_style: Option<&ShapeStyle>,
274         labels: &[(i32, String)],
275         label_style: &TextStyle,
276         label_offset: i32,
277         orientation: (i16, i16),
278         axis_desc: Option<(&str, &TextStyle)>,
279         tick_size: i32,
280     ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> {
281         let area = if let Some(target) = area {
282             target
283         } else {
284             return Ok(());
285         };
286 
287         let (x0, y0) = self.drawing_area.get_base_pixel();
288         let (tw, th) = area.dim_in_pixel();
289 
290         /* This is the minimal distance from the axis to the box of the labels */
291         let label_dist = tick_size.abs() * 2;
292 
293         /* Draw the axis and get the axis range so that we can do further label
294          * and tick mark drawing */
295         let axis_range = self.draw_axis(area, axis_style, orientation, tick_size < 0)?;
296 
297         /* To make the right label area looks nice, it's a little bit tricky, since for a that is
298          * very long, we actually prefer left alignment instead of right alignment.
299          * Otherwise, the right alignment looks better. So we estimate the max and min label width
300          * So that we are able decide if we should apply right alignment for the text. */
301         let label_width: Vec<_> = labels
302             .iter()
303             .map(|(_, text)| {
304                 if orientation.0 > 0 && orientation.1 == 0 && tick_size >= 0 {
305                     let ((x0, _), (x1, _)) = label_style
306                         .font
307                         .layout_box(text)
308                         .unwrap_or(((0, 0), (0, 0)));
309                     x1 - x0
310                 } else {
311                     // Don't ever do the layout estimationfor the drawing area that is either not
312                     // the right one or the tick mark is inward.
313                     0
314                 }
315             })
316             .collect();
317 
318         let min_width = *label_width.iter().min().unwrap_or(&1).max(&1);
319         let max_width = *label_width
320             .iter()
321             .filter(|&&x| x < min_width * 2)
322             .max()
323             .unwrap_or(&min_width);
324         let right_align_width = (min_width * 2).min(max_width);
325 
326         /* Then we need to draw the tick mark and the label */
327         for ((p, t), w) in labels.iter().zip(label_width.into_iter()) {
328             /* Make sure we are actually in the visible range */
329             let rp = if orientation.0 == 0 { *p - x0 } else { *p - y0 };
330 
331             if rp < axis_range.start.min(axis_range.end)
332                 || axis_range.end.max(axis_range.start) < rp
333             {
334                 continue;
335             }
336 
337             let (cx, cy, h_pos, v_pos) = if tick_size >= 0 {
338                 match orientation {
339                     // Right
340                     (dx, dy) if dx > 0 && dy == 0 => {
341                         if w >= right_align_width {
342                             (label_dist, *p - y0, HPos::Left, VPos::Center)
343                         } else {
344                             (
345                                 label_dist + right_align_width,
346                                 *p - y0,
347                                 HPos::Right,
348                                 VPos::Center,
349                             )
350                         }
351                     }
352                     // Left
353                     (dx, dy) if dx < 0 && dy == 0 => {
354                         (tw as i32 - label_dist, *p - y0, HPos::Right, VPos::Center)
355                     }
356                     // Bottom
357                     (dx, dy) if dx == 0 && dy > 0 => (*p - x0, label_dist, HPos::Center, VPos::Top),
358                     // Top
359                     (dx, dy) if dx == 0 && dy < 0 => {
360                         (*p - x0, th as i32 - label_dist, HPos::Center, VPos::Bottom)
361                     }
362                     _ => panic!("Bug: Invalid orientation specification"),
363                 }
364             } else {
365                 match orientation {
366                     // Right
367                     (dx, dy) if dx > 0 && dy == 0 => {
368                         (tw as i32 - label_dist, *p - y0, HPos::Right, VPos::Center)
369                     }
370                     // Left
371                     (dx, dy) if dx < 0 && dy == 0 => {
372                         (label_dist, *p - y0, HPos::Left, VPos::Center)
373                     }
374                     // Bottom
375                     (dx, dy) if dx == 0 && dy > 0 => {
376                         (*p - x0, th as i32 - label_dist, HPos::Center, VPos::Bottom)
377                     }
378                     // Top
379                     (dx, dy) if dx == 0 && dy < 0 => (*p - x0, label_dist, HPos::Center, VPos::Top),
380                     _ => panic!("Bug: Invalid orientation specification"),
381                 }
382             };
383 
384             let (text_x, text_y) = if orientation.0 == 0 {
385                 (cx + label_offset, cy)
386             } else {
387                 (cx, cy + label_offset)
388             };
389 
390             let label_style = &label_style.pos(Pos::new(h_pos, v_pos));
391             area.draw_text(&t, label_style, (text_x, text_y))?;
392 
393             if tick_size != 0 {
394                 if let Some(style) = axis_style {
395                     let xmax = tw as i32 - 1;
396                     let ymax = th as i32 - 1;
397                     let (kx0, ky0, kx1, ky1) = if tick_size > 0 {
398                         match orientation {
399                             (dx, dy) if dx > 0 && dy == 0 => (0, *p - y0, tick_size, *p - y0),
400                             (dx, dy) if dx < 0 && dy == 0 => {
401                                 (xmax - tick_size, *p - y0, xmax, *p - y0)
402                             }
403                             (dx, dy) if dx == 0 && dy > 0 => (*p - x0, 0, *p - x0, tick_size),
404                             (dx, dy) if dx == 0 && dy < 0 => {
405                                 (*p - x0, ymax - tick_size, *p - x0, ymax)
406                             }
407                             _ => panic!("Bug: Invalid orientation specification"),
408                         }
409                     } else {
410                         match orientation {
411                             (dx, dy) if dx > 0 && dy == 0 => {
412                                 (xmax, *p - y0, xmax + tick_size, *p - y0)
413                             }
414                             (dx, dy) if dx < 0 && dy == 0 => (0, *p - y0, -tick_size, *p - y0),
415                             (dx, dy) if dx == 0 && dy > 0 => {
416                                 (*p - x0, ymax, *p - x0, ymax + tick_size)
417                             }
418                             (dx, dy) if dx == 0 && dy < 0 => (*p - x0, 0, *p - x0, -tick_size),
419                             _ => panic!("Bug: Invalid orientation specification"),
420                         }
421                     };
422                     let line = PathElement::new(vec![(kx0, ky0), (kx1, ky1)], style.clone());
423                     area.draw(&line)?;
424                 }
425             }
426         }
427 
428         if let Some((text, style)) = axis_desc {
429             let actual_style = if orientation.0 == 0 {
430                 style.clone()
431             } else if orientation.0 == -1 {
432                 style.transform(FontTransform::Rotate270)
433             } else {
434                 style.transform(FontTransform::Rotate90)
435             };
436 
437             let (x0, y0, h_pos, v_pos) = match orientation {
438                 // Right
439                 (dx, dy) if dx > 0 && dy == 0 => (tw, th / 2, HPos::Center, VPos::Top),
440                 // Left
441                 (dx, dy) if dx < 0 && dy == 0 => (0, th / 2, HPos::Center, VPos::Top),
442                 // Bottom
443                 (dx, dy) if dx == 0 && dy > 0 => (tw / 2, th, HPos::Center, VPos::Bottom),
444                 // Top
445                 (dx, dy) if dx == 0 && dy < 0 => (tw / 2, 0, HPos::Center, VPos::Top),
446                 _ => panic!("Bug: Invalid orientation specification"),
447             };
448 
449             let actual_style = &actual_style.pos(Pos::new(h_pos, v_pos));
450             area.draw_text(&text, &actual_style, (x0 as i32, y0 as i32))?;
451         }
452 
453         Ok(())
454     }
455 
456     #[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(&MeshLine<X, Y>) -> Option<String>,457     pub(super) fn draw_mesh<FmtLabel, YH: KeyPointHint, XH: KeyPointHint>(
458         &mut self,
459         (r, c): (YH, XH),
460         mesh_line_style: &ShapeStyle,
461         x_label_style: &TextStyle,
462         y_label_style: &TextStyle,
463         fmt_label: FmtLabel,
464         x_mesh: bool,
465         y_mesh: bool,
466         x_label_offset: i32,
467         y_label_offset: i32,
468         x_axis: bool,
469         y_axis: bool,
470         axis_style: &ShapeStyle,
471         axis_desc_style: &TextStyle,
472         x_desc: Option<String>,
473         y_desc: Option<String>,
474         x_tick_size: [i32; 2],
475         y_tick_size: [i32; 2],
476     ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>>
477     where
478         FmtLabel: FnMut(&MeshLine<X, Y>) -> Option<String>,
479     {
480         let (x_labels, y_labels) =
481             self.draw_mesh_lines((r, c), (x_mesh, y_mesh), mesh_line_style, fmt_label)?;
482 
483         for idx in 0..2 {
484             self.draw_axis_and_labels(
485                 self.x_label_area[idx].as_ref(),
486                 if x_axis { Some(axis_style) } else { None },
487                 &x_labels[..],
488                 x_label_style,
489                 x_label_offset,
490                 (0, -1 + idx as i16 * 2),
491                 x_desc.as_ref().map(|desc| (&desc[..], axis_desc_style)),
492                 x_tick_size[idx],
493             )?;
494 
495             self.draw_axis_and_labels(
496                 self.y_label_area[idx].as_ref(),
497                 if y_axis { Some(axis_style) } else { None },
498                 &y_labels[..],
499                 y_label_style,
500                 y_label_offset,
501                 (-1 + idx as i16 * 2, 0),
502                 y_desc.as_ref().map(|desc| (&desc[..], axis_desc_style)),
503                 y_tick_size[idx],
504             )?;
505         }
506 
507         Ok(())
508     }
509 
510     /// Convert this chart context into a dual axis chart context and attach a second coordinate spec
511     /// on the chart context. For more detailed information, see documentation for [struct DualCoordChartContext](struct.DualCoordChartContext.html)
512     ///
513     /// - `x_coord`: The coordinate spec for the X axis
514     /// - `y_coord`: The coordinate spec for the Y axis
515     /// - **returns** The newly created dual spec chart context
516     #[allow(clippy::type_complexity)]
set_secondary_coord<SX: AsRangedCoord, SY: AsRangedCoord>( self, x_coord: SX, y_coord: SY, ) -> DualCoordChartContext< 'a, DB, Cartesian2d<X, Y>, Cartesian2d<SX::CoordDescType, SY::CoordDescType>, >517     pub fn set_secondary_coord<SX: AsRangedCoord, SY: AsRangedCoord>(
518         self,
519         x_coord: SX,
520         y_coord: SY,
521     ) -> DualCoordChartContext<
522         'a,
523         DB,
524         Cartesian2d<X, Y>,
525         Cartesian2d<SX::CoordDescType, SY::CoordDescType>,
526     > {
527         let mut pixel_range = self.drawing_area.get_pixel_range();
528         pixel_range.1 = pixel_range.1.end..pixel_range.1.start;
529 
530         DualCoordChartContext::new(self, Cartesian2d::new(x_coord, y_coord, pixel_range))
531     }
532 }
533 
534 pub(super) struct KeyPoints3d<X: Ranged, Y: Ranged, Z: Ranged> {
535     pub(super) x_points: Vec<X::ValueType>,
536     pub(super) y_points: Vec<Y::ValueType>,
537     pub(super) z_points: Vec<Z::ValueType>,
538 }
539 
540 #[derive(Clone, Debug)]
541 pub(super) enum Coord3D<X, Y, Z> {
542     X(X),
543     Y(Y),
544     Z(Z),
545 }
546 
547 impl<X, Y, Z> Coord3D<X, Y, Z> {
get_x(&self) -> &X548     fn get_x(&self) -> &X {
549         match self {
550             Coord3D::X(ret) => ret,
551             _ => panic!("Invalid call!"),
552         }
553     }
get_y(&self) -> &Y554     fn get_y(&self) -> &Y {
555         match self {
556             Coord3D::Y(ret) => ret,
557             _ => panic!("Invalid call!"),
558         }
559     }
get_z(&self) -> &Z560     fn get_z(&self) -> &Z {
561         match self {
562             Coord3D::Z(ret) => ret,
563             _ => panic!("Invalid call!"),
564         }
565     }
566 
build_coord([x, y, z]: [&Self; 3]) -> (X, Y, Z) where X: Clone, Y: Clone, Z: Clone,567     fn build_coord([x, y, z]: [&Self; 3]) -> (X, Y, Z)
568     where
569         X: Clone,
570         Y: Clone,
571         Z: Clone,
572     {
573         (x.get_x().clone(), y.get_y().clone(), z.get_z().clone())
574     }
575 }
576 impl<'a, DB, X, Y, Z, XT, YT, ZT> ChartContext<'a, DB, Cartesian3d<X, Y, Z>>
577 where
578     DB: DrawingBackend,
579     X: Ranged<ValueType = XT> + ValueFormatter<XT>,
580     Y: Ranged<ValueType = YT> + ValueFormatter<YT>,
581     Z: Ranged<ValueType = ZT> + ValueFormatter<ZT>,
582 {
configure_axes(&mut self) -> Axes3dStyle<'a, '_, X, Y, Z, DB>583     pub fn configure_axes(&mut self) -> Axes3dStyle<'a, '_, X, Y, Z, DB> {
584         Axes3dStyle::new(self)
585     }
586 }
587 impl<'a, DB, X: Ranged, Y: Ranged, Z: Ranged> ChartContext<'a, DB, Cartesian3d<X, Y, Z>>
588 where
589     DB: DrawingBackend,
590 {
591     /// Override the 3D projection matrix. This function allows to override the default projection
592     /// matrix.
593     /// - `pf`: A function that takes the default projection matrix configuration and returns the
594     /// projection matrix. This function will allow you to adjust the pitch, yaw angle and the
595     /// centeral point of the projection, etc. You can also build a projection matrix which is not
596     /// relies on the default configuration as well.
with_projection<P: FnOnce(ProjectionMatrixBuilder) -> ProjectionMatrix>( &mut self, pf: P, ) -> &mut Self597     pub fn with_projection<P: FnOnce(ProjectionMatrixBuilder) -> ProjectionMatrix>(
598         &mut self,
599         pf: P,
600     ) -> &mut Self {
601         let (actual_x, actual_y) = self.drawing_area.get_pixel_range();
602         self.drawing_area
603             .as_coord_spec_mut()
604             .set_projection(actual_x, actual_y, pf);
605         self
606     }
607 }
608 
609 impl<'a, DB, X: Ranged, Y: Ranged, Z: Ranged> ChartContext<'a, DB, Cartesian3d<X, Y, Z>>
610 where
611     DB: DrawingBackend,
612     X::ValueType: Clone,
613     Y::ValueType: Clone,
614     Z::ValueType: Clone,
615 {
get_key_points<XH: KeyPointHint, YH: KeyPointHint, ZH: KeyPointHint>( &self, x_hint: XH, y_hint: YH, z_hint: ZH, ) -> KeyPoints3d<X, Y, Z>616     pub(super) fn get_key_points<XH: KeyPointHint, YH: KeyPointHint, ZH: KeyPointHint>(
617         &self,
618         x_hint: XH,
619         y_hint: YH,
620         z_hint: ZH,
621     ) -> KeyPoints3d<X, Y, Z> {
622         let coord = self.plotting_area().as_coord_spec();
623         let x_points = coord.logic_x.key_points(x_hint);
624         let y_points = coord.logic_y.key_points(y_hint);
625         let z_points = coord.logic_z.key_points(z_hint);
626         KeyPoints3d {
627             x_points,
628             y_points,
629             z_points,
630         }
631     }
draw_axis_ticks( &mut self, axis: [[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3]; 2], labels: &[( [Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3], String, )], tick_size: i32, style: ShapeStyle, font: TextStyle, ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>>632     pub(super) fn draw_axis_ticks(
633         &mut self,
634         axis: [[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3]; 2],
635         labels: &[(
636             [Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3],
637             String,
638         )],
639         tick_size: i32,
640         style: ShapeStyle,
641         font: TextStyle,
642     ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> {
643         let coord = self.plotting_area().as_coord_spec();
644         let begin = coord.translate(&Coord3D::build_coord([
645             &axis[0][0],
646             &axis[0][1],
647             &axis[0][2],
648         ]));
649         let end = coord.translate(&Coord3D::build_coord([
650             &axis[1][0],
651             &axis[1][1],
652             &axis[1][2],
653         ]));
654         let axis_dir = (end.0 - begin.0, end.1 - begin.1);
655         let (x_range, y_range) = self.plotting_area().get_pixel_range();
656         let x_mid = (x_range.start + x_range.end) / 2;
657         let y_mid = (y_range.start + y_range.end) / 2;
658 
659         let x_dir = if begin.0 < x_mid {
660             (-tick_size, 0)
661         } else {
662             (tick_size, 0)
663         };
664 
665         let y_dir = if begin.1 < y_mid {
666             (0, -tick_size)
667         } else {
668             (0, tick_size)
669         };
670 
671         let x_score = (x_dir.0 * axis_dir.0 + x_dir.1 * axis_dir.1).abs();
672         let y_score = (y_dir.0 * axis_dir.0 + y_dir.1 * axis_dir.1).abs();
673 
674         let dir = if x_score < y_score { x_dir } else { y_dir };
675 
676         for (pos, text) in labels {
677             let logic_pos = Coord3D::build_coord([&pos[0], &pos[1], &pos[2]]);
678             let mut font = font.clone();
679             if dir.0 < 0 {
680                 font.pos = Pos::new(HPos::Right, VPos::Center);
681             } else if dir.0 > 0 {
682                 font.pos = Pos::new(HPos::Left, VPos::Center);
683             };
684             if dir.1 < 0 {
685                 font.pos = Pos::new(HPos::Center, VPos::Bottom);
686             } else if dir.1 > 0 {
687                 font.pos = Pos::new(HPos::Center, VPos::Top);
688             };
689             let element = EmptyElement::at(logic_pos)
690                 + PathElement::new(vec![(0, 0), dir], style.clone())
691                 + Text::new(text.to_string(), (dir.0 * 2, dir.1 * 2), font.clone());
692             self.plotting_area().draw(&element)?;
693         }
694         Ok(())
695     }
draw_axis( &mut self, idx: usize, panels: &[[[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3]; 2]; 3], style: ShapeStyle, ) -> Result< [[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3]; 2], DrawingAreaErrorKind<DB::ErrorType>, >696     pub(super) fn draw_axis(
697         &mut self,
698         idx: usize,
699         panels: &[[[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3]; 2]; 3],
700         style: ShapeStyle,
701     ) -> Result<
702         [[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3]; 2],
703         DrawingAreaErrorKind<DB::ErrorType>,
704     > {
705         let coord = self.plotting_area().as_coord_spec();
706         let x_range = coord.logic_x.range();
707         let y_range = coord.logic_y.range();
708         let z_range = coord.logic_z.range();
709 
710         let ranges: [[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 2]; 3] = [
711             [Coord3D::X(x_range.start), Coord3D::X(x_range.end)],
712             [Coord3D::Y(y_range.start), Coord3D::Y(y_range.end)],
713             [Coord3D::Z(z_range.start), Coord3D::Z(z_range.end)],
714         ];
715 
716         let (start, end) = {
717             let mut start = [&ranges[0][0], &ranges[1][0], &ranges[2][0]];
718             let mut end = [&ranges[0][1], &ranges[1][1], &ranges[2][1]];
719 
720             let mut plan = vec![];
721 
722             for i in 0..3 {
723                 if i == idx {
724                     continue;
725                 }
726                 start[i] = &panels[i][0][i];
727                 end[i] = &panels[i][0][i];
728                 for j in 0..3 {
729                     if i != idx && i != j && j != idx {
730                         for k in 0..2 {
731                             start[j] = &panels[i][k][j];
732                             end[j] = &panels[i][k][j];
733                             plan.push((start, end));
734                         }
735                     }
736                 }
737             }
738             plan.into_iter()
739                 .min_by_key(|&(s, e)| {
740                     let d = coord.projected_depth(s[0].get_x(), s[1].get_y(), s[2].get_z());
741                     let d = d + coord.projected_depth(e[0].get_x(), e[1].get_y(), e[2].get_z());
742                     let (_, y1) = coord.translate(&Coord3D::build_coord(s));
743                     let (_, y2) = coord.translate(&Coord3D::build_coord(e));
744                     let y = y1 + y2;
745                     (d, y)
746                 })
747                 .unwrap()
748         };
749 
750         self.plotting_area().draw(&PathElement::new(
751             vec![Coord3D::build_coord(start), Coord3D::build_coord(end)],
752             style.clone(),
753         ))?;
754 
755         Ok([
756             [start[0].clone(), start[1].clone(), start[2].clone()],
757             [end[0].clone(), end[1].clone(), end[2].clone()],
758         ])
759     }
draw_axis_panels( &mut self, bold_points: &KeyPoints3d<X, Y, Z>, light_points: &KeyPoints3d<X, Y, Z>, panel_style: ShapeStyle, bold_grid_style: ShapeStyle, light_grid_style: ShapeStyle, ) -> Result< [[[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3]; 2]; 3], DrawingAreaErrorKind<DB::ErrorType>, >760     pub(super) fn draw_axis_panels(
761         &mut self,
762         bold_points: &KeyPoints3d<X, Y, Z>,
763         light_points: &KeyPoints3d<X, Y, Z>,
764         panel_style: ShapeStyle,
765         bold_grid_style: ShapeStyle,
766         light_grid_style: ShapeStyle,
767     ) -> Result<
768         [[[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3]; 2]; 3],
769         DrawingAreaErrorKind<DB::ErrorType>,
770     > {
771         let mut r_iter = (0..3).map(|idx| {
772             self.draw_axis_panel(
773                 idx,
774                 bold_points,
775                 light_points,
776                 panel_style.clone(),
777                 bold_grid_style.clone(),
778                 light_grid_style.clone(),
779             )
780         });
781         Ok([
782             r_iter.next().unwrap()?,
783             r_iter.next().unwrap()?,
784             r_iter.next().unwrap()?,
785         ])
786     }
draw_axis_panel( &mut self, idx: usize, bold_points: &KeyPoints3d<X, Y, Z>, light_points: &KeyPoints3d<X, Y, Z>, panel_style: ShapeStyle, bold_grid_style: ShapeStyle, light_grid_style: ShapeStyle, ) -> Result< [[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3]; 2], DrawingAreaErrorKind<DB::ErrorType>, >787     fn draw_axis_panel(
788         &mut self,
789         idx: usize,
790         bold_points: &KeyPoints3d<X, Y, Z>,
791         light_points: &KeyPoints3d<X, Y, Z>,
792         panel_style: ShapeStyle,
793         bold_grid_style: ShapeStyle,
794         light_grid_style: ShapeStyle,
795     ) -> Result<
796         [[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3]; 2],
797         DrawingAreaErrorKind<DB::ErrorType>,
798     > {
799         let coord = self.plotting_area().as_coord_spec();
800         let x_range = coord.logic_x.range();
801         let y_range = coord.logic_y.range();
802         let z_range = coord.logic_z.range();
803 
804         let ranges: [[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 2]; 3] = [
805             [Coord3D::X(x_range.start), Coord3D::X(x_range.end)],
806             [Coord3D::Y(y_range.start), Coord3D::Y(y_range.end)],
807             [Coord3D::Z(z_range.start), Coord3D::Z(z_range.end)],
808         ];
809 
810         let (mut panel, start, end) = {
811             let a = [&ranges[0][0], &ranges[1][0], &ranges[2][0]];
812             let mut b = [&ranges[0][1], &ranges[1][1], &ranges[2][1]];
813             let mut c = a;
814             let d = b;
815 
816             b[idx] = &ranges[idx][0];
817             c[idx] = &ranges[idx][1];
818 
819             let (a, b) = if coord.projected_depth(a[0].get_x(), a[1].get_y(), a[2].get_z())
820                 >= coord.projected_depth(c[0].get_x(), c[1].get_y(), c[2].get_z())
821             {
822                 (a, b)
823             } else {
824                 (c, d)
825             };
826 
827             let mut m = a.clone();
828             m[(idx + 1) % 3] = b[(idx + 1) % 3];
829             let mut n = a.clone();
830             n[(idx + 2) % 3] = b[(idx + 2) % 3];
831 
832             (
833                 vec![
834                     Coord3D::build_coord(a),
835                     Coord3D::build_coord(m),
836                     Coord3D::build_coord(b),
837                     Coord3D::build_coord(n),
838                 ],
839                 a,
840                 b,
841             )
842         };
843         self.plotting_area()
844             .draw(&Polygon::new(panel.clone(), panel_style.clone()))?;
845         panel.push(panel[0].clone());
846         self.plotting_area()
847             .draw(&PathElement::new(panel, bold_grid_style.clone()))?;
848 
849         for (kps, style) in vec![
850             (light_points, light_grid_style),
851             (bold_points, bold_grid_style),
852         ]
853         .into_iter()
854         {
855             for idx in (0..3).filter(|&i| i != idx) {
856                 let kps: Vec<_> = match idx {
857                     0 => kps.x_points.iter().map(|x| Coord3D::X(x.clone())).collect(),
858                     1 => kps.y_points.iter().map(|y| Coord3D::Y(y.clone())).collect(),
859                     _ => kps.z_points.iter().map(|z| Coord3D::Z(z.clone())).collect(),
860                 };
861                 for kp in kps.iter() {
862                     let mut kp_start = start;
863                     let mut kp_end = end;
864                     kp_start[idx] = kp;
865                     kp_end[idx] = kp;
866                     self.plotting_area().draw(&PathElement::new(
867                         vec![Coord3D::build_coord(kp_start), Coord3D::build_coord(kp_end)],
868                         style.clone(),
869                     ))?;
870                 }
871             }
872         }
873 
874         Ok([
875             [start[0].clone(), start[1].clone(), start[2].clone()],
876             [end[0].clone(), end[1].clone(), end[2].clone()],
877         ])
878     }
879 }
880 
881 #[cfg(test)]
882 mod test {
883     use crate::prelude::*;
884 
885     #[test]
test_chart_context()886     fn test_chart_context() {
887         let drawing_area = create_mocked_drawing_area(200, 200, |_| {});
888 
889         drawing_area.fill(&WHITE).expect("Fill");
890 
891         let mut chart = ChartBuilder::on(&drawing_area)
892             .caption("Test Title", ("serif", 10))
893             .x_label_area_size(20)
894             .y_label_area_size(20)
895             .set_label_area_size(LabelAreaPosition::Top, 20)
896             .set_label_area_size(LabelAreaPosition::Right, 20)
897             .build_cartesian_2d(0..10, 0..10)
898             .expect("Create chart")
899             .set_secondary_coord(0.0..1.0, 0.0..1.0);
900 
901         chart
902             .configure_mesh()
903             .x_desc("X")
904             .y_desc("Y")
905             .draw()
906             .expect("Draw mesh");
907         chart
908             .configure_secondary_axes()
909             .x_desc("X")
910             .y_desc("Y")
911             .draw()
912             .expect("Draw Secondary axes");
913 
914         chart
915             .draw_series(std::iter::once(Circle::new((5, 5), 5, &RED)))
916             .expect("Drawing error");
917         chart
918             .draw_secondary_series(std::iter::once(Circle::new((0.3, 0.8), 5, &GREEN)))
919             .expect("Drawing error")
920             .label("Test label")
921             .legend(|(x, y)| Rectangle::new([(x - 10, y - 5), (x, y + 5)], &GREEN));
922 
923         chart
924             .configure_series_labels()
925             .position(SeriesLabelPosition::UpperMiddle)
926             .draw()
927             .expect("Drawing error");
928     }
929 
930     #[test]
test_chart_context_3d()931     fn test_chart_context_3d() {
932         let drawing_area = create_mocked_drawing_area(200, 200, |_| {});
933 
934         drawing_area.fill(&WHITE).expect("Fill");
935 
936         let mut chart = ChartBuilder::on(&drawing_area)
937             .caption("Test Title", ("serif", 10))
938             .x_label_area_size(20)
939             .y_label_area_size(20)
940             .set_label_area_size(LabelAreaPosition::Top, 20)
941             .set_label_area_size(LabelAreaPosition::Right, 20)
942             .build_cartesian_3d(0..10, 0..10, 0..10)
943             .expect("Create chart");
944 
945         chart.with_projection(|mut pb| {
946             pb.yaw = 0.5;
947             pb.pitch = 0.5;
948             pb.scale = 0.5;
949             pb.into_matrix()
950         });
951 
952         chart.configure_axes().draw().expect("Drawing axes");
953 
954         chart
955             .draw_series(std::iter::once(Circle::new((5, 5, 5), 5, &RED)))
956             .expect("Drawing error");
957     }
958 }
959