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