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<'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 pub 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.to_backend_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.to_backend_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, B>(&self, element: &'a E) -> Result<(), DrawingAreaError<DB>> where B: CoordMapper, &'a E: PointCollection<'a, CT::From, B>, E: Drawable<DB, B>,311 pub fn draw<'a, E, B>(&self, element: &'a E) -> Result<(), DrawingAreaError<DB>> 312 where 313 B: CoordMapper, 314 &'a E: PointCollection<'a, CT::From, B>, 315 E: Drawable<DB, B>, 316 { 317 let backend_coords = element.point_iter().into_iter().map(|p| { 318 let b = p.borrow(); 319 B::map(&self.coord, b, &self.rect) 320 }); 321 self.backend_ops(move |b| element.draw(backend_coords, b, self.dim_in_pixel())) 322 } 323 324 /// Map coordinate to the backend coordinate map_coordinate(&self, coord: &CT::From) -> BackendCoord325 pub fn map_coordinate(&self, coord: &CT::From) -> BackendCoord { 326 self.coord.translate(coord) 327 } 328 329 /// Estimate the dimension of the text if drawn on this drawing area. 330 /// We can't get this directly from the font, since the drawing backend may or may not 331 /// follows the font configuration. In terminal, the font family will be dropped. 332 /// So the size of the text is drawing area related. 333 /// 334 /// - `text`: The text we want to estimate 335 /// - `font`: The font spec in which we want to draw the text 336 /// - **return**: The size of the text if drawn on this area estimate_text_size( &self, text: &str, style: &TextStyle, ) -> Result<(u32, u32), DrawingAreaError<DB>>337 pub fn estimate_text_size( 338 &self, 339 text: &str, 340 style: &TextStyle, 341 ) -> Result<(u32, u32), DrawingAreaError<DB>> { 342 self.backend_ops(move |b| b.estimate_text_size(text, style)) 343 } 344 } 345 346 impl<DB: DrawingBackend> DrawingArea<DB, Shift> { with_rc_cell(backend: Rc<RefCell<DB>>) -> Self347 fn with_rc_cell(backend: Rc<RefCell<DB>>) -> Self { 348 let (x1, y1) = RefCell::borrow(backend.borrow()).get_size(); 349 Self { 350 rect: Rect { 351 x0: 0, 352 y0: 0, 353 x1: x1 as i32, 354 y1: y1 as i32, 355 }, 356 backend, 357 coord: Shift((0, 0)), 358 } 359 } 360 361 /// 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>362 pub fn shrink<A: SizeDesc, B: SizeDesc, C: SizeDesc, D: SizeDesc>( 363 mut self, 364 left_upper: (A, B), 365 dimension: (C, D), 366 ) -> DrawingArea<DB, Shift> { 367 let left_upper = (left_upper.0.in_pixels(&self), left_upper.1.in_pixels(&self)); 368 let dimension = (dimension.0.in_pixels(&self), dimension.1.in_pixels(&self)); 369 self.rect.x0 = self.rect.x1.min(self.rect.x0 + left_upper.0); 370 self.rect.y0 = self.rect.y1.min(self.rect.y0 + left_upper.1); 371 372 self.rect.x1 = self.rect.x0.max(self.rect.x0 + dimension.0); 373 self.rect.y1 = self.rect.y0.max(self.rect.y0 + dimension.1); 374 375 self.coord = Shift((self.rect.x0, self.rect.y0)); 376 377 self 378 } 379 380 /// Apply a new coord transformation object and returns a new drawing area apply_coord_spec<CT: CoordTranslate>(&self, coord_spec: CT) -> DrawingArea<DB, CT>381 pub fn apply_coord_spec<CT: CoordTranslate>(&self, coord_spec: CT) -> DrawingArea<DB, CT> { 382 DrawingArea { 383 rect: self.rect.clone(), 384 backend: self.backend.clone(), 385 coord: coord_spec, 386 } 387 } 388 389 /// 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>390 pub fn margin<ST: SizeDesc, SB: SizeDesc, SL: SizeDesc, SR: SizeDesc>( 391 &self, 392 top: ST, 393 bottom: SB, 394 left: SL, 395 right: SR, 396 ) -> DrawingArea<DB, Shift> { 397 let left = left.in_pixels(self); 398 let right = right.in_pixels(self); 399 let top = top.in_pixels(self); 400 let bottom = bottom.in_pixels(self); 401 DrawingArea { 402 rect: Rect { 403 x0: self.rect.x0 + left, 404 y0: self.rect.y0 + top, 405 x1: self.rect.x1 - right, 406 y1: self.rect.y1 - bottom, 407 }, 408 backend: self.backend.clone(), 409 coord: Shift((self.rect.x0 + left, self.rect.y0 + top)), 410 } 411 } 412 413 /// Split the drawing area vertically split_vertically<S: SizeDesc>(&self, y: S) -> (Self, Self)414 pub fn split_vertically<S: SizeDesc>(&self, y: S) -> (Self, Self) { 415 let y = y.in_pixels(self); 416 let split_point = [y + self.rect.y0]; 417 let mut ret = self.rect.split(split_point.iter(), true).map(|rect| Self { 418 rect: rect.clone(), 419 backend: self.backend.clone(), 420 coord: Shift((rect.x0, rect.y0)), 421 }); 422 423 (ret.next().unwrap(), ret.next().unwrap()) 424 } 425 426 /// Split the drawing area horizontally split_horizontally<S: SizeDesc>(&self, x: S) -> (Self, Self)427 pub fn split_horizontally<S: SizeDesc>(&self, x: S) -> (Self, Self) { 428 let x = x.in_pixels(self); 429 let split_point = [x + self.rect.x0]; 430 let mut ret = self.rect.split(split_point.iter(), false).map(|rect| Self { 431 rect: rect.clone(), 432 backend: self.backend.clone(), 433 coord: Shift((rect.x0, rect.y0)), 434 }); 435 436 (ret.next().unwrap(), ret.next().unwrap()) 437 } 438 439 /// Split the drawing area evenly split_evenly(&self, (row, col): (usize, usize)) -> Vec<Self>440 pub fn split_evenly(&self, (row, col): (usize, usize)) -> Vec<Self> { 441 self.rect 442 .split_evenly((row, col)) 443 .map(|rect| Self { 444 rect: rect.clone(), 445 backend: self.backend.clone(), 446 coord: Shift((rect.x0, rect.y0)), 447 }) 448 .collect() 449 } 450 451 /// 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>452 pub fn split_by_breakpoints< 453 XSize: SizeDesc, 454 YSize: SizeDesc, 455 XS: AsRef<[XSize]>, 456 YS: AsRef<[YSize]>, 457 >( 458 &self, 459 xs: XS, 460 ys: YS, 461 ) -> Vec<Self> { 462 self.rect 463 .split_grid( 464 xs.as_ref().iter().map(|x| x.in_pixels(self)), 465 ys.as_ref().iter().map(|x| x.in_pixels(self)), 466 ) 467 .map(|rect| Self { 468 rect: rect.clone(), 469 backend: self.backend.clone(), 470 coord: Shift((rect.x0, rect.y0)), 471 }) 472 .collect() 473 } 474 475 /// 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>>476 pub fn titled<'a, S: Into<TextStyle<'a>>>( 477 &self, 478 text: &str, 479 style: S, 480 ) -> Result<Self, DrawingAreaError<DB>> { 481 let style = style.into(); 482 483 let x_padding = (self.rect.x1 - self.rect.x0) / 2; 484 485 let (_, text_h) = self.estimate_text_size(text, &style)?; 486 let y_padding = (text_h / 2).min(5) as i32; 487 488 let style = &style.pos(Pos::new(HPos::Center, VPos::Top)); 489 490 self.backend_ops(|b| { 491 b.draw_text( 492 text, 493 style, 494 (self.rect.x0 + x_padding, self.rect.y0 + y_padding), 495 ) 496 })?; 497 498 Ok(Self { 499 rect: Rect { 500 x0: self.rect.x0, 501 y0: self.rect.y0 + y_padding * 2 + text_h as i32, 502 x1: self.rect.x1, 503 y1: self.rect.y1, 504 }, 505 backend: self.backend.clone(), 506 coord: Shift((self.rect.x0, self.rect.y0 + y_padding * 2 + text_h as i32)), 507 }) 508 } 509 510 /// Draw text on the drawing area draw_text( &self, text: &str, style: &TextStyle, pos: BackendCoord, ) -> Result<(), DrawingAreaError<DB>>511 pub fn draw_text( 512 &self, 513 text: &str, 514 style: &TextStyle, 515 pos: BackendCoord, 516 ) -> Result<(), DrawingAreaError<DB>> { 517 self.backend_ops(|b| b.draw_text(text, style, (pos.0 + self.rect.x0, pos.1 + self.rect.y0))) 518 } 519 } 520 521 impl<DB: DrawingBackend, CT: CoordTranslate> DrawingArea<DB, CT> { into_coord_spec(self) -> CT522 pub fn into_coord_spec(self) -> CT { 523 self.coord 524 } 525 as_coord_spec(&self) -> &CT526 pub fn as_coord_spec(&self) -> &CT { 527 &self.coord 528 } 529 as_coord_spec_mut(&mut self) -> &mut CT530 pub fn as_coord_spec_mut(&mut self) -> &mut CT { 531 &mut self.coord 532 } 533 } 534 535 #[cfg(test)] 536 mod drawing_area_tests { 537 use crate::{create_mocked_drawing_area, prelude::*}; 538 #[test] test_filling()539 fn test_filling() { 540 let drawing_area = create_mocked_drawing_area(1024, 768, |m| { 541 m.check_draw_rect(|c, _, f, u, d| { 542 assert_eq!(c, WHITE.to_rgba()); 543 assert_eq!(f, true); 544 assert_eq!(u, (0, 0)); 545 assert_eq!(d, (1023, 767)); 546 }); 547 548 m.drop_check(|b| { 549 assert_eq!(b.num_draw_rect_call, 1); 550 assert_eq!(b.draw_count, 1); 551 }); 552 }); 553 554 drawing_area.fill(&WHITE).expect("Drawing Failure"); 555 } 556 557 #[test] test_split_evenly()558 fn test_split_evenly() { 559 let colors = vec![ 560 &RED, &BLUE, &YELLOW, &WHITE, &BLACK, &MAGENTA, &CYAN, &BLUE, &RED, 561 ]; 562 let drawing_area = create_mocked_drawing_area(902, 900, |m| { 563 for col in 0..3 { 564 for row in 0..3 { 565 let colors = colors.clone(); 566 m.check_draw_rect(move |c, _, f, u, d| { 567 assert_eq!(c, colors[col * 3 + row].to_rgba()); 568 assert_eq!(f, true); 569 assert_eq!(u, (300 * row as i32 + 2.min(row) as i32, 300 * col as i32)); 570 assert_eq!( 571 d, 572 ( 573 300 + 300 * row as i32 + 2.min(row + 1) as i32 - 1, 574 300 + 300 * col as i32 - 1 575 ) 576 ); 577 }); 578 } 579 } 580 m.drop_check(|b| { 581 assert_eq!(b.num_draw_rect_call, 9); 582 assert_eq!(b.draw_count, 9); 583 }); 584 }); 585 586 drawing_area 587 .split_evenly((3, 3)) 588 .iter_mut() 589 .zip(colors.iter()) 590 .for_each(|(d, c)| { 591 d.fill(*c).expect("Drawing Failure"); 592 }); 593 } 594 595 #[test] test_split_horizontally()596 fn test_split_horizontally() { 597 let drawing_area = create_mocked_drawing_area(1024, 768, |m| { 598 m.check_draw_rect(|c, _, f, u, d| { 599 assert_eq!(c, RED.to_rgba()); 600 assert_eq!(f, true); 601 assert_eq!(u, (0, 0)); 602 assert_eq!(d, (345 - 1, 768 - 1)); 603 }); 604 605 m.check_draw_rect(|c, _, f, u, d| { 606 assert_eq!(c, BLUE.to_rgba()); 607 assert_eq!(f, true); 608 assert_eq!(u, (345, 0)); 609 assert_eq!(d, (1024 - 1, 768 - 1)); 610 }); 611 612 m.drop_check(|b| { 613 assert_eq!(b.num_draw_rect_call, 2); 614 assert_eq!(b.draw_count, 2); 615 }); 616 }); 617 618 let (left, right) = drawing_area.split_horizontally(345); 619 left.fill(&RED).expect("Drawing Error"); 620 right.fill(&BLUE).expect("Drawing Error"); 621 } 622 623 #[test] test_split_vertically()624 fn test_split_vertically() { 625 let drawing_area = create_mocked_drawing_area(1024, 768, |m| { 626 m.check_draw_rect(|c, _, f, u, d| { 627 assert_eq!(c, RED.to_rgba()); 628 assert_eq!(f, true); 629 assert_eq!(u, (0, 0)); 630 assert_eq!(d, (1024 - 1, 345 - 1)); 631 }); 632 633 m.check_draw_rect(|c, _, f, u, d| { 634 assert_eq!(c, BLUE.to_rgba()); 635 assert_eq!(f, true); 636 assert_eq!(u, (0, 345)); 637 assert_eq!(d, (1024 - 1, 768 - 1)); 638 }); 639 640 m.drop_check(|b| { 641 assert_eq!(b.num_draw_rect_call, 2); 642 assert_eq!(b.draw_count, 2); 643 }); 644 }); 645 646 let (left, right) = drawing_area.split_vertically(345); 647 left.fill(&RED).expect("Drawing Error"); 648 right.fill(&BLUE).expect("Drawing Error"); 649 } 650 651 #[test] test_split_grid()652 fn test_split_grid() { 653 let colors = vec![ 654 &RED, &BLUE, &YELLOW, &WHITE, &BLACK, &MAGENTA, &CYAN, &BLUE, &RED, 655 ]; 656 let breaks: [i32; 5] = [100, 200, 300, 400, 500]; 657 658 for nxb in 0..=5 { 659 for nyb in 0..=5 { 660 let drawing_area = create_mocked_drawing_area(1024, 768, |m| { 661 for row in 0..=nyb { 662 for col in 0..=nxb { 663 let get_bp = |full, limit, id| { 664 (if id == 0 { 665 0 666 } else if id > limit { 667 full 668 } else { 669 breaks[id as usize - 1] 670 }) as i32 671 }; 672 673 let expected_u = (get_bp(1024, nxb, col), get_bp(768, nyb, row)); 674 let expected_d = ( 675 get_bp(1024, nxb, col + 1) - 1, 676 get_bp(768, nyb, row + 1) - 1, 677 ); 678 let expected_color = 679 colors[(row * (nxb + 1) + col) as usize % colors.len()]; 680 681 m.check_draw_rect(move |c, _, f, u, d| { 682 assert_eq!(c, expected_color.to_rgba()); 683 assert_eq!(f, true); 684 assert_eq!(u, expected_u); 685 assert_eq!(d, expected_d); 686 }); 687 } 688 } 689 690 m.drop_check(move |b| { 691 assert_eq!(b.num_draw_rect_call, ((nxb + 1) * (nyb + 1)) as u32); 692 assert_eq!(b.draw_count, ((nyb + 1) * (nxb + 1)) as u32); 693 }); 694 }); 695 696 let result = drawing_area 697 .split_by_breakpoints(&breaks[0..nxb as usize], &breaks[0..nyb as usize]); 698 for i in 0..result.len() { 699 result[i] 700 .fill(colors[i % colors.len()]) 701 .expect("Drawing Error"); 702 } 703 } 704 } 705 } 706 #[test] test_titled()707 fn test_titled() { 708 let drawing_area = create_mocked_drawing_area(1024, 768, |m| { 709 m.check_draw_text(|c, font, size, _pos, text| { 710 assert_eq!(c, BLACK.to_rgba()); 711 assert_eq!(font, "serif"); 712 assert_eq!(size, 30.0); 713 assert_eq!("This is the title", text); 714 }); 715 m.check_draw_rect(|c, _, f, u, d| { 716 assert_eq!(c, WHITE.to_rgba()); 717 assert_eq!(f, true); 718 assert_eq!(u.0, 0); 719 assert!(u.1 > 0); 720 assert_eq!(d, (1024 - 1, 768 - 1)); 721 }); 722 m.drop_check(|b| { 723 assert_eq!(b.num_draw_text_call, 1); 724 assert_eq!(b.num_draw_rect_call, 1); 725 assert_eq!(b.draw_count, 2); 726 }); 727 }); 728 729 drawing_area 730 .titled("This is the title", ("serif", 30)) 731 .unwrap() 732 .fill(&WHITE) 733 .unwrap(); 734 } 735 736 #[test] test_margin()737 fn test_margin() { 738 let drawing_area = create_mocked_drawing_area(1024, 768, |m| { 739 m.check_draw_rect(|c, _, f, u, d| { 740 assert_eq!(c, WHITE.to_rgba()); 741 assert_eq!(f, true); 742 assert_eq!(u, (3, 1)); 743 assert_eq!(d, (1024 - 4 - 1, 768 - 2 - 1)); 744 }); 745 746 m.drop_check(|b| { 747 assert_eq!(b.num_draw_rect_call, 1); 748 assert_eq!(b.draw_count, 1); 749 }); 750 }); 751 752 drawing_area 753 .margin(1, 2, 3, 4) 754 .fill(&WHITE) 755 .expect("Drawing Failure"); 756 } 757 758 #[test] test_ranges()759 fn test_ranges() { 760 let drawing_area = create_mocked_drawing_area(1024, 768, |_m| {}) 761 .apply_coord_spec(Cartesian2d::< 762 crate::coord::types::RangedCoordi32, 763 crate::coord::types::RangedCoordu32, 764 >::new(-100..100, 0..200, (0..1024, 0..768))); 765 766 let x_range = drawing_area.get_x_range(); 767 assert_eq!(x_range, -100..100); 768 769 let y_range = drawing_area.get_y_range(); 770 assert_eq!(y_range, 0..200); 771 } 772 773 #[test] test_relative_size()774 fn test_relative_size() { 775 let drawing_area = create_mocked_drawing_area(1024, 768, |_m| {}); 776 777 assert_eq!(102.4, drawing_area.relative_to_width(0.1)); 778 assert_eq!(384.0, drawing_area.relative_to_height(0.5)); 779 780 assert_eq!(1024.0, drawing_area.relative_to_width(1.3)); 781 assert_eq!(768.0, drawing_area.relative_to_height(1.5)); 782 783 assert_eq!(0.0, drawing_area.relative_to_width(-0.2)); 784 assert_eq!(0.0, drawing_area.relative_to_height(-0.5)); 785 } 786 787 #[test] test_relative_split()788 fn test_relative_split() { 789 let drawing_area = create_mocked_drawing_area(1000, 1200, |m| { 790 let mut counter = 0; 791 m.check_draw_rect(move |c, _, f, u, d| { 792 assert_eq!(f, true); 793 794 match counter { 795 0 => { 796 assert_eq!(c, RED.to_rgba()); 797 assert_eq!(u, (0, 0)); 798 assert_eq!(d, (300 - 1, 600 - 1)); 799 } 800 1 => { 801 assert_eq!(c, BLUE.to_rgba()); 802 assert_eq!(u, (300, 0)); 803 assert_eq!(d, (1000 - 1, 600 - 1)); 804 } 805 2 => { 806 assert_eq!(c, GREEN.to_rgba()); 807 assert_eq!(u, (0, 600)); 808 assert_eq!(d, (300 - 1, 1200 - 1)); 809 } 810 3 => { 811 assert_eq!(c, WHITE.to_rgba()); 812 assert_eq!(u, (300, 600)); 813 assert_eq!(d, (1000 - 1, 1200 - 1)); 814 } 815 _ => panic!("Too many draw rect"), 816 } 817 818 counter += 1; 819 }); 820 821 m.drop_check(|b| { 822 assert_eq!(b.num_draw_rect_call, 4); 823 assert_eq!(b.draw_count, 4); 824 }); 825 }); 826 827 let split = 828 drawing_area.split_by_breakpoints([(30).percent_width()], [(50).percent_height()]); 829 830 split[0].fill(&RED).unwrap(); 831 split[1].fill(&BLUE).unwrap(); 832 split[2].fill(&GREEN).unwrap(); 833 split[3].fill(&WHITE).unwrap(); 834 } 835 836 #[test] test_relative_shrink()837 fn test_relative_shrink() { 838 let drawing_area = create_mocked_drawing_area(1000, 1200, |m| { 839 m.check_draw_rect(move |_, _, _, u, d| { 840 assert_eq!((100, 100), u); 841 assert_eq!((300 - 1, 700 - 1), d); 842 }); 843 844 m.drop_check(|b| { 845 assert_eq!(b.num_draw_rect_call, 1); 846 assert_eq!(b.draw_count, 1); 847 }); 848 }) 849 .shrink(((10).percent_width(), 100), (200, (50).percent_height())); 850 851 drawing_area.fill(&RED).unwrap(); 852 } 853 } 854