• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use super::{Drawable, PointCollection};
2 use crate::style::{Color, ShapeStyle, SizeDesc};
3 use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
4 
5 #[inline]
to_i((x, y): (f32, f32)) -> (i32, i32)6 fn to_i((x, y): (f32, f32)) -> (i32, i32) {
7     (x.round() as i32, y.round() as i32)
8 }
9 
10 #[inline]
to_f((x, y): (i32, i32)) -> (f32, f32)11 fn to_f((x, y): (i32, i32)) -> (f32, f32) {
12     (x as f32, y as f32)
13 }
14 
15 /**
16 An element representing a single pixel.
17 
18 See [`crate::element::EmptyElement`] for more information and examples.
19 */
20 pub struct Pixel<Coord> {
21     pos: Coord,
22     style: ShapeStyle,
23 }
24 
25 impl<Coord> Pixel<Coord> {
26     /**
27     Creates a new pixel.
28 
29     See [`crate::element::EmptyElement`] for more information and examples.
30     */
new<P: Into<Coord>, S: Into<ShapeStyle>>(pos: P, style: S) -> Self31     pub fn new<P: Into<Coord>, S: Into<ShapeStyle>>(pos: P, style: S) -> Self {
32         Self {
33             pos: pos.into(),
34             style: style.into(),
35         }
36     }
37 }
38 
39 impl<'a, Coord> PointCollection<'a, Coord> for &'a Pixel<Coord> {
40     type Point = &'a Coord;
41     type IntoIter = std::iter::Once<&'a Coord>;
point_iter(self) -> Self::IntoIter42     fn point_iter(self) -> Self::IntoIter {
43         std::iter::once(&self.pos)
44     }
45 }
46 
47 impl<Coord, DB: DrawingBackend> Drawable<DB> for Pixel<Coord> {
draw<I: Iterator<Item = BackendCoord>>( &self, mut points: I, backend: &mut DB, _: (u32, u32), ) -> Result<(), DrawingErrorKind<DB::ErrorType>>48     fn draw<I: Iterator<Item = BackendCoord>>(
49         &self,
50         mut points: I,
51         backend: &mut DB,
52         _: (u32, u32),
53     ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
54         if let Some((x, y)) = points.next() {
55             return backend.draw_pixel((x, y), self.style.color.to_backend_color());
56         }
57         Ok(())
58     }
59 }
60 
61 #[cfg(test)]
62 #[test]
test_pixel_element()63 fn test_pixel_element() {
64     use crate::prelude::*;
65     let da = crate::create_mocked_drawing_area(300, 300, |m| {
66         m.check_draw_pixel(|c, (x, y)| {
67             assert_eq!(x, 150);
68             assert_eq!(y, 152);
69             assert_eq!(c, RED.to_rgba());
70         });
71 
72         m.drop_check(|b| {
73             assert_eq!(b.num_draw_pixel_call, 1);
74             assert_eq!(b.draw_count, 1);
75         });
76     });
77     da.draw(&Pixel::new((150, 152), RED))
78         .expect("Drawing Failure");
79 }
80 
81 /// This is a deprecated type. Please use new name [`PathElement`] instead.
82 #[deprecated(note = "Use new name PathElement instead")]
83 pub type Path<Coord> = PathElement<Coord>;
84 
85 /// An element of a series of connected lines
86 pub struct PathElement<Coord> {
87     points: Vec<Coord>,
88     style: ShapeStyle,
89 }
90 impl<Coord> PathElement<Coord> {
91     /// Create a new path
92     /// - `points`: The iterator of the points
93     /// - `style`: The shape style
94     /// - returns the created element
new<P: Into<Vec<Coord>>, S: Into<ShapeStyle>>(points: P, style: S) -> Self95     pub fn new<P: Into<Vec<Coord>>, S: Into<ShapeStyle>>(points: P, style: S) -> Self {
96         Self {
97             points: points.into(),
98             style: style.into(),
99         }
100     }
101 }
102 
103 impl<'a, Coord> PointCollection<'a, Coord> for &'a PathElement<Coord> {
104     type Point = &'a Coord;
105     type IntoIter = &'a [Coord];
point_iter(self) -> &'a [Coord]106     fn point_iter(self) -> &'a [Coord] {
107         &self.points
108     }
109 }
110 
111 impl<Coord, DB: DrawingBackend> Drawable<DB> for PathElement<Coord> {
draw<I: Iterator<Item = BackendCoord>>( &self, points: I, backend: &mut DB, _: (u32, u32), ) -> Result<(), DrawingErrorKind<DB::ErrorType>>112     fn draw<I: Iterator<Item = BackendCoord>>(
113         &self,
114         points: I,
115         backend: &mut DB,
116         _: (u32, u32),
117     ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
118         backend.draw_path(points, &self.style)
119     }
120 }
121 
122 #[cfg(test)]
123 #[test]
test_path_element()124 fn test_path_element() {
125     use crate::prelude::*;
126     let da = crate::create_mocked_drawing_area(300, 300, |m| {
127         m.check_draw_path(|c, s, path| {
128             assert_eq!(c, BLUE.to_rgba());
129             assert_eq!(s, 5);
130             assert_eq!(path, vec![(100, 101), (105, 107), (150, 157)]);
131         });
132         m.drop_check(|b| {
133             assert_eq!(b.num_draw_path_call, 1);
134             assert_eq!(b.draw_count, 1);
135         });
136     });
137     da.draw(&PathElement::new(
138         vec![(100, 101), (105, 107), (150, 157)],
139         Into::<ShapeStyle>::into(BLUE).stroke_width(5),
140     ))
141     .expect("Drawing Failure");
142 }
143 
144 /// An element of a series of connected lines in dash style.
145 ///
146 /// It's similar to [`PathElement`] but has a dash style.
147 pub struct DashedPathElement<I: Iterator + Clone, Size: SizeDesc> {
148     points: I,
149     size: Size,
150     spacing: Size,
151     style: ShapeStyle,
152 }
153 
154 impl<I: Iterator + Clone, Size: SizeDesc> DashedPathElement<I, Size> {
155     /// Create a new path
156     /// - `points`: The iterator of the points
157     /// - `size`: The dash size
158     /// - `spacing`: The dash-to-dash spacing (gap size)
159     /// - `style`: The shape style
160     /// - returns the created element
new<I0, S>(points: I0, size: Size, spacing: Size, style: S) -> Self where I0: IntoIterator<IntoIter = I>, S: Into<ShapeStyle>,161     pub fn new<I0, S>(points: I0, size: Size, spacing: Size, style: S) -> Self
162     where
163         I0: IntoIterator<IntoIter = I>,
164         S: Into<ShapeStyle>,
165     {
166         Self {
167             points: points.into_iter(),
168             size,
169             spacing,
170             style: style.into(),
171         }
172     }
173 }
174 
175 impl<'a, I: Iterator + Clone, Size: SizeDesc> PointCollection<'a, I::Item>
176     for &'a DashedPathElement<I, Size>
177 {
178     type Point = I::Item;
179     type IntoIter = I;
point_iter(self) -> Self::IntoIter180     fn point_iter(self) -> Self::IntoIter {
181         self.points.clone()
182     }
183 }
184 
185 impl<I0: Iterator + Clone, Size: SizeDesc, DB: DrawingBackend> Drawable<DB>
186     for DashedPathElement<I0, Size>
187 {
draw<I: Iterator<Item = BackendCoord>>( &self, mut points: I, backend: &mut DB, ps: (u32, u32), ) -> Result<(), DrawingErrorKind<DB::ErrorType>>188     fn draw<I: Iterator<Item = BackendCoord>>(
189         &self,
190         mut points: I,
191         backend: &mut DB,
192         ps: (u32, u32),
193     ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
194         let mut start = match points.next() {
195             Some(c) => to_f(c),
196             None => return Ok(()),
197         };
198         let size = self.size.in_pixels(&ps).max(0) as f32;
199         if size == 0. {
200             return Ok(());
201         }
202         let spacing = self.spacing.in_pixels(&ps).max(0) as f32;
203         let mut dist = 0.;
204         let mut is_solid = true;
205         let mut queue = vec![to_i(start)];
206         for curr in points {
207             let end = to_f(curr);
208             // Loop for solid and spacing
209             while start != end {
210                 let (dx, dy) = (end.0 - start.0, end.1 - start.1);
211                 let d = dx.hypot(dy);
212                 let size = if is_solid { size } else { spacing };
213                 let left = size - dist;
214                 // Set next point to `start`
215                 if left < d {
216                     let t = left / d;
217                     start = (start.0 + dx * t, start.1 + dy * t);
218                     dist += left;
219                 } else {
220                     start = end;
221                     dist += d;
222                 }
223                 // Draw if needed
224                 if is_solid {
225                     queue.push(to_i(start));
226                 }
227                 if size <= dist {
228                     if is_solid {
229                         backend.draw_path(queue.drain(..), &self.style)?;
230                     } else {
231                         queue.push(to_i(start));
232                     }
233                     dist = 0.;
234                     is_solid = !is_solid;
235                 }
236             }
237         }
238         if queue.len() > 1 {
239             backend.draw_path(queue, &self.style)?;
240         }
241         Ok(())
242     }
243 }
244 
245 #[cfg(test)]
246 #[test]
test_dashed_path_element()247 fn test_dashed_path_element() {
248     use crate::prelude::*;
249     let check_list = std::cell::RefCell::new(vec![
250         vec![(100, 100), (100, 103), (100, 105)],
251         vec![(100, 107), (100, 112)],
252         vec![(100, 114), (100, 119)],
253         vec![(100, 119), (100, 120)],
254     ]);
255     let da = crate::create_mocked_drawing_area(300, 300, |m| {
256         m.check_draw_path(move |c, s, path| {
257             assert_eq!(c, BLUE.to_rgba());
258             assert_eq!(s, 7);
259             assert_eq!(path, check_list.borrow_mut().remove(0));
260         });
261         m.drop_check(|b| {
262             assert_eq!(b.num_draw_path_call, 3);
263             assert_eq!(b.draw_count, 3);
264         });
265     });
266     da.draw(&DashedPathElement::new(
267         vec![(100, 100), (100, 103), (100, 120)],
268         5.,
269         2.,
270         BLUE.stroke_width(7),
271     ))
272     .expect("Drawing Failure");
273 }
274 
275 /// An element of a series of connected lines in dot style for any markers.
276 ///
277 /// It's similar to [`PathElement`] but use a marker function to draw markers with spacing.
278 pub struct DottedPathElement<I: Iterator + Clone, Size: SizeDesc, Marker> {
279     points: I,
280     shift: Size,
281     spacing: Size,
282     func: Box<dyn Fn(BackendCoord) -> Marker>,
283 }
284 
285 impl<I: Iterator + Clone, Size: SizeDesc, Marker> DottedPathElement<I, Size, Marker> {
286     /// Create a new path
287     /// - `points`: The iterator of the points
288     /// - `shift`: The shift of the first marker
289     /// - `spacing`: The spacing between markers
290     /// - `func`: The marker function
291     /// - returns the created element
new<I0, F>(points: I0, shift: Size, spacing: Size, func: F) -> Self where I0: IntoIterator<IntoIter = I>, F: Fn(BackendCoord) -> Marker + 'static,292     pub fn new<I0, F>(points: I0, shift: Size, spacing: Size, func: F) -> Self
293     where
294         I0: IntoIterator<IntoIter = I>,
295         F: Fn(BackendCoord) -> Marker + 'static,
296     {
297         Self {
298             points: points.into_iter(),
299             shift,
300             spacing,
301             func: Box::new(func),
302         }
303     }
304 }
305 
306 impl<'a, I: Iterator + Clone, Size: SizeDesc, Marker> PointCollection<'a, I::Item>
307     for &'a DottedPathElement<I, Size, Marker>
308 {
309     type Point = I::Item;
310     type IntoIter = I;
point_iter(self) -> Self::IntoIter311     fn point_iter(self) -> Self::IntoIter {
312         self.points.clone()
313     }
314 }
315 
316 impl<I0, Size, DB, Marker> Drawable<DB> for DottedPathElement<I0, Size, Marker>
317 where
318     I0: Iterator + Clone,
319     Size: SizeDesc,
320     DB: DrawingBackend,
321     Marker: crate::element::IntoDynElement<'static, DB, BackendCoord>,
322 {
draw<I: Iterator<Item = BackendCoord>>( &self, mut points: I, backend: &mut DB, ps: (u32, u32), ) -> Result<(), DrawingErrorKind<DB::ErrorType>>323     fn draw<I: Iterator<Item = BackendCoord>>(
324         &self,
325         mut points: I,
326         backend: &mut DB,
327         ps: (u32, u32),
328     ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
329         let mut shift = self.shift.in_pixels(&ps).max(0) as f32;
330         let mut start = match points.next() {
331             Some(start_i) => {
332                 // Draw the first marker if no shift
333                 if shift == 0. {
334                     let mk = (self.func)(start_i).into_dyn();
335                     mk.draw(mk.point_iter().iter().copied(), backend, ps)?;
336                 }
337                 to_f(start_i)
338             }
339             None => return Ok(()),
340         };
341         let spacing = self.spacing.in_pixels(&ps).max(0) as f32;
342         let mut dist = 0.;
343         for curr in points {
344             let end = to_f(curr);
345             // Loop for spacing
346             while start != end {
347                 let (dx, dy) = (end.0 - start.0, end.1 - start.1);
348                 let d = dx.hypot(dy);
349                 let spacing = if shift == 0. { spacing } else { shift };
350                 let left = spacing - dist;
351                 // Set next point to `start`
352                 if left < d {
353                     let t = left / d;
354                     start = (start.0 + dx * t, start.1 + dy * t);
355                     dist += left;
356                 } else {
357                     start = end;
358                     dist += d;
359                 }
360                 // Draw if needed
361                 if spacing <= dist {
362                     let mk = (self.func)(to_i(start)).into_dyn();
363                     mk.draw(mk.point_iter().iter().copied(), backend, ps)?;
364                     shift = 0.;
365                     dist = 0.;
366                 }
367             }
368         }
369         Ok(())
370     }
371 }
372 
373 #[cfg(test)]
374 #[test]
test_dotted_path_element()375 fn test_dotted_path_element() {
376     use crate::prelude::*;
377     let da = crate::create_mocked_drawing_area(300, 300, |m| {
378         m.drop_check(|b| {
379             assert_eq!(b.num_draw_path_call, 0);
380             assert_eq!(b.draw_count, 7);
381         });
382     });
383     da.draw(&DottedPathElement::new(
384         vec![(100, 100), (105, 105), (150, 150)],
385         5,
386         10,
387         |c| Circle::new(c, 5, Into::<ShapeStyle>::into(RED).filled()),
388     ))
389     .expect("Drawing Failure");
390 }
391 
392 /// A rectangle element
393 pub struct Rectangle<Coord> {
394     points: [Coord; 2],
395     style: ShapeStyle,
396     margin: (u32, u32, u32, u32),
397 }
398 
399 impl<Coord> Rectangle<Coord> {
400     /// Create a new path
401     /// - `points`: The left upper and right lower corner of the rectangle
402     /// - `style`: The shape style
403     /// - returns the created element
new<S: Into<ShapeStyle>>(points: [Coord; 2], style: S) -> Self404     pub fn new<S: Into<ShapeStyle>>(points: [Coord; 2], style: S) -> Self {
405         Self {
406             points,
407             style: style.into(),
408             margin: (0, 0, 0, 0),
409         }
410     }
411 
412     /// Set the margin of the rectangle
413     /// - `t`: The top margin
414     /// - `b`: The bottom margin
415     /// - `l`: The left margin
416     /// - `r`: The right margin
set_margin(&mut self, t: u32, b: u32, l: u32, r: u32) -> &mut Self417     pub fn set_margin(&mut self, t: u32, b: u32, l: u32, r: u32) -> &mut Self {
418         self.margin = (t, b, l, r);
419         self
420     }
421 
422     /// Get the points of the rectangle
423     /// - returns the element points
get_points(&self) -> (&Coord, &Coord)424     pub fn get_points(&self) -> (&Coord, &Coord) {
425         (&self.points[0], &self.points[1])
426     }
427 
428     /// Set the style of the rectangle
429     /// - `style`: The shape style
430     /// - returns a mut reference to the rectangle
set_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self431     pub fn set_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self {
432         self.style = style.into();
433         self
434     }
435 }
436 
437 impl<'a, Coord> PointCollection<'a, Coord> for &'a Rectangle<Coord> {
438     type Point = &'a Coord;
439     type IntoIter = &'a [Coord];
point_iter(self) -> &'a [Coord]440     fn point_iter(self) -> &'a [Coord] {
441         &self.points
442     }
443 }
444 
445 impl<Coord, DB: DrawingBackend> Drawable<DB> for Rectangle<Coord> {
draw<I: Iterator<Item = BackendCoord>>( &self, mut points: I, backend: &mut DB, _: (u32, u32), ) -> Result<(), DrawingErrorKind<DB::ErrorType>>446     fn draw<I: Iterator<Item = BackendCoord>>(
447         &self,
448         mut points: I,
449         backend: &mut DB,
450         _: (u32, u32),
451     ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
452         match (points.next(), points.next()) {
453             (Some(a), Some(b)) => {
454                 let (mut a, mut b) = ((a.0.min(b.0), a.1.min(b.1)), (a.0.max(b.0), a.1.max(b.1)));
455                 a.1 += self.margin.0 as i32;
456                 b.1 -= self.margin.1 as i32;
457                 a.0 += self.margin.2 as i32;
458                 b.0 -= self.margin.3 as i32;
459                 backend.draw_rect(a, b, &self.style, self.style.filled)
460             }
461             _ => Ok(()),
462         }
463     }
464 }
465 
466 #[cfg(test)]
467 #[test]
test_rect_element()468 fn test_rect_element() {
469     use crate::prelude::*;
470     {
471         let da = crate::create_mocked_drawing_area(300, 300, |m| {
472             m.check_draw_rect(|c, s, f, u, d| {
473                 assert_eq!(c, BLUE.to_rgba());
474                 assert!(!f);
475                 assert_eq!(s, 5);
476                 assert_eq!([u, d], [(100, 101), (105, 107)]);
477             });
478             m.drop_check(|b| {
479                 assert_eq!(b.num_draw_rect_call, 1);
480                 assert_eq!(b.draw_count, 1);
481             });
482         });
483         da.draw(&Rectangle::new(
484             [(100, 101), (105, 107)],
485             Color::stroke_width(&BLUE, 5),
486         ))
487         .expect("Drawing Failure");
488     }
489 
490     {
491         let da = crate::create_mocked_drawing_area(300, 300, |m| {
492             m.check_draw_rect(|c, _, f, u, d| {
493                 assert_eq!(c, BLUE.to_rgba());
494                 assert!(f);
495                 assert_eq!([u, d], [(100, 101), (105, 107)]);
496             });
497             m.drop_check(|b| {
498                 assert_eq!(b.num_draw_rect_call, 1);
499                 assert_eq!(b.draw_count, 1);
500             });
501         });
502         da.draw(&Rectangle::new([(100, 101), (105, 107)], BLUE.filled()))
503             .expect("Drawing Failure");
504     }
505 }
506 
507 /// A circle element
508 pub struct Circle<Coord, Size: SizeDesc> {
509     center: Coord,
510     size: Size,
511     style: ShapeStyle,
512 }
513 
514 impl<Coord, Size: SizeDesc> Circle<Coord, Size> {
515     /// Create a new circle element
516     /// - `coord` The center of the circle
517     /// - `size` The radius of the circle
518     /// - `style` The style of the circle
519     /// - Return: The newly created circle element
new<S: Into<ShapeStyle>>(coord: Coord, size: Size, style: S) -> Self520     pub fn new<S: Into<ShapeStyle>>(coord: Coord, size: Size, style: S) -> Self {
521         Self {
522             center: coord,
523             size,
524             style: style.into(),
525         }
526     }
527 }
528 
529 impl<'a, Coord, Size: SizeDesc> PointCollection<'a, Coord> for &'a Circle<Coord, Size> {
530     type Point = &'a Coord;
531     type IntoIter = std::iter::Once<&'a Coord>;
point_iter(self) -> std::iter::Once<&'a Coord>532     fn point_iter(self) -> std::iter::Once<&'a Coord> {
533         std::iter::once(&self.center)
534     }
535 }
536 
537 impl<Coord, DB: DrawingBackend, Size: SizeDesc> Drawable<DB> for Circle<Coord, Size> {
draw<I: Iterator<Item = BackendCoord>>( &self, mut points: I, backend: &mut DB, ps: (u32, u32), ) -> Result<(), DrawingErrorKind<DB::ErrorType>>538     fn draw<I: Iterator<Item = BackendCoord>>(
539         &self,
540         mut points: I,
541         backend: &mut DB,
542         ps: (u32, u32),
543     ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
544         if let Some((x, y)) = points.next() {
545             let size = self.size.in_pixels(&ps).max(0) as u32;
546             return backend.draw_circle((x, y), size, &self.style, self.style.filled);
547         }
548         Ok(())
549     }
550 }
551 
552 #[cfg(test)]
553 #[test]
test_circle_element()554 fn test_circle_element() {
555     use crate::prelude::*;
556     let da = crate::create_mocked_drawing_area(300, 300, |m| {
557         m.check_draw_circle(|c, _, f, s, r| {
558             assert_eq!(c, BLUE.to_rgba());
559             assert!(!f);
560             assert_eq!(s, (150, 151));
561             assert_eq!(r, 20);
562         });
563         m.drop_check(|b| {
564             assert_eq!(b.num_draw_circle_call, 1);
565             assert_eq!(b.draw_count, 1);
566         });
567     });
568     da.draw(&Circle::new((150, 151), 20, BLUE))
569         .expect("Drawing Failure");
570 }
571 
572 /// An element of a filled polygon
573 pub struct Polygon<Coord> {
574     points: Vec<Coord>,
575     style: ShapeStyle,
576 }
577 impl<Coord> Polygon<Coord> {
578     /// Create a new polygon
579     /// - `points`: The iterator of the points
580     /// - `style`: The shape style
581     /// - returns the created element
new<P: Into<Vec<Coord>>, S: Into<ShapeStyle>>(points: P, style: S) -> Self582     pub fn new<P: Into<Vec<Coord>>, S: Into<ShapeStyle>>(points: P, style: S) -> Self {
583         Self {
584             points: points.into(),
585             style: style.into(),
586         }
587     }
588 }
589 
590 impl<'a, Coord> PointCollection<'a, Coord> for &'a Polygon<Coord> {
591     type Point = &'a Coord;
592     type IntoIter = &'a [Coord];
point_iter(self) -> &'a [Coord]593     fn point_iter(self) -> &'a [Coord] {
594         &self.points
595     }
596 }
597 
598 impl<Coord, DB: DrawingBackend> Drawable<DB> for Polygon<Coord> {
draw<I: Iterator<Item = BackendCoord>>( &self, points: I, backend: &mut DB, _: (u32, u32), ) -> Result<(), DrawingErrorKind<DB::ErrorType>>599     fn draw<I: Iterator<Item = BackendCoord>>(
600         &self,
601         points: I,
602         backend: &mut DB,
603         _: (u32, u32),
604     ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
605         backend.fill_polygon(points, &self.style.color.to_backend_color())
606     }
607 }
608 
609 #[cfg(test)]
610 #[test]
test_polygon_element()611 fn test_polygon_element() {
612     use crate::prelude::*;
613     let points = vec![(100, 100), (50, 500), (300, 400), (200, 300), (550, 200)];
614     let expected_points = points.clone();
615 
616     let da = crate::create_mocked_drawing_area(800, 800, |m| {
617         m.check_fill_polygon(move |c, p| {
618             assert_eq!(c, BLUE.to_rgba());
619             assert_eq!(expected_points.len(), p.len());
620             assert_eq!(expected_points, p);
621         });
622         m.drop_check(|b| {
623             assert_eq!(b.num_fill_polygon_call, 1);
624             assert_eq!(b.draw_count, 1);
625         });
626     });
627 
628     da.draw(&Polygon::new(points.clone(), BLUE))
629         .expect("Drawing Failure");
630 }
631