Merge branch 'develop' into 'master'

First version published on crates.io

See merge request mchodzikiewicz/embedded-plots!8
This commit is contained in:
Michał Chodzikiewicz 2021-03-03 00:08:56 +00:00
commit a8aeddd2a7
14 changed files with 655 additions and 149 deletions

16
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,16 @@
# todo: spin our own image
image: registry.gitlab.com/mchodzikiewicz/embedded-plots-docker:latest
stages:
- build
- test
build:
stage: build
script:
- cargo build
test:
stage: test
script:
- cargo test

View file

@ -3,14 +3,21 @@ name = "embedded-plots"
version = "0.1.0"
authors = ["Michał Chodzikiewicz <mchodzikiewicz@gmail.com>"]
edition = "2018"
license-file = "LICENSE"
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"
readme = "README.md"
keywords = ["embedded", "plot", "graphics"]
categories = ["embedded","visualization"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
embedded-graphics = "0.6.0"
itertools = "0.9.0"
heapless = "0.5.6"
[dev-dependencies]
embedded-graphics-simulator = "0.2.1"
test-case = "1.0.0"

5
README.md Normal file
View file

@ -0,0 +1,5 @@
# Embedded Plots
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.

View file

@ -1,32 +0,0 @@
use embedded_graphics::{
pixelcolor::Rgb565,
prelude::*,
};
use embedded_graphics_simulator::{
SimulatorDisplay,
Window,
OutputSettingsBuilder
};
use embedded_plots::curve::{PlotPoint, Curve};
fn main() -> Result<(), core::convert::Infallible> {
let mut display: SimulatorDisplay<Rgb565> = SimulatorDisplay::new(Size::new(480, 272));
let data = vec![
PlotPoint{x: 0,y: 0},
PlotPoint{x: 1,y: 1},
PlotPoint{x: 2,y: 1},
PlotPoint{x: 3,y: 0},
];
Curve::new(data.as_slice())
.into_drawable_curve(&(0..3),&(0..1),&Point{x: 20, y: 20}, &Point{x:450,y:250},RgbColor::WHITE)
.draw(&mut display)?;
let output_settings = OutputSettingsBuilder::new()
.build();
Window::new("Hello World", &output_settings).show_static(&display);
Ok(())
}

129
examples/free_axis.rs Normal file
View file

@ -0,0 +1,129 @@
use embedded_graphics::{
pixelcolor::Rgb565,
prelude::*,
style::TextStyleBuilder,
fonts::{Font6x8, Font6x6},
};
use embedded_graphics_simulator::{
SimulatorDisplay,
Window,
OutputSettingsBuilder,
};
use embedded_plots::{
axis::{Axis, Placement, Scale},
};
fn main() -> Result<(), core::convert::Infallible> {
let mut display: SimulatorDisplay<Rgb565> = SimulatorDisplay::new(Size::new(480, 272));
let text_style_white = TextStyleBuilder::new(Font6x8)
.text_color(RgbColor::WHITE)
.build();
let text_style_yellow_compact = TextStyleBuilder::new(Font6x6)
.text_color(RgbColor::YELLOW)
.build();
Axis::new(0..100)
.set_title("X Fixed 0-100(10)")
.set_scale(Scale::Fixed(10))
.into_drawable_axis(Placement::X { x1: 40, x2: 230, y: 10 })
.set_color(RgbColor::WHITE)
.set_text_style(text_style_white)
.set_thickness(2)
.set_tick_size(2)
.draw(&mut display)?;
Axis::new(0..200)
.set_title("X Fixed 0-200(100)")
.set_scale(Scale::Fixed(100))
.into_drawable_axis(Placement::X { x1: 240, x2: 470, y: 10 })
.set_color(RgbColor::YELLOW)
.set_text_style(text_style_yellow_compact)
.set_tick_size(2)
.draw(&mut display)?;
Axis::new(0..100)
.set_title("X Frac 0-100(7)")
.set_scale(Scale::RangeFraction(7))
.into_drawable_axis(Placement::X { x1: 50, x2: 220, y: 30 })
.set_color(RgbColor::BLUE)
.set_text_style(text_style_white)
.set_tick_size(3)
.draw(&mut display)?;
Axis::new(0..200)
.set_title("X Frac 0-200(4)")
.set_scale(Scale::RangeFraction(4))
.into_drawable_axis(Placement::X { x1: 250, x2: 460, y: 40 })
.set_color(RgbColor::RED)
.set_text_style(text_style_yellow_compact)
.set_tick_size(7)
.draw(&mut display)?;
Axis::new(0..100)
.set_title("Y Fixed 0-100(10)")
.set_scale(Scale::Fixed(10))
.into_drawable_axis(
Placement::Y { y1: 70, y2: 230, x: 160 })
.set_color(RgbColor::WHITE)
.set_text_style(text_style_white)
.set_tick_size(2)
.draw(&mut display)?;
Axis::new(0..200)
.set_title("Y Fixed 0-200(100)")
.set_scale(Scale::Fixed(100))
.into_drawable_axis(
Placement::Y { y1: 70, y2: 210, x: 260 })
.set_color(RgbColor::YELLOW)
.set_text_style(text_style_yellow_compact)
.set_tick_size(1)
.draw(&mut display)?;
Axis::new(0..100)
.set_title("Y Frac 0-100(7)")
.set_scale(Scale::RangeFraction(7))
.into_drawable_axis(Placement::Y { y1: 60, y2: 180, x: 370 })
.set_color(RgbColor::BLUE)
.set_text_style(text_style_white)
.set_tick_size(3)
.draw(&mut display)?;
Axis::new(0..200)
.set_title("Y Frac 0-200(4)")
.set_scale(Scale::RangeFraction(4))
.into_drawable_axis(Placement::Y { y1: 90, y2: 220, x: 470 })
.set_color(RgbColor::RED)
.set_text_style(text_style_yellow_compact)
.set_tick_size(7)
.draw(&mut display)?;
Axis::new(123..2137)
.set_title("X")
.set_scale(Scale::Fixed(150))
.into_drawable_axis(Placement::X { x1: 30, x2: 470, y: 250 })
.set_color(RgbColor::YELLOW)
.set_text_style(text_style_white)
.set_tick_size(2)
.draw(&mut display)?;
Axis::new(0..2137)
.set_title("Y")
.set_scale(Scale::RangeFraction(15))
.into_drawable_axis(Placement::Y { y1: 10, y2: 250, x: 30 })
.set_color(RgbColor::WHITE)
.set_text_style(text_style_white)
.set_tick_size(2)
.draw(&mut display)?;
let output_settings = OutputSettingsBuilder::new()
.pixel_spacing(1)
.build();
Window::new("Free axis", &output_settings).show_static(&display);
Ok(())
}

View file

@ -0,0 +1,42 @@
use embedded_graphics::{
prelude::*,
pixelcolor::BinaryColor,
};
use embedded_graphics_simulator::{SimulatorDisplay, Window, OutputSettingsBuilder, BinaryColorTheme};
use embedded_plots::{
single_plot::{SinglePlot},
curve::{PlotPoint, Curve},
axis::Scale,
};
fn main() -> Result<(), core::convert::Infallible> {
let mut display: SimulatorDisplay<BinaryColor> = SimulatorDisplay::new(Size::new(128, 48));
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: 18, y: 2 },
Point { x: 120, y: 30 },
).set_color(BinaryColor::On);
plot.draw(&mut display)?;
let output_settings = OutputSettingsBuilder::new()
.theme(BinaryColorTheme::OledBlue)
.build();
Window::new("Basic plot", &output_settings)
.show_static(&display);
Ok(())
}

