Merge branch 'develop' into 'master'

Initial documentation

See merge request mchodzikiewicz/embedded-plots!10
This commit is contained in:
Michał Chodzikiewicz 2021-03-03 21:57:56 +00:00
commit bdbacd5c24
9 changed files with 270 additions and 7 deletions

View file

@ -3,7 +3,7 @@ name = "embedded-plots"
version = "0.1.0" version = "0.1.0"
authors = ["Michał Chodzikiewicz <mchodzikiewicz@gmail.com>"] authors = ["Michał Chodzikiewicz <mchodzikiewicz@gmail.com>"]
edition = "2018" edition = "2018"
license-file = "LICENSE" license = "LGPL-2.1-only"
description = "Heapless plotting library for embedded targets based on embedded-graphics crate" description = "Heapless plotting library for embedded targets based on embedded-graphics crate"
homepage = "https://gitlab.com/mchodzikiewicz/embedded-plots" homepage = "https://gitlab.com/mchodzikiewicz/embedded-plots"
repository = "https://gitlab.com/mchodzikiewicz/embedded-plots" repository = "https://gitlab.com/mchodzikiewicz/embedded-plots"

110
README.md
View file

@ -1,5 +1,109 @@
# Embedded Plots # Embedded Plots
Heapless plotting library for small embedded targets, based on [embedded-graphics](https://crates.io/crates/embedded-graphics) Heapless plotting library for small embedded targets, based on [embedded-graphics](https://crates.io/crates/embedded-graphics)
crate. crate.
This is very beginning of the development, however it is functional to the point where single plot can be drawn. 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<Rgb565> = 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<Rgb565> = 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -10,7 +10,7 @@ use embedded_graphics::{
}; };
use crate::range_conv::Scalable; 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 { pub enum Placement {
X { X {
x1: i32, x1: i32,
@ -24,8 +24,13 @@ pub enum Placement {
}, },
} }
/// Used to describe how densely ticks should be drawn
pub enum Scale { 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), 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), 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> { pub struct Axis<'a> {
/// range that the scale will be drawn for
range: Range<i32>, range: Range<i32>,
/// axis title displayed right next to it
title: Option<&'a str>, title: Option<&'a str>,
/// Definition on how scale ticks should be drawn
scale: Option<Scale>, scale: Option<Scale>,
} }
/// builder methods to modify axis decoration
impl<'a> Axis<'a> impl<'a> Axis<'a>
{ {
/// create new axis data
pub fn new(range: Range<i32>) -> Axis<'a> { pub fn new(range: Range<i32>) -> Axis<'a> {
Axis { range, title: None, scale: None } Axis { range, title: None, scale: None }
} }
/// define how scale ticks should be drawn
pub fn set_scale(mut self, scale: Scale) -> Axis<'a> { pub fn set_scale(mut self, scale: Scale) -> Axis<'a> {
self.scale = Some(scale); self.scale = Some(scale);
self self
} }
/// set axis title
pub fn set_title(mut self, title: &'a str) -> Axis<'a> { pub fn set_title(mut self, title: &'a str) -> Axis<'a> {
self.title = Some(title); self.title = Some(title);
self self
} }
/// turn axis data into drawable object suitable for specific display
pub fn into_drawable_axis<C, F>(self, placement: Placement) -> DrawableAxis<'a, C, F> pub fn into_drawable_axis<C, F>(self, placement: Placement) -> DrawableAxis<'a, C, F>
where where
C: PixelColor + Default, C: PixelColor + Default,
@ -74,6 +88,7 @@ impl<'a> Axis<'a>
} }
} }
/// Drawable axis object, constructed for specific display
pub struct DrawableAxis<'a, C, F> pub struct DrawableAxis<'a, C, F>
where where
C: PixelColor, C: PixelColor,
@ -102,10 +117,14 @@ impl<'a, C, F> DrawableAxis<'a, C, F>
self.text_style = Some(val); self.text_style = Some(val);
self self
} }
/// set how wide tick should be drawn on the axis
pub fn set_tick_size(mut self, val: usize) -> DrawableAxis<'a, C, F> { pub fn set_tick_size(mut self, val: usize) -> DrawableAxis<'a, C, F> {
self.tick_size = Some(val); self.tick_size = Some(val);
self self
} }
/// set thickness of the main line of the axis
pub fn set_thickness(mut self, val: usize) -> DrawableAxis<'a, C, F> { pub fn set_thickness(mut self, val: usize) -> DrawableAxis<'a, C, F> {
self.thickness = Some(val); self.thickness = Some(val);
self self
@ -119,6 +138,8 @@ impl<'a, C, F> Drawable<C> for DrawableAxis<'a, C, F>
F: Font + Copy, F: Font + Copy,
TextStyle<C, F>: Clone + Default, TextStyle<C, F>: Clone + Default,
{ {
/// most important function - draw the axis on the display
fn draw<D: DrawTarget<C>>(self, display: &mut D) -> Result<(), D::Error> { fn draw<D: DrawTarget<C>>(self, display: &mut D) -> Result<(), D::Error> {
let color = self.color.unwrap_or_default(); let color = self.color.unwrap_or_default();
let text_style = self.text_style.unwrap_or_default(); let text_style = self.text_style.unwrap_or_default();

View file

@ -10,23 +10,27 @@ use embedded_graphics::pixelcolor::{PixelColor};
use embedded_graphics::primitives::{Line, Primitive}; use embedded_graphics::primitives::{Line, Primitive};
use embedded_graphics::style::PrimitiveStyle; use embedded_graphics::style::PrimitiveStyle;
/// representation of the single point on the curve
pub struct PlotPoint { pub struct PlotPoint {
pub x: i32, pub x: i32,
pub y: i32, pub y: i32,
} }
/// curve object that contains data to be plotted
pub struct Curve<'a> { pub struct Curve<'a> {
/// slice of points to be drawn
points: &'a [PlotPoint], points: &'a [PlotPoint],
pub x_range: Range<i32>, pub x_range: Range<i32>,
pub y_range: Range<i32>, pub y_range: Range<i32>,
} }
impl<'a> Curve<'a> { impl<'a> Curve<'a> {
/// create new curve data with manual ranges
pub fn new(points: &'a [PlotPoint], x_range: Range<i32>, y_range: Range<i32>) -> Curve { pub fn new(points: &'a [PlotPoint], x_range: Range<i32>, y_range: Range<i32>) -> Curve {
Curve { points, x_range, y_range } 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 { pub fn from_data(points: &'a [PlotPoint]) -> Curve {
let x_range = match points let x_range = match points
.iter() .iter()
@ -46,6 +50,7 @@ impl<'a> Curve<'a> {
Curve { points, x_range, y_range } Curve { points, x_range, y_range }
} }
/// create curve that can be drawed on specific display
pub fn into_drawable_curve<C>(&self, pub fn into_drawable_curve<C>(&self,
top_left: &'a Point, top_left: &'a Point,
bottom_right: &'a Point, bottom_right: &'a Point,
@ -76,6 +81,7 @@ impl<'a> Curve<'a> {
} }
} }
/// Drawable curve object, constructed for specific display
pub struct DrawableCurve<C, I> pub struct DrawableCurve<C, I>
{ {
scaled_data: I, scaled_data: I,
@ -83,15 +89,19 @@ pub struct DrawableCurve<C, I>
thickness: Option<usize>, thickness: Option<usize>,
} }
/// builder methods to modify curve decoration
impl<C, I> DrawableCurve<C, I> impl<C, I> DrawableCurve<C, I>
where where
C: PixelColor, C: PixelColor,
I: Iterator<Item=Point>, I: Iterator<Item=Point>,
{ {
/// set curve color
pub fn set_color(mut self, color: C) -> DrawableCurve<C, I> { pub fn set_color(mut self, color: C) -> DrawableCurve<C, I> {
self.color = Some(color); self.color = Some(color);
self self
} }
/// set curve line thickness
pub fn set_thickness(mut self, thickness: usize) -> DrawableCurve<C,I> { pub fn set_thickness(mut self, thickness: usize) -> DrawableCurve<C,I> {
self.thickness = Some(thickness); self.thickness = Some(thickness);
self self
@ -102,6 +112,7 @@ impl<C, I> Drawable<C> for DrawableCurve<C, I>
where C: PixelColor + Default, where C: PixelColor + Default,
I: Iterator<Item=Point>, I: Iterator<Item=Point>,
{ {
/// most important function - draw the curve on the display
fn draw<D: DrawTarget<C>>(self, display: &mut D) -> Result<(), D::Error> { fn draw<D: DrawTarget<C>>(self, display: &mut D) -> Result<(), D::Error> {
let color = match self.color { let color = match self.color {
None => C::default(), None => C::default(),

View file

@ -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<Rgb565> = 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<Rgb565> = 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 curve;
pub mod axis; pub mod axis;
/// plot that draws single data series
pub mod single_plot; pub mod single_plot;
mod range_conv; mod range_conv;

View file

@ -7,22 +7,31 @@ use crate::axis::{Scale, Placement, Axis};
use embedded_graphics::style::TextStyleBuilder; use embedded_graphics::style::TextStyleBuilder;
use embedded_graphics::fonts::Font6x8; use embedded_graphics::fonts::Font6x8;
/// Display agnostic single curve plot object
pub struct SinglePlot<'a> { pub struct SinglePlot<'a> {
/// curve to be drawn on the plot
curve: &'a Curve<'a>, curve: &'a Curve<'a>,
/// range of X axis on which curve will be drawn
x_scale: Scale, x_scale: Scale,
/// range of Y axis on which curve will be drawn
y_scale: Scale, y_scale: Scale,
} }
impl<'a> SinglePlot<'a> { impl<'a> SinglePlot<'a> {
/// create SinglePlot object with manual range
pub fn new(curve: &'a Curve<'a>, x_scale: Scale, y_scale: Scale) -> SinglePlot { pub fn new(curve: &'a Curve<'a>, x_scale: Scale, y_scale: Scale) -> SinglePlot {
SinglePlot { curve, x_scale, y_scale } SinglePlot { curve, x_scale, y_scale }
} }
//TODO: add auto range plot constructor
/// convert to drawable form for specific display
pub fn into_drawable<C: PixelColor + Default>(self, top_left: Point, bottom_right: Point) -> DrawableSinglePlot<'a, C> { pub fn into_drawable<C: PixelColor + Default>(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 } 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> pub struct DrawableSinglePlot<'a, C>
where where
C: PixelColor + Default, C: PixelColor + Default,
@ -37,6 +46,7 @@ pub struct DrawableSinglePlot<'a, C>
bottom_right: Point, bottom_right: Point,
} }
/// builder methods to modify plot decoration
impl<'a, C> DrawableSinglePlot<'a, C> impl<'a, C> DrawableSinglePlot<'a, C>
where where
C: PixelColor + Default, C: PixelColor + Default,
@ -46,32 +56,39 @@ impl<'a, C> DrawableSinglePlot<'a, C>
self self
} }
/// if not set, main color will be used
pub fn set_text_color(mut self, color: C) -> DrawableSinglePlot<'a, C> { pub fn set_text_color(mut self, color: C) -> DrawableSinglePlot<'a, C> {
self.text_color = Some(color); self.text_color = Some(color);
self self
} }
/// if not set, main color will be used
pub fn set_axis_color(mut self, color: C) -> DrawableSinglePlot<'a, C> { pub fn set_axis_color(mut self, color: C) -> DrawableSinglePlot<'a, C> {
self.axis_color = Some(color); self.axis_color = Some(color);
self self
} }
/// set curve thickness
pub fn set_thickness(mut self, thickness: usize) -> DrawableSinglePlot<'a, C> { pub fn set_thickness(mut self, thickness: usize) -> DrawableSinglePlot<'a, C> {
self.thickness = Some(thickness); self.thickness = Some(thickness);
self self
} }
///set axis thickness
pub fn set_axis_thickness(mut self, thickness: usize) -> DrawableSinglePlot<'a, C> { pub fn set_axis_thickness(mut self, thickness: usize) -> DrawableSinglePlot<'a, C> {
self.axis_thickness = Some(thickness); self.axis_thickness = Some(thickness);
self self
} }
//TODO: add axis ticks thickness
} }
impl<'a, C> Drawable<C> for DrawableSinglePlot<'a, C> impl<'a, C> Drawable<C> for DrawableSinglePlot<'a, C>
where where
C: PixelColor + Default, C: PixelColor + Default,
{ {
/// most important function - draw the plot on the display
fn draw<D: DrawTarget<C>>(self, display: &mut D) -> Result<(), D::Error> { fn draw<D: DrawTarget<C>>(self, display: &mut D) -> Result<(), D::Error> {
let color = self.color.unwrap_or_default(); let color = self.color.unwrap_or_default();
let text_color = self.text_color.unwrap_or(color); let text_color = self.text_color.unwrap_or(color);