1 use crate::coord::cartesian::{Cartesian2d, MeshLine}; 2 use crate::coord::ranged1d::{KeyPointHint, Ranged}; 3 use crate::coord::{CoordTranslate, Shift}; 4 use crate::element::{CoordMapper, Drawable, PointCollection}; 5 use crate::style::text_anchor::{HPos, Pos, VPos}; 6 use crate::style::{Color, SizeDesc, TextStyle}; 7 8 /// The abstraction of a drawing area 9 use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; 10 11 use std::borrow::Borrow; 12 use std::cell::RefCell; 13 use std::error::Error; 14 use std::iter::{once, repeat}; 15 use std::ops::Range; 16 use std::rc::Rc; 17 18 /// The representation of the rectangle in backend canvas 19 #[derive(Clone, Debug)] 20 pub struct Rect { 21 x0: i32, 22 y0: i32, 23 x1: i32, 24 y1: i32, 25 } 26 27 impl Rect { 28 /// Split the rectangle into a few smaller rectangles split<'a, BPI: IntoIterator<Item = &'a i32> + 'a>( &'a self, break_points: BPI, vertical: bool, ) -> impl Iterator<Item = Rect> + 'a29 fn split<'a, BPI: IntoIterator<Item = &'a i32> + 'a>( 30 &'a self, 31 break_points: BPI, 32 vertical: bool, 33 ) -> impl Iterator<Item = Rect> + 'a { 34 let (mut x0, mut y0) = (self.x0, self.y0); 35 let (full_x, full_y) = (self.x1, self.y1); 36 break_points 37 .into_iter() 38 .chain(once(if vertical { &self.y1 } else { &self.x1 })) 39 .map(move |&p| { 40 let x1 = if vertical { full_x } else { p }; 41 let y1 = if vertical { p } else { full_y }; 42 let ret = Rect { x0, y0, x1, y1 }; 43 44 if vertical { 45 y0 = y1 46 } else { 47 x0 = x1; 48 } 49 50 ret 51 }) 52 } 53 54 /// Evenly split the rectangle to a row * col mesh split_evenly(&self, (row, col): (usize, usize)) -> impl Iterator<Item = Rect> + '_55 fn split_evenly(&self, (row, col): (usize, usize)) -> impl Iterator<Item = Rect> + '_ { 56 fn compute_evenly_split(from: i32, to: i32, n: usize, idx: usize) -> i32 { 57 let size = (to - from) as usize; 58 from + idx as i32 * (size / n) as i32 + idx.min(size % n) as i32 59 } 60 (0..row) 61 .flat_map(move |x| repeat(x).zip(0..col)) 62 .map(move |(ri, ci)| Self { 63 y0: compute_evenly_split(self.y0, self.y1, row, ri), 64 y1: compute_evenly_split(self.y0, self.y1, row, ri + 1), 65 x0: compute_evenly_split(self.x0, self.x1, col, ci), 66 x1: compute_evenly_split(self.x0, self.x1, col, ci + 1), 67 }) 68 } 69 70 /// Evenly the rectangle into a grid with arbitrary breaks; return a rect iterator. split_grid( &self, x_breaks: impl Iterator<Item = i32>, y_breaks: impl Iterator<Item = i32>, ) -> impl Iterator<Item = Rect>71 fn split_grid( 72 &self, 73 x_breaks: impl Iterator<Item = i32>, 74 y_breaks: impl Iterator<Item = i32>, 75 ) -> impl Iterator<Item = Rect> { 76 let mut xs = vec![self.x0, self.x1]; 77 let mut ys = vec![self.y0, self.y1]; 78 xs.extend(x_breaks.map(|v| v + self.x0)); 79 ys.extend(y_breaks.map(|v| v + self.y0)); 80 81 xs.sort_unstable(); 82 ys.sort_unstable(); 83 84 let xsegs: Vec<_> = xs 85 .iter() 86 .zip(xs.iter().skip(1)) 87 .map(|(a, b)| (*a, *b)) 88 .collect(); 89 90 // Justify: this is actually needed. Because we need to return a iterator that have 91 // static life time, thus we need to copy the value to a buffer and then turn the buffer 92 // into a iterator. 93 #[allow(clippy::needless_collect)] 94 let ysegs: Vec<_> = ys 95 .iter() 96 .zip(ys.iter().skip(1)) 97 .map(|(a, b)| (*a, *b)) 98 .collect(); 99 100 ysegs 101 .into_iter() 102 .flat_map(move |(y0, y1)| { 103 xsegs 104 .clone() 105 .into_iter() 106 .map(move |(x0, x1)| Self { x0, y0, x1, y1 }) 107 }) 108 } 109 110 /// Make the coordinate in the range of the rectangle truncate(&self, p: (i32, i32)) -> (i32, i32)111 pub fn truncate(&self, p: (i32, i32)) -> (i32, i32) { 112 (p.0.min(self.x1).max(self.x0), p.1.min(self.y1).max(self.y0)) 113 } 114 } 115 116 /// The abstraction of a drawing area. Plotters uses drawing area as the fundamental abstraction for the 117 /// high level drawing API. The major functionality provided by the drawing area is 118 /// 1. Layout specification - Split the parent drawing area into sub-drawing-areas 119 /// 2. Coordinate Translation - Allows guest coordinate system attached and used for drawing. 120 /// 3. Element based drawing - drawing area provides the environment the element can be drawn onto it. 121 pub struct DrawingArea<DB: DrawingBackend, CT: CoordTranslate> { 122 backend: Rc<RefCell<DB>>, 123 rect: Rect, 124 coord: CT, 125 } 126 127 impl<DB: DrawingBackend, CT: CoordTranslate + Clone> Clone for DrawingArea<DB, CT> { clone(&self) -> Self128 fn clone(&self) -> Self { 129 Self { 130 backend: self.backend.clone(), 131 rect: self.rect.clone(), 132 coord: self.coord.clone(), 133 } 134 } 135 } 136 137 /// The error description of any drawing area API 138 #[derive(Debug)] 139 pub enum DrawingAreaErrorKind<E: Error + Send + Sync> { 140 /// The error is due to drawing backend failure 141 BackendError(DrawingErrorKind<E>), 142 /// We are not able to get the mutable reference of the backend, 143 /// which indicates the drawing backend is current used by other 144 /// drawing operation 145 SharingError, 146 /// The error caused by invalid layout 147 LayoutError, 148 } 149 150 impl<E: Error + Send + Sync> std::fmt::Display for DrawingAreaErrorKind<E> { fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error>151 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { 152 match self { 153 DrawingAreaErrorKind::BackendError(e) => write!(fmt, "backend error: {}", e), 154 DrawingAreaErrorKind::SharingError => { 155 write!(fmt, "Multiple backend operation in progress") 156 } 157 DrawingAreaErrorKind::LayoutError => write!(fmt, "Bad layout"), 158 } 159 } 160 } 161 162 impl<E: Error + Send + Sync> Error for DrawingAreaErrorKind<E> {} 163 164 #[allow(type_alias_bounds)] 165 type DrawingAreaError<T: DrawingBackend> = DrawingAreaErrorKind<T::ErrorType>; 166 167 impl<DB: DrawingBackend> From<DB> for DrawingArea<DB, Shift> { from(backend: DB) -> Self168 fn from(backend: DB) -> Self { 169 Self::with_rc_cell(Rc::new(RefCell::new(backend))) 170 } 171 } 172 173 impl<'a, DB: DrawingBackend> From<&'a Rc<RefCell<DB>>> for DrawingArea<DB, Shift> { from(backend: &'a Rc<RefCell<DB>>) -> Self174 fn from(backend: &'a Rc<RefCell<DB>>) -> Self { 175 Self::with_rc_cell(backend.clone()) 176 } 177 } 178 179 /// A type which can be converted into a root drawing area 180 pub trait IntoDrawingArea: DrawingBackend + Sized { 181 /// Convert the type into a root drawing area into_drawing_area(self) -> DrawingArea<Self, Shift>182 fn into_drawing_area(self) -> DrawingArea<Self, Shift>; 183 } 184 185 impl<T: DrawingBackend> IntoDrawingArea for T { into_drawing_area(self) -> DrawingArea<T, Shift>186 fn into_drawing_area(self) -> DrawingArea<T, Shift> { 187 self.into() 188 } 189 } 190 191 impl<DB: DrawingBackend, X: Ranged, Y: Ranged> DrawingArea<DB, Cartesian2d<X, Y>> { 192 /// Draw the mesh on a area draw_mesh<DrawFunc, YH: KeyPointHint, XH: KeyPointHint>( &self, mut draw_func: DrawFunc, y_count_max: YH, x_count_max: XH, ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> where DrawFunc: FnMut(&mut DB, MeshLine<X, Y>) -> Result<(), DrawingErrorKind<DB::ErrorType>>,193 pub fn draw_mesh<DrawFunc, YH: KeyPointHint, XH: KeyPointHint>( 194 &self, 195 mut draw_func: DrawFunc, 196 y_count_max: YH, 197 x_count_max: XH, 198 ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> 199 where 200 DrawFunc: FnMut(&mut DB, MeshLine<X, Y>) -> Result<(), DrawingErrorKind<DB::ErrorType>>, 201 { 202 self.backend_ops(move |b| { 203 self.coord 204 .draw_mesh(y_count_max, x_count_max, |line| draw_func(b, line)) 205 }) 206 } 207 208 /// Get the range of X of the guest coordinate for current drawing area get_x_range(&self) -> Range<X::ValueType>209 pub fn get_x_range(&self) -> Range<X::ValueType> { 210 self.coord.get_x_range() 211 } 212 213 /// Get the range of Y of the guest coordinate for current drawing area get_y_range(&self) -> Range<Y::ValueType>214 pub fn get_y_range(&self) -> Range<Y::ValueType> { 215 self.coord.get_y_range() 216 } 217 218 /// Get the range of X of the backend coordinate for current drawing area get_x_axis_pixel_range(&self) -> Range<i32>219 pub fn get_x_axis_pixel_range(&self) -> Range<i32> { 220 self.coord.get_x_axis_pixel_range() 221 } 222 223 /// Get the range of Y of the backend coordinate for current drawing area get_y_axis_pixel_range(&self) -> Range<i32>224 pub fn get_y_axis_pixel_range(&self) -> Range<i32> { 225 self.coord.get_y_axis_pixel_range() 226 } 227 } 228 229 impl<DB: DrawingBackend, CT: CoordTranslate> DrawingArea<DB, CT> { 230 /// Get the left upper conner of this area in the drawing backend get_base_pixel(&self) -> BackendCoord231 pub fn get_base_pixel(&self) -> BackendCoord { 232 (self.rect.x0, self.rect.y0) 233 } 234 235 /// Strip the applied coordinate specification and returns a shift-based drawing area strip_coord_spec(&self) -> DrawingArea<DB, Shift>236 pub fn strip_coord_spec(&self) -> DrawingArea<DB, Shift> { 237 DrawingArea { 238 rect: self.rect.clone(), 239 backend: self.backend.clone(), 240 coord: Shift((self.rect.x0, self.rect.y0)), 241 } 242 } 243 244 /// Strip the applied coordinate specification and returns a drawing area use_screen_coord(&self) -> DrawingArea<DB, Shift>245 pub fn use_screen_coord(&self) -> DrawingArea<DB, Shift> { 246 DrawingArea { 247 rect: self.rect.clone(), 248 backend: self.backend.clone(), 249 coord: Shift((0, 0)), 250 } 251 } 252 253 /// Get the area dimension in pixel dim_in_pixel(&self) -> (u32, u32)254 pub fn dim_in_pixel(&self) -> (u32, u32) { 255 ( 256 (self.rect.x1 - self.rect.x0) as u32, 257 (self.rect.y1 - self.rect.y0) as u32, 258 ) 259 } 260 261 /// Compute the relative size based on the drawing area's height relative_to_height(&self, p: f64) -> f64262 pub fn relative_to_height(&self, p: f64) -> f64 { 263 f64::from((self.rect.y1 - self.rect.y0).max(0)) * (p.min(1.0).max(0.0)) 264 } 265 266 /// Compute the relative size based on the drawing area's width relative_to_width(&self, p: f64) -> f64267 pub fn relative_to_width(&self, p: f64) -> f64 { 268 f64::from((self.rect.x1 - self.rect.x0).max(0)) * (p.min(1.0).max(0.0)) 269 } 270 271 /// Get the pixel range of this area get_pixel_range(&self) -> (Range<i32>, Range<i32>)272 pub fn get_pixel_range(&self) -> (Range<i32>, Range<i32>) { 273 (self.rect.x0..self.rect.x1, self.rect.y0..self.rect.y1) 274 } 275 276 /// Perform operation on the drawing backend backend_ops<R, O: FnOnce(&mut DB) -> Result<R, DrawingErrorKind<DB::ErrorType>>>( &self, ops: O, ) -> Result<R, DrawingAreaError<DB>>277 fn backend_ops<R, O: FnOnce(&mut DB) -> Result<R, DrawingErrorKind<DB::ErrorType>>>( 278 &self, 279 ops: O, 280 ) -> Result<R, DrawingAreaError<DB>> { 281 if let Ok(mut db) = self.backend.try_borrow_mut() { 282 db.ensure_prepared() 283 .map_err(DrawingAreaErrorKind::BackendError)?; 284 ops(&mut db).map_err(DrawingAreaErrorKind::BackendError) 285 } else { 286 Err(DrawingAreaErrorKind::SharingError) 287 } 288 } 289 290 /// Fill the entire drawing area with a color fill<ColorType: Color>(&self, color: &ColorType) -> Result<(), DrawingAreaError<DB>>291 pub fn fill<ColorType: Color>(&self, color: &ColorType) -> Result<(), DrawingAreaError<DB>> { 292 self.backend_ops(|backend| { 293 backend.draw_rect( 294 (self.rect.x0, self.rect.y0), 295 (self.rect.x1, self.rect.y1), 296 &color.to_backend_color(), 297 true, 298 ) 299 }) 300 } 301 302 /// Draw a single pixel draw_pixel<ColorType: Color>( &self, pos: CT::From, color: &ColorType, ) -> Result<(), DrawingAreaError<DB>>303 pub fn draw_pixel<ColorType: Color>( 304 &self, 305 pos: CT::From, 306 color: &ColorType, 307 ) -> Result<(), DrawingAreaError<DB>> { 308 let pos = self.coord.translate(&pos); 309 self.backend_ops(|b| b.draw_pixel(pos, color.to_backend_color())) 310 } 311 312 /// Present all the pending changes to the backend present(&self) -> Result<(), DrawingAreaError<DB>>313 pub fn present(&self) -> Result<(), DrawingAreaError<DB>> { 314 self.backend_ops(|b| b.present()) 315 } 316 317 /// Draw an high-level element draw<'a, E, B>(&self, element: &'a E) -> Result<(), DrawingAreaError<DB>> where B: CoordMapper, &'a E: PointCollection<'a, CT::From, B>, E: Drawable<DB, B>,318 pub fn draw<'a, E, B>(&self, element: &'a E) -> Result<(), DrawingAreaError<DB>> 319 where 320 B: CoordMapper, 321 &'a E: PointCollection<'a, CT::From, B>, 322 E: Drawable<DB, B>, 323 { 324 let backend_coords = element.point_iter().into_iter().map(|p| { 325 let b = p.borrow(); 326 B::map(&self.coord, b, &self.rect) 327 }); 328 self.backend_ops(move |b| element.draw(backend_coords, b, self.dim_in_pixel())) 329 } 330 331 /// Map coordinate to the backend coordinate map_coordinate(&self, coord: &CT::From) -> BackendCoord332 pub fn map_coordinate(&self, coord: &CT::From) -> BackendCoord { 333 self.coord.translate(coord) 334 } 335 336 /// Estimate the dimension of the text if drawn on this drawing area. 337 /// We can't get this directly from the font, since the drawing backend may or may not 338 /// follows the font configuration. In terminal, the font family will be dropped. 339 /// So the size of the text is drawing area related. 340 /// 341 /// - `text`: The text we want to estimate 342 /// - `font`: The font spec in which we want to draw the text 343 /// - **return**: The size of the text if drawn on this area estimate_text_size( &self, text: &str, style: &TextStyle, ) -> Result<(u32, u32), DrawingAreaError<DB>>344 pub fn estimate_text_size( 345 &self, 346 text: &str, 347 style: &TextStyle, 348 ) -> Result<(u32, u32), DrawingAreaError<DB>> { 349 self.backend_ops(move |b| b.estimate_text_size(text, style)) 350 } 351 } 352 353 impl<DB: DrawingBackend> DrawingArea<DB, Shift> { with_rc_cell(backend: Rc<RefCell<DB>>) -> Self354 fn with_rc_cell(backend: Rc<RefCell<DB>>) -> Self { 355 let (x1, y1) = RefCell::borrow(backend.borrow()).get_size(); 356 Self { 357 rect: Rect { 358 x0: 0, 359 y0: 0, 360 x1: x1 as i32, 361 y1: y1 as i32, 362 }, 363 backend, 364 coord: Shift((0, 0)), 365 } 366 } 367 368 /// Shrink the region, note all the locations are in guest coordinate shrink<A: SizeDesc, B: SizeDesc, C: SizeDesc, D: SizeDesc>( mut self, left_upper: (A, B), dimension: (C, D), ) -> DrawingArea<DB, Shift>369 pub fn shrink<A: SizeDesc, B: SizeDesc, C: SizeDesc, D: SizeDesc>( 370 mut self, 371 left_upper: (A, B), 372 dimension: (C, D), 373 ) -> DrawingArea<DB, Shift> { 374 let left_upper = (left_upper.0.in_pixels(&self), left_upper.1.in_pixels(&self)); 375 let dimension = (dimension.0.in_pixels(&self), dimension.1.in_pixels(&self)); 376 self.rect.x0 = self.rect.x1.min(self.rect.x0 + left_upper.0); 377 self.rect.y0 = self.rect.y1.min(self.rect.y0 + left_upper.1); 378 379 self.rect.x1 = self.rect.x0.max(self.rect.x0 + dimension.0); 380 self.rect.y1 = self.rect.y0.max(self.rect.y0 + dimension.1); 381 382 self.coord = Shift((self.rect.x0, self.rect.y0)); 383 384 self 385 } 386 387 /// Apply a new coord transformation object and returns a new drawing area apply_coord_spec<CT: CoordTranslate>(&self, coord_spec: CT) -> DrawingArea<DB, CT>388 pub fn apply_coord_spec<CT: CoordTranslate>(&self, coord_spec: CT) -> DrawingArea<DB, CT> { 389 DrawingArea { 390 rect: self.rect.clone(), 391 backend: self.backend.clone(), 392 coord: coord_spec, 393 } 394 } 395 396 /// Create a margin for the given drawing area and returns the new drawing area margin<ST: SizeDesc, SB: SizeDesc, SL: SizeDesc, SR: SizeDesc>( &self, top: ST, bottom: SB, left: SL, right: SR, ) -> DrawingArea<DB, Shift>397 pub fn margin<ST: SizeDesc, SB: SizeDesc, SL: SizeDesc, SR: SizeDesc>( 398 &self, 399 top: ST, 400 bottom: SB, 401 left: SL, 402 right: SR, 403 ) -> DrawingArea<DB, Shift> { 404 let left = left.in_pixels(self); 405 let right = right.in_pixels(self); 406 let top = top.in_pixels(self); 407 let bottom = bottom.in_pixels(self); 408 DrawingArea { 409 rect: Rect { 410 x0: self.rect.x0 + left, 411 y0: self.rect.y0 + top, 412 x1: self.rect.x1 - right, 413 y1: self.rect.y1 - bottom, 414 }, 415 backend: self.backend.clone(), 416 coord: Shift((self.rect.x0 + left, self.rect.y0 + top)), 417 } 418 } 419 420 /// Split the drawing area vertically split_vertically<S: SizeDesc>(&self, y: S) -> (Self, Self)421 pub fn split_vertically<S: SizeDesc>(&self, y: S) -> (Self, Self) { 422 let y = y.in_pixels(self); 423 let split_point = [y + self.rect.y0]; 424 let mut ret = self.rect.split(split_point.iter(), true).map(|rect| Self { 425 rect: rect.clone(), 426 backend: self.backend.clone(), 427 coord: Shift((rect.x0, rect.y0)), 428 }); 429 430 (ret.next().unwrap(), ret.next().unwrap()) 431 } 432 433 /// Split the drawing area horizontally split_horizontally<S: SizeDesc>(&self, x: S) -> (Self, Self)434 pub fn split_horizontally<S: SizeDesc>(&self, x: S) -> (Self, Self) { 435 let x = x.in_pixels(self); 436 let split_point = [x + self.rect.x0]; 437 let mut ret = self.rect.split(split_point.iter(), false).map(|rect| Self { 438 rect: rect.clone(), 439 backend: self.backend.clone(), 440 coord: Shift((rect.x0, rect.y0)), 441 }); 442 443 (ret.next().unwrap(), ret.next().unwrap()) 444 } 445 446 /// Split the drawing area evenly split_evenly(&self, (row, col): (usize, usize)) -> Vec<Self>447 pub fn split_evenly(&self, (row, col): (usize, usize)) -> Vec<Self> { 448 self.rect 449 .split_evenly((row, col)) 450 .map(|rect| Self { 451 rect: rect.clone(), 452 backend: self.backend.clone(), 453 coord: Shift((rect.x0, rect.y0)), 454 }) 455 .collect() 456 } 457 458 /// Split the drawing area into a grid with specified breakpoints on both X axis and Y axis split_by_breakpoints< XSize: SizeDesc, YSize: SizeDesc, XS: AsRef<[XSize]>, YS: AsRef<[YSize]>, >( &self, xs: XS, ys: YS, ) -> Vec<Self>459 pub fn split_by_breakpoints< 460 XSize: SizeDesc, 461 YSize: SizeDesc, 462 XS: AsRef<[XSize]>, 463 YS: AsRef<[YSize]>, 464 >( 465 &self, 466 xs: XS, 467 ys: YS, 468 ) -> Vec<Self> { 469 self.rect 470 .split_grid( 471 xs.as_ref().iter().map(|x| x.in_pixels(self)), 472 ys.as_ref().iter().map(|x| x.in_pixels(self)), 473 ) 474 .map(|rect| Self { 475 rect: rect.clone(), 476 backend: self.backend.clone(), 477 coord: Shift((rect.x0, rect.y0)), 478 }) 479 .collect() 480 } 481 482 /// Draw a title of the drawing area and return the remaining drawing area titled<'a, S: Into<TextStyle<'a>>>( &self, text: &str, style: S, ) -> Result<Self, DrawingAreaError<DB>>483 pub fn titled<'a, S: Into<TextStyle<'a>>>( 484 &self, 485 text: &str, 486 style: S, 487 ) -> Result<Self, DrawingAreaError<DB>> { 488 let style = style.into(); 489 490 let x_padding = (self.rect.x1 - self.rect.x0) / 2; 491 492 let (_, text_h) = self.estimate_text_size(text, &style)?; 493 let y_padding = (text_h / 2).min(5) as i32; 494 495 let style = &style.pos(Pos::new(HPos::Center, VPos::Top)); 496 497 self.backend_ops(|b| { 498 b.draw_text( 499 text, 500 style, 501 (self.rect.x0 + x_padding, self.rect.y0 + y_padding), 502 ) 503 })?; 504 505 Ok(Self { 506 rect: Rect { 507 x0: self.rect.x0, 508 y0: self.rect.y0 + y_padding * 2 + text_h as i32, 509 x1: self.rect.x1, 510 y1: self.rect.y1, 511 }, 512 backend: self.backend.clone(), 513 coord: Shift((self.rect.x0, self.rect.y0 + y_padding * 2 + text_h as i32)), 514 }) 515 } 516 517 /// Draw text on the drawing area draw_text( &self, text: &str, style: &TextStyle, pos: BackendCoord, ) -> Result<(), DrawingAreaError<DB>>518 pub fn draw_text( 519 &self, 520 text: &str, 521 style: &TextStyle, 522 pos: BackendCoord, 523 ) -> Result<(), DrawingAreaError<DB>> { 524 self.backend_ops(|b| b.draw_text(text, style, (pos.0 + self.rect.x0, pos.1 + self.rect.y0))) 525 } 526 } 527 528 impl<DB: DrawingBackend, CT: CoordTranslate> DrawingArea<DB, CT> { 529 /// Returns the coordinates by value into_coord_spec(self) -> CT530 pub fn into_coord_spec(self) -> CT { 531 self.coord 532 } 533 534 /// Returns the coordinates by reference as_coord_spec(&self) -> &CT535 pub fn as_coord_spec(&self) -> &CT { 536 &self.coord 537 } 538 539 /// Returns the coordinates by mutable reference as_coord_spec_mut(&mut self) -> &mut CT540 pub fn as_coord_spec_mut(&mut self) -> &mut CT { 541 &mut self.coord 542 } 543 } 544 545 #[cfg(test)] 546 mod drawing_area_tests { 547 use crate::{create_mocked_drawing_area, prelude::*}; 548 #[test] test_filling()549 fn test_filling() { 550 let drawing_area = create_mocked_drawing_area(1024, 768, |m| { 551 m.check_draw_rect(|c, _, f, u, d| { 552 assert_eq!(c, WHITE.to_rgba()); 553 assert_eq!(f, true); 554 assert_eq!(u, (0, 0)); 555 assert_eq!(d, (1024, 768)); 556 }); 557 558 m.drop_check(|b| { 559 assert_eq!(b.num_draw_rect_call, 1); 560 assert_eq!(b.draw_count, 1); 561 }); 562 }); 563 564 drawing_area.fill(&WHITE).expect("Drawing Failure"); 565 } 566 567 #[test] test_split_evenly()568 fn test_split_evenly() { 569 let colors = vec![ 570 &RED, &BLUE, &YELLOW, &WHITE, &BLACK, &MAGENTA, &CYAN, &BLUE, &RED, 571 ]; 572 let drawing_area = create_mocked_drawing_area(902, 900, |m| { 573 for col in 0..3 { 574 for row in 0..3 { 575 let colors = colors.clone(); 576 m.check_draw_rect(move |c, _, f, u, d| { 577 assert_eq!(c, colors[col * 3 + row].to_rgba()); 578 assert_eq!(f, true); 579 assert_eq!(u, (300 * row as i32 + 2.min(row) as i32, 300 * col as i32)); 580 assert_eq!( 581 d, 582 ( 583 300 + 300 * row as i32 + 2.min(row + 1) as i32, 584 300 + 300 * col as i32 585 ) 586 ); 587 }); 588 } 589 } 590 m.drop_check(|b| { 591 assert_eq!(b.num_draw_rect_call, 9); 592 assert_eq!(b.draw_count, 9); 593 }); 594 }); 595 596 drawing_area 597 .split_evenly((3, 3)) 598 .iter_mut() 599 .zip(colors.iter()) 600 .for_each(|(d, c)| { 601 d.fill(*c).expect("Drawing Failure"); 602 }); 603 } 604 605 #[test] test_split_horizontally()606 fn test_split_horizontally() { 607 let drawing_area = create_mocked_drawing_area(1024, 768, |m| { 608 m.check_draw_rect(|c, _, f, u, d| { 609 assert_eq!(c, RED.to_rgba()); 610 assert_eq!(f, true); 611 assert_eq!(u, (0, 0)); 612 assert_eq!(d, (345, 768)); 613 }); 614 615 m.check_draw_rect(|c, _, f, u, d| { 616 assert_eq!(c, BLUE.to_rgba()); 617 assert_eq!(f, true); 618 assert_eq!(u, (345, 0)); 619 assert_eq!(d, (1024, 768)); 620 }); 621 622 m.drop_check(|b| { 623 assert_eq!(b.num_draw_rect_call, 2); 624 assert_eq!(b.draw_count, 2); 625 }); 626 }); 627 628 let (left, right) = drawing_area.split_horizontally(345); 629 left.fill(&RED).expect("Drawing Error"); 630 right.fill(&BLUE).expect("Drawing Error"); 631 } 632 633 #[test] test_split_vertically()634 fn test_split_vertically() { 635 let drawing_area = create_mocked_drawing_area(1024, 768, |m| { 636 m.check_draw_rect(|c, _, f, u, d| { 637 assert_eq!(c, RED.to_rgba()); 638 assert_eq!(f, true); 639 assert_eq!(u, (0, 0)); 640 assert_eq!(d, (1024, 345)); 641 }); 642 643 m.check_draw_rect(|c, _, f, u, d| { 644 assert_eq!(c, BLUE.to_rgba()); 645 assert_eq!(f, true); 646 assert_eq!(u, (0, 345)); 647 assert_eq!(d, (1024, 768)); 648 }); 649 650 m.drop_check(|b| { 651 assert_eq!(b.num_draw_rect_call, 2); 652 assert_eq!(b.draw_count, 2); 653 }); 654 }); 655 656 let (left, right) = drawing_area.split_vertically(345); 657 left.fill(&RED).expect("Drawing Error"); 658 right.fill(&BLUE).expect("Drawing Error"); 659 } 660 661 #[test] test_split_grid()662 fn test_split_grid() { 663 let colors = vec![ 664 &RED, &BLUE, &YELLOW, &WHITE, &BLACK, &MAGENTA, &CYAN, &BLUE, &RED, 665 ]; 666 let breaks: [i32; 5] = [100, 200, 300, 400, 500]; 667 668 for nxb in 0..=5 { 669 for nyb in 0..=5 { 670 let drawing_area = create_mocked_drawing_area(1024, 768, |m| { 671 for row in 0..=nyb { 672 for col in 0..=nxb { 673 let get_bp = |full, limit, id| { 674 (if id == 0 { 675 0 676 } else if id > limit { 677 full 678 } else { 679 breaks[id as usize - 1] 680 }) as i32 681 }; 682 683 let expected_u = (get_bp(1024, nxb, col), get_bp(768, nyb, row)); 684 let expected_d = 685 (get_bp(1024, nxb, col + 1), get_bp(768, nyb, row + 1)); 686 let expected_color = 687 colors[(row * (nxb + 1) + col) as usize % colors.len()]; 688 689 m.check_draw_rect(move |c, _, f, u, d| { 690 assert_eq!(c, expected_color.to_rgba()); 691 assert_eq!(f, true); 692 assert_eq!(u, expected_u); 693 assert_eq!(d, expected_d); 694 }); 695 } 696 } 697 698 m.drop_check(move |b| { 699 assert_eq!(b.num_draw_rect_call, ((nxb + 1) * (nyb + 1)) as u32); 700 assert_eq!(b.draw_count, ((nyb + 1) * (nxb + 1)) as u32); 701 }); 702 }); 703 704 let result = drawing_area 705 .split_by_breakpoints(&breaks[0..nxb as usize], &breaks[0..nyb as usize]); 706 for i in 0..result.len() { 707 result[i] 708 .fill(colors[i % colors.len()]) 709 .expect("Drawing Error"); 710 } 711 } 712 } 713 } 714 #[test] test_titled()715 fn test_titled() { 716 let drawing_area = create_mocked_drawing_area(1024, 768, |m| { 717 m.check_draw_text(|c, font, size, _pos, text| { 718 assert_eq!(c, BLACK.to_rgba()); 719 assert_eq!(font, "serif"); 720 assert_eq!(size, 30.0); 721 assert_eq!("This is the title", text); 722 }); 723 m.check_draw_rect(|c, _, f, u, d| { 724 assert_eq!(c, WHITE.to_rgba()); 725 assert_eq!(f, true); 726 assert_eq!(u.0, 0); 727 assert!(u.1 > 0); 728 assert_eq!(d, (1024, 768)); 729 }); 730 m.drop_check(|b| { 731 assert_eq!(b.num_draw_text_call, 1); 732 assert_eq!(b.num_draw_rect_call, 1); 733 assert_eq!(b.draw_count, 2); 734 }); 735 }); 736 737 drawing_area 738 .titled("This is the title", ("serif", 30)) 739 .unwrap() 740 .fill(&WHITE) 741 .unwrap(); 742 } 743 744 #[test] test_margin()745 fn test_margin() { 746 let drawing_area = create_mocked_drawing_area(1024, 768, |m| { 747 m.check_draw_rect(|c, _, f, u, d| { 748 assert_eq!(c, WHITE.to_rgba()); 749 assert_eq!(f, true); 750 assert_eq!(u, (3, 1)); 751 assert_eq!(d, (1024 - 4, 768 - 2)); 752 }); 753 754 m.drop_check(|b| { 755 assert_eq!(b.num_draw_rect_call, 1); 756 assert_eq!(b.draw_count, 1); 757 }); 758 }); 759 760 drawing_area 761 .margin(1, 2, 3, 4) 762 .fill(&WHITE) 763 .expect("Drawing Failure"); 764 } 765 766 #[test] test_ranges()767 fn test_ranges() { 768 let drawing_area = create_mocked_drawing_area(1024, 768, |_m| {}) 769 .apply_coord_spec(Cartesian2d::< 770 crate::coord::types::RangedCoordi32, 771 crate::coord::types::RangedCoordu32, 772 >::new(-100..100, 0..200, (0..1024, 0..768))); 773 774 let x_range = drawing_area.get_x_range(); 775 assert_eq!(x_range, -100..100); 776 777 let y_range = drawing_area.get_y_range(); 778 assert_eq!(y_range, 0..200); 779 } 780 781 #[test] test_relative_size()782 fn test_relative_size() { 783 let drawing_area = create_mocked_drawing_area(1024, 768, |_m| {}); 784 785 assert_eq!(102.4, drawing_area.relative_to_width(0.1)); 786 assert_eq!(384.0, drawing_area.relative_to_height(0.5)); 787 788 assert_eq!(1024.0, drawing_area.relative_to_width(1.3)); 789 assert_eq!(768.0, drawing_area.relative_to_height(1.5)); 790 791 assert_eq!(0.0, drawing_area.relative_to_width(-0.2)); 792 assert_eq!(0.0, drawing_area.relative_to_height(-0.5)); 793 } 794 795 #[test] test_relative_split()796 fn test_relative_split() { 797 let drawing_area = create_mocked_drawing_area(1000, 1200, |m| { 798 let mut counter = 0; 799 m.check_draw_rect(move |c, _, f, u, d| { 800 assert_eq!(f, true); 801 802 match counter { 803 0 => { 804 assert_eq!(c, RED.to_rgba()); 805 assert_eq!(u, (0, 0)); 806 assert_eq!(d, (300, 600)); 807 } 808 1 => { 809 assert_eq!(c, BLUE.to_rgba()); 810 assert_eq!(u, (300, 0)); 811 assert_eq!(d, (1000, 600)); 812 } 813 2 => { 814 assert_eq!(c, GREEN.to_rgba()); 815 assert_eq!(u, (0, 600)); 816 assert_eq!(d, (300, 1200)); 817 } 818 3 => { 819 assert_eq!(c, WHITE.to_rgba()); 820 assert_eq!(u, (300, 600)); 821 assert_eq!(d, (1000, 1200)); 822 } 823 _ => panic!("Too many draw rect"), 824 } 825 826 counter += 1; 827 }); 828 829 m.drop_check(|b| { 830 assert_eq!(b.num_draw_rect_call, 4); 831 assert_eq!(b.draw_count, 4); 832 }); 833 }); 834 835 let split = 836 drawing_area.split_by_breakpoints([(30).percent_width()], [(50).percent_height()]); 837 838 split[0].fill(&RED).unwrap(); 839 split[1].fill(&BLUE).unwrap(); 840 split[2].fill(&GREEN).unwrap(); 841 split[3].fill(&WHITE).unwrap(); 842 } 843 844 #[test] test_relative_shrink()845 fn test_relative_shrink() { 846 let drawing_area = create_mocked_drawing_area(1000, 1200, |m| { 847 m.check_draw_rect(move |_, _, _, u, d| { 848 assert_eq!((100, 100), u); 849 assert_eq!((300, 700), d); 850 }); 851 852 m.drop_check(|b| { 853 assert_eq!(b.num_draw_rect_call, 1); 854 assert_eq!(b.draw_count, 1); 855 }); 856 }) 857 .shrink(((10).percent_width(), 100), (200, (50).percent_height())); 858 859 drawing_area.fill(&RED).unwrap(); 860 } 861 } 862