plotters/element/
basic_shapes.rs

1use super::{Drawable, PointCollection};
2use crate::style::{Color, ShapeStyle, SizeDesc};
3use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
4
5#[inline]
6fn to_i((x, y): (f32, f32)) -> (i32, i32) {
7    (x.round() as i32, y.round() as i32)
8}
9
10#[inline]
11fn to_f((x, y): (i32, i32)) -> (f32, f32) {
12    (x as f32, y as f32)
13}
14
15/**
16An element representing a single pixel.
17
18See [`crate::element::EmptyElement`] for more information and examples.
19*/
20pub struct Pixel<Coord> {
21    pos: Coord,
22    style: ShapeStyle,
23}
24
25impl<Coord> Pixel<Coord> {
26    /**
27    Creates a new pixel.
28
29    See [`crate::element::EmptyElement`] for more information and examples.
30    */
31    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
39impl<'a, Coord> PointCollection<'a, Coord> for &'a Pixel<Coord> {
40    type Point = &'a Coord;
41    type IntoIter = std::iter::Once<&'a Coord>;
42    fn point_iter(self) -> Self::IntoIter {
43        std::iter::once(&self.pos)
44    }
45}
46
47impl<Coord, DB: DrawingBackend> Drawable<DB> for Pixel<Coord> {
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]
63fn 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")]
83pub type Path<Coord> = PathElement<Coord>;
84
85/// An element of a series of connected lines
86pub struct PathElement<Coord> {
87    points: Vec<Coord>,
88    style: ShapeStyle,
89}
90impl<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
95    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
103impl<'a, Coord> PointCollection<'a, Coord> for &'a PathElement<Coord> {
104    type Point = &'a Coord;
105    type IntoIter = &'a [Coord];
106    fn point_iter(self) -> &'a [Coord] {
107        &self.points
108    }
109}
110
111impl<Coord, DB: DrawingBackend> Drawable<DB> for PathElement<Coord> {
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]
124fn 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.
147pub struct DashedPathElement<I: Iterator + Clone, Size: SizeDesc> {
148    points: I,
149    size: Size,
150    spacing: Size,
151    style: ShapeStyle,
152}
153
154impl<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
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
175impl<'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;
180    fn point_iter(self) -> Self::IntoIter {
181        self.points.clone()
182    }
183}
184
185impl<I0: Iterator + Clone, Size: SizeDesc, DB: DrawingBackend> Drawable<DB>
186    for DashedPathElement<I0, Size>
187{
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]
247fn 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.
278pub 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
285impl<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
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
306impl<'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;
311    fn point_iter(self) -> Self::IntoIter {
312        self.points.clone()
313    }
314}
315
316impl<I0, Size, DB, Marker> Drawable<DB> for DottedPathElement<I0, Size, Marker>
317where
318    I0: Iterator + Clone,
319    Size: SizeDesc,
320    DB: DrawingBackend,
321    Marker: crate::element::IntoDynElement<'static, DB, BackendCoord>,
322{
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]
375fn 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
393pub struct Rectangle<Coord> {
394    points: [Coord; 2],
395    style: ShapeStyle,
396    margin: (u32, u32, u32, u32),
397}
398
399impl<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
404    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
417    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
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
431    pub fn set_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self {
432        self.style = style.into();
433        self
434    }
435}
436
437impl<'a, Coord> PointCollection<'a, Coord> for &'a Rectangle<Coord> {
438    type Point = &'a Coord;
439    type IntoIter = &'a [Coord];
440    fn point_iter(self) -> &'a [Coord] {
441        &self.points
442    }
443}
444
445impl<Coord, DB: DrawingBackend> Drawable<DB> for Rectangle<Coord> {
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]
468fn 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
508pub struct Circle<Coord, Size: SizeDesc> {
509    center: Coord,
510    size: Size,
511    style: ShapeStyle,
512}
513
514impl<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
520    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
529impl<'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>;
532    fn point_iter(self) -> std::iter::Once<&'a Coord> {
533        std::iter::once(&self.center)
534    }
535}
536
537impl<Coord, DB: DrawingBackend, Size: SizeDesc> Drawable<DB> for Circle<Coord, Size> {
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]
554fn 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
573pub struct Polygon<Coord> {
574    points: Vec<Coord>,
575    style: ShapeStyle,
576}
577impl<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
582    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
590impl<'a, Coord> PointCollection<'a, Coord> for &'a Polygon<Coord> {
591    type Point = &'a Coord;
592    type IntoIter = &'a [Coord];
593    fn point_iter(self) -> &'a [Coord] {
594        &self.points
595    }
596}
597
598impl<Coord, DB: DrawingBackend> Drawable<DB> for Polygon<Coord> {
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]
611fn 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}