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}
243