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