| 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 |  |
| 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 |  |
| 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 |  |
| 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 = rect.truncate(coord_trans.translate(from)); |
| 287 | let z = coord_trans.depth(from); |
| 288 | (coord, z) |
| 289 | } |
| 290 | } |
| 291 | |