diff --git a/Cargo.toml b/Cargo.toml index e765d10..ad4a6e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "embedded-plots" version = "0.1.0" authors = ["MichaƂ Chodzikiewicz "] edition = "2018" -license-file = "LICENSE" +license = "LGPL-2.1-only" description = "Heapless plotting library for embedded targets based on embedded-graphics crate" homepage = "https://gitlab.com/mchodzikiewicz/embedded-plots" repository = "https://gitlab.com/mchodzikiewicz/embedded-plots" diff --git a/README.md b/README.md index 54e712e..98f9550 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,109 @@ # Embedded Plots -Heapless plotting library for small embedded targets, based on [embedded-graphics](https://crates.io/crates/embedded-graphics) -crate. + Heapless plotting library for small embedded targets, based on [embedded-graphics](https://crates.io/crates/embedded-graphics) + crate. -This is very beginning of the development, however it is functional to the point where single plot can be drawn. \ No newline at end of file + Thanks to basing it on `embedded-graphics` crate the library is very portable out of the box. + It is not dependent on any hardware target. + To throw it into your project, you only need to have a display that implements `DrawTarget` trait. + For more details see [DrawTarget](https://docs.rs/embedded-graphics/latest/embedded_graphics/prelude/trait.DrawTarget.html) docs. + + Bonus feature of `embedded-graphics` is the simulator. + You can use it to develop your plots without your target hardware, easily create documentation and so on. + + Library utilizes builder pattern and type states - it allows easy separation of the data and decoration in the target application. + + ## Examples + ### Single plot + Simple plot example + #### On color display: + ![single plot on color display](doc-resources/single-plot-color.png "Color plot of single curve") + #### On monochromatic display: + ![single plot on monochromatic display](doc-resources/single-plot-mono.png "Monochromatic plot of single curve") + + Code to render: + ```rust + use embedded_plots::curve::{Curve, PlotPoint}; + use embedded_plots::single_plot::SinglePlot; + use embedded_plots::axis::Scale; + use embedded_graphics::geometry::{Point, Size}; + use embedded_graphics::pixelcolor::{RgbColor, Rgb565}; + use embedded_graphics::drawable::Drawable; + + //simulator dependencies, aka screen driver + use embedded_graphics_simulator::SimulatorDisplay; + + let mut display: SimulatorDisplay = SimulatorDisplay::new(Size::new(480, 272)); + let data = vec![ + PlotPoint { x: 0, y: 0 }, + PlotPoint { x: 1, y: 2 }, + PlotPoint { x: 2, y: 2 }, + PlotPoint { x: 3, y: 0 }, + ]; + let curve = Curve::from_data(data.as_slice()); + + let plot = SinglePlot::new( + &curve, + Scale::RangeFraction(3), + Scale::RangeFraction(2)) + .into_drawable( + Point { x: 50, y: 10 }, + Point { x: 430, y: 250 }) + .set_color(RgbColor::YELLOW) + .set_text_color(RgbColor::WHITE); + + plot.draw(&mut display).unwrap(); + ``` + + ### Axis + You can also use axis on its own, it looks like this: + ![free axis examples](doc-resources/free-axis-example.png "Free axis example") + Code to render example axis: + ```rust + use embedded_plots::axis::{Axis, Scale, Placement}; + use embedded_graphics::pixelcolor::{RgbColor, Rgb565}; + use embedded_graphics::drawable::Drawable; + use embedded_graphics::geometry::Size; + + //simulator dependencies, aka screen driver + use embedded_graphics_simulator::SimulatorDisplay; + use embedded_graphics::style::TextStyleBuilder; + use embedded_graphics::fonts::Font6x8; + + let mut display: SimulatorDisplay = SimulatorDisplay::new(Size::new(480, 272)); + + let text_style_white = TextStyleBuilder::new(Font6x8) + .text_color(RgbColor::WHITE) + .build(); + Axis::new(0..100) + .set_title("Title") + .set_scale(Scale::Fixed(10)) + .into_drawable_axis(Placement::X { x1: 40, x2: 230, y: 10 }) + .set_text_style(text_style_white) + .set_color(RgbColor::WHITE) + .draw(&mut display).unwrap(); + ``` + For more details, see `free_axis` example + + ## Current limitations and future plans + This is very beginning of the development, however it is functional to the point where single plot can be drawn. + + Main issue for now is that you need to predict on how much space will be occupied by axis ticks, + numbers and titles, points passed to `.into_drawable()` are the boundaries for which curve is scaled. + This will be fixed, please be prepared for it since it might be a breaking change for you. + + #### Main features planned soon: + * Drawing multiple curves that share the same X and Y domains on a single plot (take curves slice instead of single curve) + * Dual plot - drawing curves that have two separate domains (either only on one axis or both). + Axis on both sides, left and right (or top and bottom) will be drawn with color corresponding to plot + * Support for floating point domains + * Support for fixed point curve data with intermediate floating point scales (to avoid floating point calculations for each drawn point) + + #### Features I'd love to see in the future: + * Partial redrawing - possibility to substitute data and detect which parts of the screen needs to be redrawed + * Oscilloscope style live mode (adding new points without any redrawing, no data retention) + * Cursors - manual and math based (max,min,avg and so on...) + + ## Contributions + Contributions are more than welcome, if you have particular improvement, raise an issue or submit merge request on project's Gitlab page. + + If you just want to help but don't have anything specific in mind, please take a look at [issue tracker](https://gitlab.com/mchodzikiewicz/embedded-plots/-/issues) and pick one. diff --git a/doc-resources/free-axis-example.png b/doc-resources/free-axis-example.png new file mode 100644 index 0000000..f33bb2a Binary files /dev/null and b/doc-resources/free-axis-example.png differ diff --git a/doc-resources/single-plot-color.png b/doc-resources/single-plot-color.png new file mode 100644 index 0000000..fe5d332 Binary files /dev/null and b/doc-resources/single-plot-color.png differ diff --git a/doc-resources/single-plot-mono.png b/doc-resources/single-plot-mono.png new file mode 100644 index 0000000..8e8e3e6 Binary files /dev/null and b/doc-resources/single-plot-mono.png differ diff --git a/src/axis.rs b/src/axis.rs index f3e8ab3..cd5b038 100644 --- a/src/axis.rs +++ b/src/axis.rs @@ -10,7 +10,7 @@ use embedded_graphics::{ }; use crate::range_conv::Scalable; - +/// Used to provide alignment of an axis, it will be drown exactly on the line marked by the points pub enum Placement { X { x1: i32, @@ -24,8 +24,13 @@ pub enum Placement { }, } +/// Used to describe how densely ticks should be drawn pub enum Scale { + /// Fixed scale means that ticks will be drawn between each increment of absolute distance provided. + /// for example, on range 0..30 and Fixed(10), ticks will be drawn for 0, 10 and 20 Fixed(usize), + /// RangeFraction means that provided number of ticks ticks will be drawn on entire range + /// for example, on range 0..60 and RangeFraction(3), ticks will be drawn for 0, 20 and 40 RangeFraction(usize), } @@ -35,28 +40,37 @@ impl Default for Scale { } } +/// Display-agnostic axis object, only contains scale range and title, can be converted to drawable axis for specific display pub struct Axis<'a> { + /// range that the scale will be drawn for range: Range, + /// axis title displayed right next to it title: Option<&'a str>, + /// Definition on how scale ticks should be drawn scale: Option, } +/// builder methods to modify axis decoration impl<'a> Axis<'a> { + /// create new axis data pub fn new(range: Range) -> Axis<'a> { Axis { range, title: None, scale: None } } + /// define how scale ticks should be drawn pub fn set_scale(mut self, scale: Scale) -> Axis<'a> { self.scale = Some(scale); self } + /// set axis title pub fn set_title(mut self, title: &'a str) -> Axis<'a> { self.title = Some(title); self } + /// turn axis data into drawable object suitable for specific display pub fn into_drawable_axis(self, placement: Placement) -> DrawableAxis<'a, C, F> where C: PixelColor + Default, @@ -74,6 +88,7 @@ impl<'a> Axis<'a> } } +/// Drawable axis object, constructed for specific display pub struct DrawableAxis<'a, C, F> where C: PixelColor, @@ -102,10 +117,14 @@ impl<'a, C, F> DrawableAxis<'a, C, F> self.text_style = Some(val); self } + + /// set how wide tick should be drawn on the axis pub fn set_tick_size(mut self, val: usize) -> DrawableAxis<'a, C, F> { self.tick_size = Some(val); self } + + /// set thickness of the main line of the axis pub fn set_thickness(mut self, val: usize) -> DrawableAxis<'a, C, F> { self.thickness = Some(val); self @@ -119,6 +138,8 @@ impl<'a, C, F> Drawable for DrawableAxis<'a, C, F> F: Font + Copy, TextStyle: Clone + Default, { + + /// most important function - draw the axis on the display fn draw>(self, display: &mut D) -> Result<(), D::Error> { let color = self.color.unwrap_or_default(); let text_style = self.text_style.unwrap_or_default(); diff --git a/src/curve.rs b/src/curve.rs index 0036141..0c0a736 100644 --- a/src/curve.rs +++ b/src/curve.rs @@ -10,23 +10,27 @@ use embedded_graphics::pixelcolor::{PixelColor}; use embedded_graphics::primitives::{Line, Primitive}; use embedded_graphics::style::PrimitiveStyle; - +/// representation of the single point on the curve pub struct PlotPoint { pub x: i32, pub y: i32, } +/// curve object that contains data to be plotted pub struct Curve<'a> { + /// slice of points to be drawn points: &'a [PlotPoint], pub x_range: Range, pub y_range: Range, } impl<'a> Curve<'a> { + /// create new curve data with manual ranges pub fn new(points: &'a [PlotPoint], x_range: Range, y_range: Range) -> Curve { Curve { points, x_range, y_range } } + /// create new curve data with ranges automatically deducted based on provided points pub fn from_data(points: &'a [PlotPoint]) -> Curve { let x_range = match points .iter() @@ -46,6 +50,7 @@ impl<'a> Curve<'a> { Curve { points, x_range, y_range } } + /// create curve that can be drawed on specific display pub fn into_drawable_curve(&self, top_left: &'a Point, bottom_right: &'a Point, @@ -76,6 +81,7 @@ impl<'a> Curve<'a> { } } +/// Drawable curve object, constructed for specific display pub struct DrawableCurve { scaled_data: I, @@ -83,15 +89,19 @@ pub struct DrawableCurve thickness: Option, } +/// builder methods to modify curve decoration impl DrawableCurve where C: PixelColor, I: Iterator, { + /// set curve color pub fn set_color(mut self, color: C) -> DrawableCurve { self.color = Some(color); self } + + /// set curve line thickness pub fn set_thickness(mut self, thickness: usize) -> DrawableCurve { self.thickness = Some(thickness); self @@ -102,6 +112,7 @@ impl Drawable for DrawableCurve where C: PixelColor + Default, I: Iterator, { + /// most important function - draw the curve on the display fn draw>(self, display: &mut D) -> Result<(), D::Error> { let color = match self.color { None => C::default(), diff --git a/src/lib.rs b/src/lib.rs index 7b2b50c..151f2a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,117 @@ -#![no_std] +//!# Embedded Plots +//! Heapless plotting library for small embedded targets, based on [embedded-graphics](https://crates.io/crates/embedded-graphics) +//! crate. +//! +//! Thanks to basing it on `embedded-graphics` crate the library is very portable out of the box. +//! It is not dependent on any hardware target. +//! To throw it into your project, you only need to have a display that implements `DrawTarget` trait. +//! For more details see [DrawTarget](https://docs.rs/embedded-graphics/latest/embedded_graphics/prelude/trait.DrawTarget.html) docs. +//! +//! Bonus feature of `embedded-graphics` is the simulator. +//! You can use it to develop your plots without your target hardware, easily create documentation and so on. +//! +//! Library utilizes builder pattern and type states - it allows easy separation of the data and decoration in the target application. +//! +//! ## Examples +//! ### Single plot +//! Simple plot example +//! #### On color display: +//! ![single plot on color display](doc-resources/single-plot-color.png "Color plot of single curve") +//! #### On monochromatic display: +//! ![single plot on monochromatic display](doc-resources/single-plot-mono.png "Monochromatic plot of single curve") +//! +//! Code to render: +//! ```rust +//! use embedded_plots::curve::{Curve, PlotPoint}; +//! use embedded_plots::single_plot::SinglePlot; +//! use embedded_plots::axis::Scale; +//! use embedded_graphics::geometry::{Point, Size}; +//! use embedded_graphics::pixelcolor::{RgbColor, Rgb565}; +//! use embedded_graphics::drawable::Drawable; +//! +//! //simulator dependencies, aka screen driver +//! use embedded_graphics_simulator::SimulatorDisplay; +//! +//! let mut display: SimulatorDisplay = SimulatorDisplay::new(Size::new(480, 272)); +//! let data = vec![ +//! PlotPoint { x: 0, y: 0 }, +//! PlotPoint { x: 1, y: 2 }, +//! PlotPoint { x: 2, y: 2 }, +//! PlotPoint { x: 3, y: 0 }, +//! ]; +//! let curve = Curve::from_data(data.as_slice()); +//! +//! let plot = SinglePlot::new( +//! &curve, +//! Scale::RangeFraction(3), +//! Scale::RangeFraction(2)) +//! .into_drawable( +//! Point { x: 50, y: 10 }, +//! Point { x: 430, y: 250 }) +//! .set_color(RgbColor::YELLOW) +//! .set_text_color(RgbColor::WHITE); +//! +//! plot.draw(&mut display).unwrap(); +//! ``` +//! +//! ### Axis +//! You can also use axis on its own, it looks like this: +//! ![free axis examples](doc-resources/free-axis-example.png "Free axis example") +//! Code to render example axis: +//! ```rust +//! use embedded_plots::axis::{Axis, Scale, Placement}; +//! use embedded_graphics::pixelcolor::{RgbColor, Rgb565}; +//! use embedded_graphics::drawable::Drawable; +//! use embedded_graphics::geometry::Size; +//! +//! //simulator dependencies, aka screen driver +//! use embedded_graphics_simulator::SimulatorDisplay; +//! use embedded_graphics::style::TextStyleBuilder; +//! use embedded_graphics::fonts::Font6x8; +//! +//! let mut display: SimulatorDisplay = SimulatorDisplay::new(Size::new(480, 272)); +//! +//! let text_style_white = TextStyleBuilder::new(Font6x8) +//! .text_color(RgbColor::WHITE) +//! .build(); +//! Axis::new(0..100) +//! .set_title("Title") +//! .set_scale(Scale::Fixed(10)) +//! .into_drawable_axis(Placement::X { x1: 40, x2: 230, y: 10 }) +//! .set_text_style(text_style_white) +//! .set_color(RgbColor::WHITE) +//! .draw(&mut display).unwrap(); +//! ``` +//! For more details, see `free_axis` example +//! +//! ## Current limitations and future plans +//! This is very beginning of the development, however it is functional to the point where single plot can be drawn. +//! +//! Main issue for now is that you need to predict on how much space will be occupied by axis ticks, +//! numbers and titles, points passed to `.into_drawable()` are the boundaries for which curve is scaled. +//! This will be fixed, please be prepared for it since it might be a breaking change for you. +//! +//! #### Main features planned soon: +//! * Drawing multiple curves that share the same X and Y domains on a single plot (take curves slice instead of single curve) +//! * Dual plot - drawing curves that have two separate domains (either only on one axis or both). +//! Axis on both sides, left and right (or top and bottom) will be drawn with color corresponding to plot +//! * Support for floating point domains +//! * Support for fixed point curve data with intermediate floating point scales (to avoid floating point calculations for each drawn point) +//! +//! #### Features I'd love to see in the future: +//! * Partial redrawing - possibility to substitute data and detect which parts of the screen needs to be redrawed +//! * Oscilloscope style live mode (adding new points without any redrawing, no data retention) +//! * Cursors - manual and math based (max,min,avg and so on...) +//! +//! ## Contributions +//! Contributions are more than welcome, if you have particular improvement, raise an issue or submit merge request on project's Gitlab page. +//! +//! If you just want to help but don't have anything specific in mind, please take a look at [issue tracker](https://gitlab.com/mchodzikiewicz/embedded-plots/-/issues) and pick one. +#![no_std] pub mod curve; pub mod axis; +/// plot that draws single data series pub mod single_plot; mod range_conv; \ No newline at end of file diff --git a/src/single_plot.rs b/src/single_plot.rs index 58f5ddc..18f65cc 100644 --- a/src/single_plot.rs +++ b/src/single_plot.rs @@ -7,22 +7,31 @@ use crate::axis::{Scale, Placement, Axis}; use embedded_graphics::style::TextStyleBuilder; use embedded_graphics::fonts::Font6x8; +/// Display agnostic single curve plot object pub struct SinglePlot<'a> { + /// curve to be drawn on the plot curve: &'a Curve<'a>, + /// range of X axis on which curve will be drawn x_scale: Scale, + /// range of Y axis on which curve will be drawn y_scale: Scale, } impl<'a> SinglePlot<'a> { + /// create SinglePlot object with manual range pub fn new(curve: &'a Curve<'a>, x_scale: Scale, y_scale: Scale) -> SinglePlot { SinglePlot { curve, x_scale, y_scale } } + //TODO: add auto range plot constructor + + /// convert to drawable form for specific display pub fn into_drawable(self, top_left: Point, bottom_right: Point) -> DrawableSinglePlot<'a, C> { DrawableSinglePlot { plot: self, color: None, text_color: None, axis_color: None, thickness: None, axis_thickness: None, top_left, bottom_right } } } +/// Drawable single plot object, constructed for specific display pub struct DrawableSinglePlot<'a, C> where C: PixelColor + Default, @@ -37,6 +46,7 @@ pub struct DrawableSinglePlot<'a, C> bottom_right: Point, } +/// builder methods to modify plot decoration impl<'a, C> DrawableSinglePlot<'a, C> where C: PixelColor + Default, @@ -46,32 +56,39 @@ impl<'a, C> DrawableSinglePlot<'a, C> self } + /// if not set, main color will be used pub fn set_text_color(mut self, color: C) -> DrawableSinglePlot<'a, C> { self.text_color = Some(color); self } + /// if not set, main color will be used pub fn set_axis_color(mut self, color: C) -> DrawableSinglePlot<'a, C> { self.axis_color = Some(color); self } + /// set curve thickness pub fn set_thickness(mut self, thickness: usize) -> DrawableSinglePlot<'a, C> { self.thickness = Some(thickness); self } + ///set axis thickness pub fn set_axis_thickness(mut self, thickness: usize) -> DrawableSinglePlot<'a, C> { self.axis_thickness = Some(thickness); self } + //TODO: add axis ticks thickness + } impl<'a, C> Drawable for DrawableSinglePlot<'a, C> where C: PixelColor + Default, { + /// most important function - draw the plot on the display fn draw>(self, display: &mut D) -> Result<(), D::Error> { let color = self.color.unwrap_or_default(); let text_color = self.text_color.unwrap_or(color);