View file

@ -0,0 +1,43 @@
use embedded_graphics::{
pixelcolor::Rgb565,
prelude::*,
};
use embedded_graphics_simulator::{
SimulatorDisplay,
Window,
OutputSettingsBuilder,
};
use embedded_plots::{
single_plot::{SinglePlot},
curve::{PlotPoint, Curve},
axis::Scale,
};
fn main() -> Result<(), core::convert::Infallible> {
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)?;
let output_settings = OutputSettingsBuilder::new()
.build();
Window::new("Basic plot", &output_settings).show_static(&display);
Ok(())
}

188
src/axis.rs Normal file
View file

@ -0,0 +1,188 @@
use core::ops::Range;
use core::fmt::Write;
use heapless::{consts::*, String};
use embedded_graphics::{
prelude::*,
style::{TextStyle, PrimitiveStyle},
primitives::Line,
fonts::Text,
};
use crate::range_conv::Scalable;
pub enum Placement {
X {
x1: i32,
x2: i32,
y: i32,
},
Y {
y1: i32,
y2: i32,
x: i32,
},
}
pub enum Scale {
Fixed(usize),
RangeFraction(usize),
}
impl Default for Scale {
fn default() -> Self {
Scale::RangeFraction(5)
}
}
pub struct Axis<'a> {
range: Range<i32>,
title: Option<&'a str>,
scale: Option<Scale>,
}
impl<'a> Axis<'a>
{
pub fn new(range: Range<i32>) -> Axis<'a> {
Axis { range, title: None, scale: None }
}
pub fn set_scale(mut self, scale: Scale) -> Axis<'a> {
self.scale = Some(scale);
self
}
pub fn set_title(mut self, title: &'a str) -> Axis<'a> {
self.title = Some(title);
self
}
pub fn into_drawable_axis<C, F>(self, placement: Placement) -> DrawableAxis<'a, C, F>
where
C: PixelColor + Default,
F: Font,
TextStyle<C, F>: Clone + Default,
{
DrawableAxis{
axis: self,
placement,
color: None,
text_style: None,
tick_size: None,
thickness: None,
}
}
}
pub struct DrawableAxis<'a, C, F>
where
C: PixelColor,
F: Font,
TextStyle<C, F>: Clone + Default,
{
axis: Axis<'a>,
placement: Placement,
color: Option<C>,
text_style: Option<TextStyle<C, F>>,
tick_size: Option<usize>,
thickness: Option<usize>,
}
impl<'a, C, F> DrawableAxis<'a, C, F>
where
C: PixelColor + Default,
F: Font,
TextStyle<C, F>: Clone + Default,
{
pub fn set_color(mut self, val: C) -> DrawableAxis<'a, C, F> {
self.color = Some(val);
self
}
pub fn set_text_style(mut self, val: TextStyle<C, F>) -> DrawableAxis<'a, C, F> {
self.text_style = Some(val);
self
}
pub fn set_tick_size(mut self, val: usize) -> DrawableAxis<'a, C, F> {
self.tick_size = Some(val);
self
}
pub fn set_thickness(mut self, val: usize) -> DrawableAxis<'a, C, F> {
self.thickness = Some(val);
self
}
}
impl<'a, C, F> Drawable<C> for DrawableAxis<'a, C, F>
where
C: PixelColor + Default,
F: Font + Copy,
TextStyle<C, F>: Clone + Default,
{
fn draw<D: DrawTarget<C>>(self, display: &mut D) -> Result<(), D::Error> {
let color = self.color.unwrap_or_default();
let text_style = self.text_style.unwrap_or_default();
let thickness = self.thickness.unwrap_or(1);
let tick_size = self.tick_size.unwrap_or(2);
let scale_marks = match self.axis.scale.unwrap_or_default() {
Scale::Fixed(interval) => {
self.axis.range.clone().into_iter().step_by(interval)
}
Scale::RangeFraction(fraction) => {
let len = self.axis.range.len();
self.axis.range.clone().into_iter().step_by(len / fraction)
}
};
match self.placement {
Placement::X { x1, x2, y } => {
Line { start: Point { x: x1, y }, end: Point { x: x2, y } }
.into_styled(PrimitiveStyle::with_stroke(color, thickness as u32))
.draw(display)?;
if let Some(title) = self.axis.title {
let title = Text::new(title, Point { x: x1, y: y + 10 })
.into_styled(text_style);
let title = title.translate(Point { x: (x2 - x1) / 2 - title.size().width as i32 / 2, y: 0 });
title.draw(display)?;
}
for mark in scale_marks {
let x = mark.scale_between_ranges(&self.axis.range, &(x1..x2));
Line { start: Point { x, y: y - tick_size as i32 }, end: Point { x, y: y + tick_size as i32 } }
.into_styled(PrimitiveStyle::with_stroke(color, thickness as u32))
.draw(display)?;
let mut buf: String::<U8> = String::new();
write!(buf, "{}", mark).unwrap();
Text::new(&buf, Point { x: x + 2, y: y + 2 }).into_styled(text_style).draw(display)?;
}
}
Placement::Y { y1, y2, x } => {
Line { start: Point { x, y: y1 }, end: Point { x, y: y2 } }
.into_styled(PrimitiveStyle::with_stroke(color, thickness as u32))
.draw(display)?;
let mut max_tick_text_width = 0;
for mark in scale_marks {
let y = mark.scale_between_ranges(&self.axis.range, &(y2..y1));
Line { start: Point { x: x - tick_size as i32, y }, end: Point { x: x + tick_size as i32, y } }
.into_styled(PrimitiveStyle::with_stroke(color, thickness as u32))
.draw(display)?;
let mut buf: String::<U8> = String::new();
write!(buf, "{}", mark).unwrap();
let tick_val = Text::new(&buf, Point { x, y }).into_styled(text_style);
let tick_val = tick_val.translate(Point { x: -(tick_val.size().width as i32) - 2, y: 2 });
if tick_val.size().width > max_tick_text_width { max_tick_text_width = tick_val.size().width }
tick_val.draw(display)?;
}
if let Some(title) = self.axis.title {
let title = Text::new(title, Point { x, y: y1 })
.into_styled(text_style);
let title = title.translate(Point { x: -(title.size().width as i32) - max_tick_text_width as i32 - tick_size as i32 - 2, y: (y2 - y1) / 2 });
title.draw(display)?;
}
}
}
Ok(())
}
}

