1use crate::coord::cartesian::{Cartesian2d, MeshLine};
2use crate::coord::ranged1d::{KeyPointHint, Ranged};
3use crate::coord::{CoordTranslate, Shift};
4use crate::element::{CoordMapper, Drawable, PointCollection};
5use crate::style::text_anchor::{HPos, Pos, VPos};
6use crate::style::{Color, SizeDesc, TextStyle};
7
8/// The abstraction of a drawing area
9use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
10
11use std::borrow::Borrow;
12use std::cell::RefCell;
13use std::error::Error;
14use std::iter::{once, repeat};
15use std::ops::Range;
16use std::rc::Rc;
17
18/// The representation of the rectangle in backend canvas
19#[derive(Clone, Debug)]
20pub struct Rect {
21 x0: i32,
22 y0: i32,
23 x1: i32,
24 y1: i32,
25}
26
27impl Rect {
28 /// Split the rectangle into a few smaller rectangles
29 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
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.
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
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.
119pub struct DrawingArea<DB: DrawingBackend, CT: CoordTranslate> {
120 backend: Rc<RefCell<DB>>,
121 rect: Rect,
122 coord: CT,
123}
124
125impl<DB: DrawingBackend, CT: CoordTranslate + Clone> Clone for DrawingArea<DB, CT> {
126 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)]
137pub 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
148impl<E: Error + Send + Sync> std::fmt::Display for DrawingAreaErrorKind<E> {
149 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
150 match self {
151 DrawingAreaErrorKind::BackendError(e: &DrawingErrorKind) => 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
160impl<E: Error + Send + Sync> Error for DrawingAreaErrorKind<E> {}
161
162#[allow(type_alias_bounds)]
163type DrawingAreaError<T: DrawingBackend> = DrawingAreaErrorKind<T::ErrorType>;
164
165impl<DB: DrawingBackend> From<DB> for DrawingArea<DB, Shift> {
166 fn from(backend: DB) -> Self {
167 Self::with_rc_cell(backend:Rc::new(RefCell::new(backend)))
168 }
169}
170
171impl<'a, DB: DrawingBackend> From<&'a Rc<RefCell<DB>>> for DrawingArea<DB, Shift> {
172 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
178pub trait IntoDrawingArea: DrawingBackend + Sized {
179 /// Convert the type into a root drawing area
180 fn into_drawing_area(self) -> DrawingArea<Self, Shift>;
181}
182
183impl<T: DrawingBackend> IntoDrawingArea for T {
184 fn into_drawing_area(self) -> DrawingArea<T, Shift> {
185 self.into()
186 }
187}
188
189impl<DB: DrawingBackend, X: Ranged, Y: Ranged> DrawingArea<DB, Cartesian2d<X, Y>> {
190 /// Draw the mesh on a area
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
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
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
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
222 pub fn get_y_axis_pixel_range(&self) -> Range<i32> {
223 self.coord.get_y_axis_pixel_range()
224 }
225}
226
227impl<DB: DrawingBackend, CT: CoordTranslate> DrawingArea<DB, CT> {
228 /// Get the left upper conner of this area in the drawing backend
229 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
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
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
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
260 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
265 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
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
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
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
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
311 pub fn present(&self) -> Result<(), DrawingAreaError<DB>> {
312 self.backend_ops(|b| b.present())
313 }
314
315 /// Draw an high-level element
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
330 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
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
351impl<DB: DrawingBackend> DrawingArea<DB, Shift> {
352 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
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
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
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
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
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
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
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
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
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
526impl<DB: DrawingBackend, CT: CoordTranslate> DrawingArea<DB, CT> {
527 /// Returns the coordinates by value
528 pub fn into_coord_spec(self) -> CT {
529 self.coord
530 }
531
532 /// Returns the coordinates by reference
533 pub fn as_coord_spec(&self) -> &CT {
534 &self.coord
535 }
536
537 /// Returns the coordinates by mutable reference
538 pub fn as_coord_spec_mut(&mut self) -> &mut CT {
539 &mut self.coord
540 }
541}
542
543#[cfg(test)]
544mod drawing_area_tests {
545 use crate::{create_mocked_drawing_area, prelude::*};
546 #[test]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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