1 | /*! |
2 | Defines the drawing elements, the high-level drawing unit in Plotters drawing system |
3 | |
4 | ## Introduction |
5 | An element is the drawing unit for Plotter's high-level drawing API. |
6 | Different from low-level drawing API, an element is a logic unit of component in the image. |
7 | There are few built-in elements, including `Circle`, `Pixel`, `Rectangle`, `Path`, `Text`, etc. |
8 | |
9 | All element can be drawn onto the drawing area using API `DrawingArea::draw(...)`. |
10 | Plotters use "iterator of elements" as the abstraction of any type of plot. |
11 | |
12 | ## Implementing your own element |
13 | You can also define your own element, `CandleStick` is a good sample of implementing complex |
14 | element. There are two trait required for an element: |
15 | |
16 | - `PointCollection` - the struct should be able to return an iterator of key-points under guest coordinate |
17 | - `Drawable` - the struct is a pending drawing operation on a drawing backend with pixel-based coordinate |
18 | |
19 | An example of element that draws a red "X" in a red rectangle onto the backend: |
20 | |
21 | ```rust |
22 | use std::iter::{Once, once}; |
23 | use plotters::element::{PointCollection, Drawable}; |
24 | use plotters_backend::{BackendCoord, DrawingErrorKind, BackendStyle}; |
25 | use plotters::style::IntoTextStyle; |
26 | use plotters::prelude::*; |
27 | |
28 | // Any example drawing a red X |
29 | struct RedBoxedX((i32, i32)); |
30 | |
31 | // For any reference to RedX, we can convert it into an iterator of points |
32 | impl <'a> PointCollection<'a, (i32, i32)> for &'a RedBoxedX { |
33 | type Point = &'a (i32, i32); |
34 | type IntoIter = Once<&'a (i32, i32)>; |
35 | fn point_iter(self) -> Self::IntoIter { |
36 | once(&self.0) |
37 | } |
38 | } |
39 | |
40 | // How to actually draw this element |
41 | impl <DB:DrawingBackend> Drawable<DB> for RedBoxedX { |
42 | fn draw<I:Iterator<Item = BackendCoord>>( |
43 | &self, |
44 | mut pos: I, |
45 | backend: &mut DB, |
46 | _: (u32, u32), |
47 | ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { |
48 | let pos = pos.next().unwrap(); |
49 | backend.draw_rect(pos, (pos.0 + 10, pos.1 + 12), &RED, false)?; |
50 | let text_style = &("sans-serif" , 20).into_text_style(&backend.get_size()).color(&RED); |
51 | backend.draw_text("X" , text_style, pos) |
52 | } |
53 | } |
54 | |
55 | fn main() -> Result<(), Box<dyn std::error::Error>> { |
56 | let root = BitMapBackend::new( |
57 | "plotters-doc-data/element-0.png" , |
58 | (640, 480) |
59 | ).into_drawing_area(); |
60 | root.draw(&RedBoxedX((200, 200)))?; |
61 | Ok(()) |
62 | } |
63 | ``` |
64 | ![](https://plotters-rs.github.io/plotters-doc-data/element-0.png) |
65 | |
66 | ## Composable Elements |
67 | You also have an convenient way to build an element that isn't built into the Plotters library by |
68 | combining existing elements into a logic group. To build an composable element, you need to use an |
69 | logic empty element that draws nothing to the backend but denotes the relative zero point of the logical |
70 | group. Any element defined with pixel based offset coordinate can be added into the group later using |
71 | the `+` operator. |
72 | |
73 | For example, the red boxed X element can be implemented with Composable element in the following way: |
74 | ```rust |
75 | use plotters::prelude::*; |
76 | fn main() -> Result<(), Box<dyn std::error::Error>> { |
77 | let root = BitMapBackend::new( |
78 | "plotters-doc-data/element-1.png" , |
79 | (640, 480) |
80 | ).into_drawing_area(); |
81 | let font:FontDesc = ("sans-serif" , 20).into(); |
82 | root.draw(&(EmptyElement::at((200, 200)) |
83 | + Text::new("X" , (0, 0), &"sans-serif" .into_font().resize(20.0).color(&RED)) |
84 | + Rectangle::new([(0,0), (10, 12)], &RED) |
85 | ))?; |
86 | Ok(()) |
87 | } |
88 | ``` |
89 | ![](https://plotters-rs.github.io/plotters-doc-data/element-1.png) |
90 | |
91 | ## Dynamic Elements |
92 | By default, Plotters uses static dispatch for all the elements and series. For example, |
93 | the `ChartContext::draw_series` method accepts an iterator of `T` where type `T` implements |
94 | all the traits a element should implement. Although, we can use the series of composable element |
95 | for complex series drawing. But sometimes, we still want to make the series heterogynous, which means |
96 | the iterator should be able to holds elements in different type. |
97 | For example, a point series with cross and circle. This requires the dynamically dispatched elements. |
98 | In plotters, all the elements can be converted into `DynElement`, the dynamic dispatch container for |
99 | all elements (include external implemented ones). |
100 | Plotters automatically implements `IntoDynElement` for all elements, by doing so, any dynamic element should have |
101 | `into_dyn` function which would wrap the element into a dynamic element wrapper. |
102 | |
103 | For example, the following code counts the number of factors of integer and mark all prime numbers in cross. |
104 | ```rust |
105 | use plotters::prelude::*; |
106 | fn num_of_factor(n: i32) -> i32 { |
107 | let mut ret = 2; |
108 | for i in 2..n { |
109 | if i * i > n { |
110 | break; |
111 | } |
112 | |
113 | if n % i == 0 { |
114 | if i * i != n { |
115 | ret += 2; |
116 | } else { |
117 | ret += 1; |
118 | } |
119 | } |
120 | } |
121 | return ret; |
122 | } |
123 | fn main() -> Result<(), Box<dyn std::error::Error>> { |
124 | let root = |
125 | BitMapBackend::new("plotters-doc-data/element-3.png" , (640, 480)) |
126 | .into_drawing_area(); |
127 | root.fill(&WHITE)?; |
128 | let mut chart = ChartBuilder::on(&root) |
129 | .x_label_area_size(40) |
130 | .y_label_area_size(40) |
131 | .margin(5) |
132 | .build_cartesian_2d(0..50, 0..10)?; |
133 | |
134 | chart |
135 | .configure_mesh() |
136 | .disable_x_mesh() |
137 | .disable_y_mesh() |
138 | .draw()?; |
139 | |
140 | chart.draw_series((0..50).map(|x| { |
141 | let center = (x, num_of_factor(x)); |
142 | // Although the arms of if statement has different types, |
143 | // but they can be placed into a dynamic element wrapper, |
144 | // by doing so, the type is unified. |
145 | if center.1 == 2 { |
146 | Cross::new(center, 4, Into::<ShapeStyle>::into(&RED).filled()).into_dyn() |
147 | } else { |
148 | Circle::new(center, 4, Into::<ShapeStyle>::into(&GREEN).filled()).into_dyn() |
149 | } |
150 | }))?; |
151 | |
152 | Ok(()) |
153 | } |
154 | ``` |
155 | ![](https://plotters-rs.github.io/plotters-doc-data/element-3.png) |
156 | */ |
157 | use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; |
158 | use std::borrow::Borrow; |
159 | |
160 | mod basic_shapes; |
161 | pub use basic_shapes::*; |
162 | |
163 | mod basic_shapes_3d; |
164 | pub use basic_shapes_3d::*; |
165 | |
166 | mod text; |
167 | pub use text::*; |
168 | |
169 | mod points; |
170 | pub use points::*; |
171 | |
172 | mod composable; |
173 | pub use composable::{ComposedElement, EmptyElement}; |
174 | |
175 | #[cfg (feature = "candlestick" )] |
176 | mod candlestick; |
177 | #[cfg (feature = "candlestick" )] |
178 | pub use candlestick::CandleStick; |
179 | |
180 | #[cfg (feature = "errorbar" )] |
181 | mod errorbar; |
182 | #[cfg (feature = "errorbar" )] |
183 | pub use errorbar::{ErrorBar, ErrorBarOrientH, ErrorBarOrientV}; |
184 | |
185 | #[cfg (feature = "boxplot" )] |
186 | mod boxplot; |
187 | #[cfg (feature = "boxplot" )] |
188 | pub use boxplot::Boxplot; |
189 | |
190 | #[cfg (feature = "bitmap_backend" )] |
191 | mod image; |
192 | #[cfg (feature = "bitmap_backend" )] |
193 | pub use self::image::BitMapElement; |
194 | |
195 | mod dynelem; |
196 | pub use dynelem::{DynElement, IntoDynElement}; |
197 | |
198 | mod pie; |
199 | pub use pie::Pie; |
200 | |
201 | use crate::coord::CoordTranslate; |
202 | use crate::drawing::Rect; |
203 | |
204 | /// A type which is logically a collection of points, under any given coordinate system. |
205 | /// Note: Ideally, a point collection trait should be any type of which coordinate elements can be |
206 | /// iterated. This is similar to `iter` method of many collection types in std. |
207 | /// |
208 | /// ```ignore |
209 | /// trait PointCollection<Coord> { |
210 | /// type PointIter<'a> : Iterator<Item = &'a Coord>; |
211 | /// fn iter(&self) -> PointIter<'a>; |
212 | /// } |
213 | /// ``` |
214 | /// |
215 | /// However, |
216 | /// [Generic Associated Types](https://github.com/rust-lang/rfcs/blob/master/text/1598-generic_associated_types.md) |
217 | /// is far away from stablize. |
218 | /// So currently we have the following workaround: |
219 | /// |
220 | /// Instead of implement the PointCollection trait on the element type itself, it implements on the |
221 | /// reference to the element. By doing so, we now have a well-defined lifetime for the iterator. |
222 | /// |
223 | /// In addition, for some element, the coordinate is computed on the fly, thus we can't hard-code |
224 | /// the iterator's return type is `&'a Coord`. |
225 | /// `Borrow` trait seems to strict in this case, since we don't need the order and hash |
226 | /// preservation properties at this point. However, `AsRef` doesn't work with `Coord` |
227 | /// |
228 | /// This workaround also leads overly strict lifetime bound on `ChartContext::draw_series`. |
229 | /// |
230 | /// TODO: Once GAT is ready on stable Rust, we should simplify the design. |
231 | /// |
232 | pub trait PointCollection<'a, Coord, CM = BackendCoordOnly> { |
233 | /// The item in point iterator |
234 | type Point: Borrow<Coord> + 'a; |
235 | |
236 | /// The point iterator |
237 | type IntoIter: IntoIterator<Item = Self::Point>; |
238 | |
239 | /// framework to do the coordinate mapping |
240 | fn point_iter(self) -> Self::IntoIter; |
241 | } |
242 | /// The trait indicates we are able to draw it on a drawing area |
243 | pub trait Drawable<DB: DrawingBackend, CM: CoordMapper = BackendCoordOnly> { |
244 | /// Actually draws the element. The key points is already translated into the |
245 | /// image coordinate and can be used by DC directly |
246 | fn draw<I: Iterator<Item = CM::Output>>( |
247 | &self, |
248 | pos: I, |
249 | backend: &mut DB, |
250 | parent_dim: (u32, u32), |
251 | ) -> Result<(), DrawingErrorKind<DB::ErrorType>>; |
252 | } |
253 | |
254 | /// Useful to translate from guest coordinates to backend coordinates |
255 | pub trait CoordMapper { |
256 | /// Specifies the output data from the translation |
257 | type Output; |
258 | /// Performs the translation from guest coordinates to backend coordinates |
259 | fn map<CT: CoordTranslate>(coord_trans: &CT, from: &CT::From, rect: &Rect) -> Self::Output; |
260 | } |
261 | |
262 | /// Used for 2d coordinate transformations. |
263 | pub struct BackendCoordOnly; |
264 | |
265 | impl CoordMapper for BackendCoordOnly { |
266 | type Output = BackendCoord; |
267 | fn map<CT: CoordTranslate>(coord_trans: &CT, from: &CT::From, rect: &Rect) -> BackendCoord { |
268 | rect.truncate(coord_trans.translate(from)) |
269 | } |
270 | } |
271 | |
272 | /** |
273 | Used for 3d coordinate transformations. |
274 | |
275 | See [`Cubiod`] for more information and an example. |
276 | */ |
277 | pub struct BackendCoordAndZ; |
278 | |
279 | impl CoordMapper for BackendCoordAndZ { |
280 | type Output = (BackendCoord, i32); |
281 | fn map<CT: CoordTranslate>( |
282 | coord_trans: &CT, |
283 | from: &CT::From, |
284 | rect: &Rect, |
285 | ) -> (BackendCoord, i32) { |
286 | let coord: (i32, i32) = rect.truncate(coord_trans.translate(from)); |
287 | let z: i32 = coord_trans.depth(from); |
288 | (coord, z) |
289 | } |
290 | } |
291 | |