View file

@ -1,53 +1,128 @@
use core::ops::{Range};
use crate::range_conv::Scalable;
use crate::drawable_curve::DrawableCurve;
use embedded_graphics::prelude::*;
use itertools::{Itertools, MinMaxResult::MinMax, MinMaxResult};
use embedded_graphics::drawable::{Drawable};
use embedded_graphics::DrawTarget;
use embedded_graphics::geometry::Point;
use embedded_graphics::pixelcolor::{PixelColor};
use embedded_graphics::primitives::{Line, Primitive};
use embedded_graphics::style::PrimitiveStyle;
pub struct PlotPoint {
pub x: i32,
pub y: i32,
}
pub struct Curve<'a>{
pub struct Curve<'a> {
points: &'a [PlotPoint],
pub x_range: Range<i32>,
pub y_range: Range<i32>,
}
impl<'a> Curve<'a> {
pub fn new(points: &'a [PlotPoint]) -> Curve {
Curve {points}
pub fn new(points: &'a [PlotPoint], x_range: Range<i32>, y_range: Range<i32>) -> Curve {
Curve { points, x_range, y_range }
}
pub fn into_drawable_curve<C>(self,
x_range: &'a Range<i32>,
y_range: &'a Range<i32>,
top_left : &'a Point,
pub fn from_data(points: &'a [PlotPoint]) -> Curve {
let x_range = match points
.iter()
.map(|p| (p.x))
.minmax() {
MinMaxResult::NoElements => 0..0,
MinMaxResult::OneElement(v) => v..v,
MinMax(min, max) => min..max,
};
let y_range = match points.iter().map(|p| (p.y)).minmax() {
MinMaxResult::NoElements => 0..0,
MinMaxResult::OneElement(v) => v..v,
MinMax(min, max) => min..max,
};
Curve { points, x_range, y_range }
}
pub fn into_drawable_curve<C>(&self,
top_left: &'a Point,
bottom_right: &'a Point,
color: C
) -> DrawableCurve<C,impl Iterator<Item=Point> + 'a>
) -> DrawableCurve<C, impl Iterator<Item=Point> + '_>
where C: PixelColor
{
assert!(top_left.x < bottom_right.x);
assert!(top_left.y < bottom_right.y);
assert!(!x_range.is_empty());
assert!(!y_range.is_empty());
assert!(!self.x_range.is_empty());
assert!(!self.y_range.is_empty());
let it = self.points.iter()
.map(move |p| Point{
.map(move |p| Point {
x: p.x.scale_between_ranges(
x_range,
&Range{start: top_left.x, end: bottom_right.x}
&self.x_range,
&Range { start: top_left.x, end: bottom_right.x },
),
y: p.y.scale_between_ranges(
y_range,
&Range{start: bottom_right.y, end: top_left.y}
&self.y_range,
&Range { start: bottom_right.y, end: top_left.y },
),
});
DrawableCurve::new(it,color)
DrawableCurve {
scaled_data: it,
color: None,
thickness: None,
}
}
}
pub struct DrawableCurve<C, I>
{
scaled_data: I,
color: Option<C>,
thickness: Option<usize>,
}
impl<C, I> DrawableCurve<C, I>
where
C: PixelColor,
I: Iterator<Item=Point>,
{
pub fn set_color(mut self, color: C) -> DrawableCurve<C, I> {
self.color = Some(color);
self
}
pub fn set_thickness(mut self, thickness: usize) -> DrawableCurve<C,I> {
self.thickness = Some(thickness);
self
}
}
impl<C, I> Drawable<C> for DrawableCurve<C, I>
where C: PixelColor + Default,
I: Iterator<Item=Point>,
{
fn draw<D: DrawTarget<C>>(self, display: &mut D) -> Result<(), D::Error> {
let color = match self.color {
None => C::default(),
Some(c) => c,
};
let thickness = match self.thickness {
None => 2,
Some(t) => t,
};
let style = PrimitiveStyle::with_stroke(color, thickness as u32);
let mut iter = self.scaled_data.into_iter();
let mut prev = iter.next().unwrap();
for point in iter {
Line::new(prev, point)
.into_styled(style)
.draw(display)?;
prev = point;
}
Ok(())
}
}
#[cfg(test)]
mod tests {

View file

@ -1,44 +0,0 @@
use embedded_graphics::drawable::{Drawable};
use embedded_graphics::DrawTarget;
use embedded_graphics::geometry::Point;
use embedded_graphics::pixelcolor::{PixelColor};
use embedded_graphics::primitives::{Line, Primitive};
use embedded_graphics::style::PrimitiveStyle;
pub struct DrawableCurve<C, I>
where
I: Iterator<Item=Point>,
{
scaled_data: I,
color: C,
}
impl<C,I> DrawableCurve<C,I>
where
C: PixelColor,
I: Iterator<Item=Point>,
{
pub fn new(data: I,color : C) -> DrawableCurve<C,I> {
DrawableCurve {
scaled_data: data,
color,
}
}
}
impl<C,I> Drawable<C> for DrawableCurve<C,I>
where C: PixelColor,
I: Iterator<Item=Point>,
{
fn draw<D: DrawTarget<C>>(self, display: &mut D) -> Result<(), <D as DrawTarget<C>>::Error> {
let style = PrimitiveStyle::with_stroke(self.color,2);
let mut iter = self.scaled_data.into_iter();
let mut prev = iter.next().unwrap();
for point in iter {
Line::new(prev,point)
.into_styled(style)
.draw(display)?;
prev = point;
}
Ok(())
}
}

View file

@ -1,6 +1,7 @@
#![no_std]
pub mod curve;
pub mod plot;
mod drawable_curve;
pub mod curve;
pub mod axis;
pub mod single_plot;
mod range_conv;

View file

@ -1,37 +0,0 @@
use crate::curve::Curve;
use embedded_graphics::drawable::Drawable;
use embedded_graphics::DrawTarget;
use embedded_graphics::prelude::Point;
use embedded_graphics::pixelcolor::PixelColor;
struct Plot<'a, C>
where
C: PixelColor
{
curves: &'a [Curve<'a>],
color: C,
}
impl<'a, C> Plot<'a, C> {
fn new(curves: &'a [Curve<'a>], color : C) -> Plot<C> {
Plot{curves, color}
}
}
impl<C> Drawable<C> for Plot<'_, C>
where
C: PixelColor
{
fn draw<D: DrawTarget<C>>(self, display: &mut D) -> Result<(), <D as DrawTarget<_>>::Error> {
for curve in self.curves {
curve.into_drawable_curve(
&(0..50),
&(-10..10),
&Point{x: 10,y: 10},
&Point{x: 470, y: 270},
self.color
).draw(display)?;
}
Ok(())
}
}

View file

@ -4,7 +4,7 @@ pub trait Scalable<T>
where
T: Copy + Add<Output=T> + Sub<Output=T> + Mul<Output=T> + Div<Output=T>,
{
fn scale_between_ranges(&self,input_range: &Range<T>, output_range: &Range<T>) -> T;
fn scale_between_ranges(&self, input_range: &Range<T>, output_range: &Range<T>) -> T;
}
impl<T> Scalable<T> for T
@ -19,21 +19,20 @@ impl<T> Scalable<T> for T
}
#[cfg(test)]
mod tests {
use core::ops::Range;
use test_case::test_case;
use crate::range_conv::Scalable;
#[test_case(0..10,0..10,5 => 5 ; "equal ranges")]
#[test_case(0..10,0..20,5 => 10 ; "double")]
#[test_case(0..20,0..10,10 => 5 ; "half")]
#[test_case(-20..20,0..10,0 => 5 ; "negative input range")]
#[test_case(0..10,-20..20,5 => 0 ; "negative output range")]
#[test_case(0..10,10..0,2 => 8 ; "reversing")]
#[test_case(-20..20,0..20,-10 => 5 ; "reversing negative range")]
fn convert(in_range: Range<i32>,out_range: Range<i32>,val: i32) -> i32 {
val.scale_between_ranges(&in_range,&out_range)
#[test_case(0..10, 0..10, 5 => 5; "equal ranges")]
#[test_case(0..10, 0..20, 5 => 10; "double")]
#[test_case(0..20, 0..10, 10 => 5; "half")]
#[test_case(- 20..20, 0..10, 0 => 5; "negative input range")]
#[test_case(0..10, - 20..20, 5 => 0; "negative output range")]
#[test_case(0..10, 10..0, 2 => 8; "reversing")]
#[test_case(- 20..20, 0..20, - 10 => 5; "reversing negative range")]
fn convert(in_range: Range<i32>, out_range: Range<i32>, val: i32) -> i32 {
val.scale_between_ranges(&in_range, &out_range)
}
}

114
src/single_plot.rs Normal file
View file

@ -0,0 +1,114 @@
use crate::curve::Curve;
use embedded_graphics::drawable::Drawable;
use embedded_graphics::DrawTarget;
use embedded_graphics::prelude::Point;
use embedded_graphics::pixelcolor::PixelColor;
use crate::axis::{Scale, Placement, Axis};
use embedded_graphics::style::TextStyleBuilder;
use embedded_graphics::fonts::Font6x8;
pub struct SinglePlot<'a> {
curve: &'a Curve<'a>,
x_scale: Scale,
y_scale: Scale,
}
impl<'a> SinglePlot<'a> {
pub fn new(curve: &'a Curve<'a>, x_scale: Scale, y_scale: Scale) -> SinglePlot {
SinglePlot { curve, x_scale, y_scale }
}
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 }
}
}
pub struct DrawableSinglePlot<'a, C>
where
C: PixelColor + Default,
{
plot: SinglePlot<'a>,
color: Option<C>,
text_color: Option<C>,
axis_color: Option<C>,
thickness: Option<usize>,
axis_thickness: Option<usize>,
top_left: Point,
bottom_right: Point,
}
impl<'a, C> DrawableSinglePlot<'a, C>
where
C: PixelColor + Default,
{
pub fn set_color(mut self, color: C) -> DrawableSinglePlot<'a, C> {
self.color = Some(color);
self
}
pub fn set_text_color(mut self, color: C) -> DrawableSinglePlot<'a, C> {
self.text_color = Some(color);
self
}
pub fn set_axis_color(mut self, color: C) -> DrawableSinglePlot<'a, C> {
self.axis_color = Some(color);
self
}
pub fn set_thickness(mut self, thickness: usize) -> DrawableSinglePlot<'a, C> {
self.thickness = Some(thickness);
self
}
pub fn set_axis_thickness(mut self, thickness: usize) -> DrawableSinglePlot<'a, C> {
self.axis_thickness = Some(thickness);
self
}
}
impl<'a, C> Drawable<C> for DrawableSinglePlot<'a, C>
where
C: PixelColor + Default,
{
fn draw<D: DrawTarget<C>>(self, display: &mut D) -> Result<(), D::Error> {
let color = self.color.unwrap_or_default();
let text_color = self.text_color.unwrap_or(color);
let axis_color = self.axis_color.unwrap_or(color);
let thickness = self.thickness.unwrap_or(2);
let axis_thickness = self.axis_thickness.unwrap_or(thickness);
let text_style = TextStyleBuilder::new(Font6x8)
.text_color(text_color)
.build();
Axis::new( self.plot.curve.x_range.clone())
.set_title("X")
.set_scale(self.plot.x_scale)
.into_drawable_axis(Placement::X { x1: self.top_left.x, x2: self.bottom_right.x, y: self.bottom_right.y })
.set_color(axis_color)
.set_text_style(text_style)
.set_tick_size(2)
.set_thickness(axis_thickness)
.draw(display)?;
Axis::new(self.plot.curve.y_range.clone())
.set_title("Y")
.set_scale(self.plot.y_scale)
.into_drawable_axis(Placement::Y { y1: self.top_left.y, y2: self.bottom_right.y, x: self.top_left.x })
.set_color(axis_color)
.set_text_style(text_style)
.set_tick_size(2)
.set_thickness(axis_thickness)
.draw(display)?;
self.plot.curve.into_drawable_curve(
&self.top_left,
&self.bottom_right,
).set_color(color)
.set_thickness(thickness)
.draw(display)?;
Ok(())
}
}