1 | use super::*; |
2 | use plotters_backend::DrawingBackend; |
3 | use std::borrow::Borrow; |
4 | use std::iter::{once, Once}; |
5 | use std::marker::PhantomData; |
6 | use std::ops::Add; |
7 | |
8 | /** |
9 | An empty composable element. This is the starting point of a composed element. |
10 | |
11 | # Example |
12 | |
13 | ``` |
14 | use plotters::prelude::*; |
15 | let data = [(1.0, 3.3), (2., 2.1), (3., 1.5), (4., 1.9), (5., 1.0)]; |
16 | let drawing_area = SVGBackend::new("composable.svg" , (300, 200)).into_drawing_area(); |
17 | drawing_area.fill(&WHITE).unwrap(); |
18 | let mut chart_builder = ChartBuilder::on(&drawing_area); |
19 | chart_builder.margin(7).set_left_and_bottom_label_area_size(20); |
20 | let mut chart_context = chart_builder.build_cartesian_2d(0.0..5.5, 0.0..5.5).unwrap(); |
21 | chart_context.configure_mesh().draw().unwrap(); |
22 | chart_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 | |
31 | The 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 | */ |
36 | pub struct EmptyElement<Coord, DB: DrawingBackend> { |
37 | coord: Coord, |
38 | phantom: PhantomData<DB>, |
39 | } |
40 | |
41 | impl<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 | |
55 | impl<Coord, Other, DB: DrawingBackend> Add<Other> for EmptyElement<Coord, DB> |
56 | where |
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 | |
70 | impl<'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 | |
78 | impl<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 | /** |
90 | A container for one drawable element, used for composition. |
91 | |
92 | This is used internally by Plotters and should probably not be included in user code. |
93 | See [`EmptyElement`] for more information and examples. |
94 | */ |
95 | pub struct BoxedElement<Coord, DB: DrawingBackend, A: Drawable<DB>> { |
96 | inner: A, |
97 | offset: Coord, |
98 | phantom: PhantomData<DB>, |
99 | } |
100 | |
101 | impl<'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 | |
111 | impl<Coord, DB: DrawingBackend, A> Drawable<DB> for BoxedElement<Coord, DB, A> |
112 | where |
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 | |
136 | impl<Coord, DB: DrawingBackend, My, Yours> Add<Yours> for BoxedElement<Coord, DB, My> |
137 | where |
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 | /** |
155 | A container for two drawable elements, used for composition. |
156 | |
157 | This is used internally by Plotters and should probably not be included in user code. |
158 | See [`EmptyElement`] for more information and examples. |
159 | */ |
160 | pub struct ComposedElement<Coord, DB: DrawingBackend, A, B> |
161 | where |
162 | A: Drawable<DB>, |
163 | B: Drawable<DB>, |
164 | { |
165 | first: A, |
166 | second: B, |
167 | offset: Coord, |
168 | phantom: PhantomData<DB>, |
169 | } |
170 | |
171 | impl<'b, Coord, DB: DrawingBackend, A, B> PointCollection<'b, Coord> |
172 | for &'b ComposedElement<Coord, DB, A, B> |
173 | where |
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 | |
184 | impl<Coord, DB: DrawingBackend, A, B> Drawable<DB> for ComposedElement<Coord, DB, A, B> |
185 | where |
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 | |
219 | impl<Coord, DB: DrawingBackend, A, B, C> Add<C> for ComposedElement<Coord, DB, A, B> |
220 | where |
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 | |