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