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