1use std::borrow::Borrow;
2use std::ops::Range;
3
4use super::axes3d::Axes3dStyle;
5use super::{DualCoordChartContext, MeshStyle, SeriesAnno, SeriesLabelStyle};
6
7use crate::coord::cartesian::{Cartesian2d, Cartesian3d, MeshLine};
8use crate::coord::ranged1d::{AsRangedCoord, KeyPointHint, Ranged, ValueFormatter};
9use crate::coord::ranged3d::{ProjectionMatrix, ProjectionMatrixBuilder};
10use crate::coord::{CoordTranslate, ReverseCoordTranslate, Shift};
11
12use crate::drawing::{DrawingArea, DrawingAreaErrorKind};
13use crate::element::{
14 CoordMapper, Drawable, EmptyElement, PathElement, PointCollection, Polygon, Text,
15};
16use crate::style::text_anchor::{HPos, Pos, VPos};
17use crate::style::{ShapeStyle, TextStyle};
18
19use plotters_backend::{BackendCoord, DrawingBackend, FontTransform};
20
21pub 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
36impl<'a, DB, XT, YT, X, Y> ChartContext<'a, DB, Cartesian2d<X, Y>>
37where
38 DB: DrawingBackend,
39 X: Ranged<ValueType = XT> + ValueFormatter<XT>,
40 Y: Ranged<ValueType = YT> + ValueFormatter<YT>,
41{
42 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 pub fn configure_mesh(&mut self) -> MeshStyle<'a, '_, X, Y, DB> {
66 MeshStyle::new(self)
67 }
68}
69
70impl<'a, DB: DrawingBackend, CT: ReverseCoordTranslate> ChartContext<'a, DB, CT> {
71 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
78impl<'a, DB: DrawingBackend, CT: CoordTranslate> ChartContext<'a, DB, CT> {
79 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 pub fn plotting_area(&self) -> &DrawingArea<DB, CT> {
89 &self.drawing_area
90 }
91
92 pub fn as_coord_spec(&self) -> &CT {
94 self.drawing_area.as_coord_spec()
95 }
96
97 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
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 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
142impl<'a, DB: DrawingBackend, X: Ranged, Y: Ranged> ChartContext<'a, DB, Cartesian2d<X, Y>> {
143 pub fn x_range(&self) -> Range<X::ValueType> {
145 self.drawing_area.get_x_range()
146 }
147
148 pub fn y_range(&self) -> Range<Y::ValueType> {
150 self.drawing_area.get_y_range()
151 }
152
153 pub fn backend_coord(&self, coord: &(X::ValueType, Y::ValueType)) -> BackendCoord {
156 self.drawing_area.map_coordinate(coord)
157 }
158
159 #[allow(clippy::type_complexity)]
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
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 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 #[allow(clippy::too_many_arguments)]
273 #[allow(clippy::cognitive_complexity)]
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 let label_dist = tick_size.abs() * 2;
296
297 let axis_range = self.draw_axis(area, axis_style, orientation, tick_size < 0)?;
300
301 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 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 for ((p, t), w) in labels.iter().zip(label_width.into_iter()) {
331 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 (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 (dx, dy) if dx < 0 && dy == 0 => {
357 (tw as i32 - label_dist, *p - y0, HPos::Right, VPos::Center)
358 }
359 (dx, dy) if dx == 0 && dy > 0 => (*p - x0, label_dist, HPos::Center, VPos::Top),
361 (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 (dx, dy) if dx > 0 && dy == 0 => {
371 (tw as i32 - label_dist, *p - y0, HPos::Right, VPos::Center)
372 }
373 (dx, dy) if dx < 0 && dy == 0 => {
375 (label_dist, *p - y0, HPos::Left, VPos::Center)
376 }
377 (dx, dy) if dx == 0 && dy > 0 => {
379 (*p - x0, th as i32 - label_dist, HPos::Center, VPos::Bottom)
380 }
381 (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 (dx, dy) if dx > 0 && dy == 0 => (tw, th / 2, HPos::Center, VPos::Top),
443 (dx, dy) if dx < 0 && dy == 0 => (0, th / 2, HPos::Center, VPos::Top),
445 (dx, dy) if dx == 0 && dy > 0 => (tw / 2, th, HPos::Center, VPos::Bottom),
447 (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)]
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 #[allow(clippy::type_complexity)]
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
537pub(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)]
544pub(super) enum Coord3D<X, Y, Z> {
545 X(X),
546 Y(Y),
547 Z(Z),
548}
549
550impl<X, Y, Z> Coord3D<X, Y, Z> {
551 fn get_x(&self) -> &X {
552 match self {
553 Coord3D::X(ret) => ret,
554 _ => panic!("Invalid call!"),
555 }
556 }
557 fn get_y(&self) -> &Y {
558 match self {
559 Coord3D::Y(ret) => ret,
560 _ => panic!("Invalid call!"),
561 }
562 }
563 fn get_z(&self) -> &Z {
564 match self {
565 Coord3D::Z(ret) => ret,
566 _ => panic!("Invalid call!"),
567 }
568 }
569
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}
579impl<'a, DB, X, Y, Z, XT, YT, ZT> ChartContext<'a, DB, Cartesian3d<X, Y, Z>>
580where
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{
586 pub fn configure_axes(&mut self) -> Axes3dStyle<'a, '_, X, Y, Z, DB> {
587 Axes3dStyle::new(self)
588 }
589}
590impl<'a, DB, X: Ranged, Y: Ranged, Z: Ranged> ChartContext<'a, DB, Cartesian3d<X, Y, Z>>
591where
592 DB: DrawingBackend,
593{
594 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
611 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
620impl<'a, DB, X: Ranged, Y: Ranged, Z: Ranged> ChartContext<'a, DB, Cartesian3d<X, Y, Z>>
621where
622 DB: DrawingBackend,
623 X::ValueType: Clone,
624 Y::ValueType: Clone,
625 Z::ValueType: Clone,
626{
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 }
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 }
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 }
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 }
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)]
893mod test {
894 use crate::prelude::*;
895
896 #[test]
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]
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}