plotters/element/
composable.rs

1use super::*;
2use plotters_backend::DrawingBackend;
3use std::borrow::Borrow;
4use std::iter::{once, Once};
5use std::marker::PhantomData;
6use std::ops::Add;
7
8/**
9An empty composable element. This is the starting point of a composed element.
10
11# Example
12
13```
14use plotters::prelude::*;
15let data = [(1.0, 3.3), (2., 2.1), (3., 1.5), (4., 1.9), (5., 1.0)];
16let drawing_area = SVGBackend::new("composable.svg", (300, 200)).into_drawing_area();
17drawing_area.fill(&WHITE).unwrap();
18let mut chart_builder = ChartBuilder::on(&drawing_area);
19chart_builder.margin(7).set_left_and_bottom_label_area_size(20);
20let mut chart_context = chart_builder.build_cartesian_2d(0.0..5.5, 0.0..5.5).unwrap();
21chart_context.configure_mesh().draw().unwrap();
22chart_context.draw_series(data.map(|(x, y)| {
23    EmptyElement::at((x, y)) // Use the guest coordinate system with EmptyElement
24    + Circle::new((0, 0), 10, BLUE) // Use backend coordinates with the rest
25    + Cross::new((4, 4), 3, RED)
26    + Pixel::new((4, -4), RED)
27    + TriangleMarker::new((-4, -4), 4, RED)
28})).unwrap();
29```
30
31The result is a data series where each point consists of a circle, a cross, a pixel, and a triangle:
32
33![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@06d370f/apidoc/composable.svg)
34
35*/
36pub struct EmptyElement<Coord, DB: DrawingBackend> {
37    coord: Coord,
38    phantom: PhantomData<DB>,
39}
40
41impl<Coord, DB: DrawingBackend> EmptyElement<Coord, DB> {
42    /**
43    An empty composable element. This is the starting point of a composed element.
44
45    See [`EmptyElement`] for more information and examples.
46    */
47    pub fn at(coord: Coord) -> Self {
48        Self {
49            coord,
50            phantom: PhantomData,
51        }
52    }
53}
54
55impl<Coord, Other, DB: DrawingBackend> Add<Other> for EmptyElement<Coord, DB>
56where
57    Other: Drawable<DB>,
58    for<'a> &'a Other: PointCollection<'a, BackendCoord>,
59{
60    type Output = BoxedElement<Coord, DB, Other>;
61    fn add(self, other: Other) -> Self::Output {
62        BoxedElement {
63            offset: self.coord,
64            inner: other,
65            phantom: PhantomData,
66        }
67    }
68}
69
70impl<'a, Coord, DB: DrawingBackend> PointCollection<'a, Coord> for &'a EmptyElement<Coord, DB> {
71    type Point = &'a Coord;
72    type IntoIter = Once<&'a Coord>;
73    fn point_iter(self) -> Self::IntoIter {
74        once(&self.coord)
75    }
76}
77
78impl<Coord, DB: DrawingBackend> Drawable<DB> for EmptyElement<Coord, DB> {
79    fn draw<I: Iterator<Item = BackendCoord>>(
80        &self,
81        _pos: I,
82        _backend: &mut DB,
83        _: (u32, u32),
84    ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
85        Ok(())
86    }
87}
88
89/**
90A container for one drawable element, used for composition.
91
92This is used internally by Plotters and should probably not be included in user code.
93See [`EmptyElement`] for more information and examples.
94*/
95pub struct BoxedElement<Coord, DB: DrawingBackend, A: Drawable<DB>> {
96    inner: A,
97    offset: Coord,
98    phantom: PhantomData<DB>,
99}
100
101impl<'b, Coord, DB: DrawingBackend, A: Drawable<DB>> PointCollection<'b, Coord>
102    for &'b BoxedElement<Coord, DB, A>
103{
104    type Point = &'b Coord;
105    type IntoIter = Once<&'b Coord>;
106    fn point_iter(self) -> Self::IntoIter {
107        once(&self.offset)
108    }
109}
110
111impl<Coord, DB: DrawingBackend, A> Drawable<DB> for BoxedElement<Coord, DB, A>
112where
113    for<'a> &'a A: PointCollection<'a, BackendCoord>,
114    A: Drawable<DB>,
115{
116    fn draw<I: Iterator<Item = BackendCoord>>(
117        &self,
118        mut pos: I,
119        backend: &mut DB,
120        ps: (u32, u32),
121    ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
122        if let Some((x0, y0)) = pos.next() {
123            self.inner.draw(
124                self.inner.point_iter().into_iter().map(|p| {
125                    let p = p.borrow();
126                    (p.0 + x0, p.1 + y0)
127                }),
128                backend,
129                ps,
130            )?;
131        }
132        Ok(())
133    }
134}
135
136impl<Coord, DB: DrawingBackend, My, Yours> Add<Yours> for BoxedElement<Coord, DB, My>
137where
138    My: Drawable<DB>,
139    for<'a> &'a My: PointCollection<'a, BackendCoord>,
140    Yours: Drawable<DB>,
141    for<'a> &'a Yours: PointCollection<'a, BackendCoord>,
142{
143    type Output = ComposedElement<Coord, DB, My, Yours>;
144    fn add(self, yours: Yours) -> Self::Output {
145        ComposedElement {
146            offset: self.offset,
147            first: self.inner,
148            second: yours,
149            phantom: PhantomData,
150        }
151    }
152}
153
154/**
155A container for two drawable elements, used for composition.
156
157This is used internally by Plotters and should probably not be included in user code.
158See [`EmptyElement`] for more information and examples.
159*/
160pub struct ComposedElement<Coord, DB: DrawingBackend, A, B>
161where
162    A: Drawable<DB>,
163    B: Drawable<DB>,
164{
165    first: A,
166    second: B,
167    offset: Coord,
168    phantom: PhantomData<DB>,
169}
170
171impl<'b, Coord, DB: DrawingBackend, A, B> PointCollection<'b, Coord>
172    for &'b ComposedElement<Coord, DB, A, B>
173where
174    A: Drawable<DB>,
175    B: Drawable<DB>,
176{
177    type Point = &'b Coord;
178    type IntoIter = Once<&'b Coord>;
179    fn point_iter(self) -> Self::IntoIter {
180        once(&self.offset)
181    }
182}
183
184impl<Coord, DB: DrawingBackend, A, B> Drawable<DB> for ComposedElement<Coord, DB, A, B>
185where
186    for<'a> &'a A: PointCollection<'a, BackendCoord>,
187    for<'b> &'b B: PointCollection<'b, BackendCoord>,
188    A: Drawable<DB>,
189    B: Drawable<DB>,
190{
191    fn draw<I: Iterator<Item = BackendCoord>>(
192        &self,
193        mut pos: I,
194        backend: &mut DB,
195        ps: (u32, u32),
196    ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
197        if let Some((x0, y0)) = pos.next() {
198            self.first.draw(
199                self.first.point_iter().into_iter().map(|p| {
200                    let p = p.borrow();
201                    (p.0 + x0, p.1 + y0)
202                }),
203                backend,
204                ps,
205            )?;
206            self.second.draw(
207                self.second.point_iter().into_iter().map(|p| {
208                    let p = p.borrow();
209                    (p.0 + x0, p.1 + y0)
210                }),
211                backend,
212                ps,
213            )?;
214        }
215        Ok(())
216    }
217}
218
219impl<Coord, DB: DrawingBackend, A, B, C> Add<C> for ComposedElement<Coord, DB, A, B>
220where
221    A: Drawable<DB>,
222    for<'a> &'a A: PointCollection<'a, BackendCoord>,
223    B: Drawable<DB>,
224    for<'a> &'a B: PointCollection<'a, BackendCoord>,
225    C: Drawable<DB>,
226    for<'a> &'a C: PointCollection<'a, BackendCoord>,
227{
228    type Output = ComposedElement<Coord, DB, A, ComposedElement<BackendCoord, DB, B, C>>;
229    fn add(self, rhs: C) -> Self::Output {
230        ComposedElement {
231            offset: self.offset,
232            first: self.first,
233            second: ComposedElement {
234                offset: (0, 0),
235                first: self.second,
236                second: rhs,
237                phantom: PhantomData,
238            },
239            phantom: PhantomData,
240        }
241    }
242}