diff options
Diffstat (limited to 'src/element/mod.rs')
-rw-r--r-- | src/element/mod.rs | 218 |
1 files changed, 218 insertions, 0 deletions
diff --git a/src/element/mod.rs b/src/element/mod.rs new file mode 100644 index 0000000..99b7a62 --- /dev/null +++ b/src/element/mod.rs @@ -0,0 +1,218 @@ +/*! + Defines the drawing elements, the high-level drawing unit in Plotters drawing system + + ## Introduction + An element is the drawing unit for Plotter's high-level drawing API. + Different from low-level drawing API, an element is a logic unit of component in the image. + There are few built-in elements, including `Circle`, `Pixel`, `Rectangle`, `Path`, `Text`, etc. + + All element can be drawn onto the drawing area using API `DrawingArea::draw(...)`. + Plotters use "iterator of elements" as the abstraction of any type of plot. + + ## Implementing your own element + You can also define your own element, `CandleStick` is a good sample of implementing complex + element. There are two trait required for an element: + + - `PointCollection` - the struct should be able to return an iterator of key-points under guest coordinate + - `Drawable` - the struct is a pending drawing operation on a drawing backend with pixel-based coordinate + + An example of element that draws a red "X" in a red rectangle onto the backend: + + ```rust + use std::iter::{Once, once}; + use plotters::element::{PointCollection, Drawable}; + use plotters::drawing::backend::{BackendCoord, DrawingErrorKind}; + use plotters::style::IntoTextStyle; + use plotters::prelude::*; + + // Any example drawing a red X + struct RedBoxedX((i32, i32)); + + // For any reference to RedX, we can convert it into an iterator of points + impl <'a> PointCollection<'a, (i32, i32)> for &'a RedBoxedX { + type Borrow = &'a (i32, i32); + type IntoIter = Once<&'a (i32, i32)>; + fn point_iter(self) -> Self::IntoIter { + once(&self.0) + } + } + + // How to actually draw this element + impl <DB:DrawingBackend> Drawable<DB> for RedBoxedX { + fn draw<I:Iterator<Item = BackendCoord>>( + &self, + mut pos: I, + backend: &mut DB, + _: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + let pos = pos.next().unwrap(); + let color = RED.to_rgba(); + backend.draw_rect(pos, (pos.0 + 10, pos.1 + 12), &color, false)?; + let text_style = &("sans-serif", 20).into_text_style(backend).color(&color); + backend.draw_text("X", &text_style, pos) + } + } + + fn main() -> Result<(), Box<dyn std::error::Error>> { + let root = BitMapBackend::new( + "plotters-doc-data/element-0.png", + (640, 480) + ).into_drawing_area(); + root.draw(&RedBoxedX((200, 200)))?; + Ok(()) + } + ``` + ![](https://plotters-rs.github.io/plotters-doc-data/element-0.png) + + ## Composable Elements + You also have an convenient way to build an element that isn't built into the Plotters library by + combining existing elements into a logic group. To build an composable element, you need to use an + logic empty element that draws nothing to the backend but denotes the relative zero point of the logical + group. Any element defined with pixel based offset coordinate can be added into the group later using + the `+` operator. + + For example, the red boxed X element can be implemented with Composable element in the following way: + ```rust + use plotters::prelude::*; + fn main() -> Result<(), Box<dyn std::error::Error>> { + let root = BitMapBackend::new( + "plotters-doc-data/element-1.png", + (640, 480) + ).into_drawing_area(); + let font:FontDesc = ("sans-serif", 20).into(); + root.draw(&(EmptyElement::at((200, 200)) + + Text::new("X", (0, 0), &"sans-serif".into_font().resize(20.0).color(&RED)) + + Rectangle::new([(0,0), (10, 12)], &RED) + ))?; + Ok(()) + } + ``` + ![](https://plotters-rs.github.io/plotters-doc-data/element-1.png) + + ## Dynamic Elements + By default, Plotters uses static dispatch for all the elements and series. For example, + the `ChartContext::draw_series` method accepts an iterator of `T` where type `T` implements + all the traits a element should implement. Although, we can use the series of composable element + for complex series drawing. But sometimes, we still want to make the series heterogynous, which means + the iterator should be able to holds elements in different type. + For example, a point series with cross and circle. This requires the dynamically dispatched elements. + In plotters, all the elements can be converted into `DynElement`, the dynamic dispatch container for + all elements (include external implemented ones). + Plotters automatically implements `IntoDynElement` for all elements, by doing so, any dynamic element should have + `into_dyn` function which would wrap the element into a dynamic element wrapper. + + For example, the following code counts the number of factors of integer and mark all prime numbers in cross. + ```rust + use plotters::prelude::*; + fn num_of_factor(n: i32) -> i32 { + let mut ret = 2; + for i in 2..n { + if i * i > n { + break; + } + + if n % i == 0 { + if i * i != n { + ret += 2; + } else { + ret += 1; + } + } + } + return ret; + } + fn main() -> Result<(), Box<dyn std::error::Error>> { + let root = + BitMapBackend::new("plotters-doc-data/element-3.png", (640, 480)) + .into_drawing_area(); + root.fill(&WHITE)?; + let mut chart = ChartBuilder::on(&root) + .x_label_area_size(40) + .y_label_area_size(40) + .margin(5) + .build_ranged(0..50, 0..10)?; + + chart + .configure_mesh() + .disable_x_mesh() + .disable_y_mesh() + .draw()?; + + chart.draw_series((0..50).map(|x| { + let center = (x, num_of_factor(x)); + // Although the arms of if statement has different types, + // but they can be placed into a dynamic element wrapper, + // by doing so, the type is unified. + if center.1 == 2 { + Cross::new(center, 4, Into::<ShapeStyle>::into(&RED).filled()).into_dyn() + } else { + Circle::new(center, 4, Into::<ShapeStyle>::into(&GREEN).filled()).into_dyn() + } + }))?; + + Ok(()) + } + ``` + ![](https://plotters-rs.github.io/plotters-doc-data/element-3.png) +*/ +use crate::drawing::backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; +use std::borrow::Borrow; + +mod basic_shapes; +pub use basic_shapes::*; + +mod text; +pub use text::*; + +mod points; +pub use points::*; + +mod composable; +pub use composable::{ComposedElement, EmptyElement}; + +#[cfg(feature = "candlestick")] +mod candlestick; +#[cfg(feature = "candlestick")] +pub use candlestick::CandleStick; + +#[cfg(feature = "errorbar")] +mod errorbar; +#[cfg(feature = "errorbar")] +pub use errorbar::{ErrorBar, ErrorBarOrientH, ErrorBarOrientV}; + +#[cfg(feature = "boxplot")] +mod boxplot; +#[cfg(feature = "boxplot")] +pub use boxplot::Boxplot; + +#[cfg(feature = "bitmap")] +mod image; +#[cfg(feature = "bitmap")] +pub use self::image::BitMapElement; + +mod dynelem; +pub use dynelem::{DynElement, IntoDynElement}; + +/// A type which is logically a collection of points, under any given coordinate system +pub trait PointCollection<'a, Coord> { + /// The item in point iterator + type Borrow: Borrow<Coord>; + + /// The point iterator + type IntoIter: IntoIterator<Item = Self::Borrow>; + + /// framework to do the coordinate mapping + fn point_iter(self) -> Self::IntoIter; +} + +/// The trait indicates we are able to draw it on a drawing area +pub trait Drawable<DB: DrawingBackend> { + /// Actually draws the element. The key points is already translated into the + /// image coordinate and can be used by DC directly + fn draw<I: Iterator<Item = BackendCoord>>( + &self, + pos: I, + backend: &mut DB, + parent_dim: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>>; +} |