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