Merge branch 'feature/bump-dep-versions' into 'master'

Port to embedded_graphics 0.7.1

See merge request mchodzikiewicz/embedded-plots!20
This commit is contained in:
Michał Chodzikiewicz 2022-06-27 17:52:40 +00:00
commit 0ce5278b52
9 changed files with 373 additions and 274 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "embedded-plots" name = "embedded-plots"
version = "0.1.2" version = "0.2.0"
authors = ["Michał Chodzikiewicz <mchodzikiewicz@gmail.com>"] authors = ["Michał Chodzikiewicz <mchodzikiewicz@gmail.com>"]
edition = "2018" edition = "2018"
license = "LGPL-2.1-only" license = "LGPL-2.1-only"
@ -14,10 +14,10 @@ categories = ["embedded","visualization","no-std","graphics"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
embedded-graphics = "0.6.0" embedded-graphics = "0.7.1"
itertools = {version = "0.9.0", default-features = false} itertools = {version = "0.9.0", default-features = false }
heapless = "0.5.6" heapless = "0.7.3"
[dev-dependencies] [dev-dependencies]
embedded-graphics-simulator = "0.2.1" embedded-graphics-simulator = "0.3.0"
test-case = "1.0.0" test-case = "1.0.0"

View file

@ -1,35 +1,38 @@
use embedded_graphics::{ use embedded_graphics::{
mono_font::{
ascii::{FONT_5X8, FONT_5X7},
MonoTextStyleBuilder,
},
pixelcolor::Rgb565, pixelcolor::Rgb565,
prelude::*, prelude::*,
style::TextStyleBuilder,
fonts::{Font6x8, Font6x6},
}; };
use embedded_graphics_simulator::{ use embedded_graphics_simulator::{OutputSettingsBuilder, SimulatorDisplay, Window};
SimulatorDisplay,
Window,
OutputSettingsBuilder,
};
use embedded_plots::{ use embedded_graphics::pixelcolor::RgbColor;
axis::{Axis, Placement, Scale}, use embedded_plots::axis::{Axis, Placement, Scale};
};
fn main() -> Result<(), core::convert::Infallible> { fn main() -> Result<(), core::convert::Infallible> {
let mut display: SimulatorDisplay<Rgb565> = SimulatorDisplay::new(Size::new(480, 272)); let mut display: SimulatorDisplay<Rgb565> = SimulatorDisplay::new(Size::new(480, 272));
let text_style_white = TextStyleBuilder::new(Font6x8) let text_style_white = MonoTextStyleBuilder::new()
.font(&FONT_5X8)
.text_color(RgbColor::WHITE) .text_color(RgbColor::WHITE)
.build(); .build();
let text_style_yellow_compact = TextStyleBuilder::new(Font6x6) let text_style_yellow_compact = MonoTextStyleBuilder::new()
.font(&FONT_5X7)
.text_color(RgbColor::YELLOW) .text_color(RgbColor::YELLOW)
.build(); .build();
Axis::new(0..100) Axis::new(0..100)
.set_title("X Fixed 0-100(10)") .set_title("X Fixed 0-100(10)")
.set_scale(Scale::Fixed(10)) .set_scale(Scale::Fixed(10))
.into_drawable_axis(Placement::X { x1: 40, x2: 230, y: 10 }) .into_drawable_axis(Placement::X {
x1: 40,
x2: 230,
y: 10,
})
.set_color(RgbColor::WHITE) .set_color(RgbColor::WHITE)
.set_text_style(text_style_white) .set_text_style(text_style_white)
.set_thickness(2) .set_thickness(2)
@ -39,7 +42,11 @@ fn main() -> Result<(), core::convert::Infallible> {
Axis::new(0..200) Axis::new(0..200)
.set_title("X Fixed 0-200(100)") .set_title("X Fixed 0-200(100)")
.set_scale(Scale::Fixed(100)) .set_scale(Scale::Fixed(100))
.into_drawable_axis(Placement::X { x1: 240, x2: 470, y: 10 }) .into_drawable_axis(Placement::X {
x1: 240,
x2: 470,
y: 10,
})
.set_color(RgbColor::YELLOW) .set_color(RgbColor::YELLOW)
.set_text_style(text_style_yellow_compact) .set_text_style(text_style_yellow_compact)
.set_tick_size(2) .set_tick_size(2)
@ -48,7 +55,11 @@ fn main() -> Result<(), core::convert::Infallible> {
Axis::new(0..100) Axis::new(0..100)
.set_title("X Frac 0-100(7)") .set_title("X Frac 0-100(7)")
.set_scale(Scale::RangeFraction(7)) .set_scale(Scale::RangeFraction(7))
.into_drawable_axis(Placement::X { x1: 50, x2: 220, y: 30 }) .into_drawable_axis(Placement::X {
x1: 50,
x2: 220,
y: 30,
})
.set_color(RgbColor::BLUE) .set_color(RgbColor::BLUE)
.set_text_style(text_style_white) .set_text_style(text_style_white)
.set_tick_size(3) .set_tick_size(3)
@ -57,7 +68,11 @@ fn main() -> Result<(), core::convert::Infallible> {
Axis::new(0..200) Axis::new(0..200)
.set_title("X Frac 0-200(4)") .set_title("X Frac 0-200(4)")
.set_scale(Scale::RangeFraction(4)) .set_scale(Scale::RangeFraction(4))
.into_drawable_axis(Placement::X { x1: 250, x2: 460, y: 40 }) .into_drawable_axis(Placement::X {
x1: 250,
x2: 460,
y: 40,
})
.set_color(RgbColor::RED) .set_color(RgbColor::RED)
.set_text_style(text_style_yellow_compact) .set_text_style(text_style_yellow_compact)
.set_tick_size(7) .set_tick_size(7)
@ -66,8 +81,11 @@ fn main() -> Result<(), core::convert::Infallible> {
Axis::new(0..100) Axis::new(0..100)
.set_title("Y Fixed 0-100(10)") .set_title("Y Fixed 0-100(10)")
.set_scale(Scale::Fixed(10)) .set_scale(Scale::Fixed(10))
.into_drawable_axis( .into_drawable_axis(Placement::Y {
Placement::Y { y1: 70, y2: 230, x: 160 }) y1: 70,
y2: 230,
x: 130,
})
.set_color(RgbColor::WHITE) .set_color(RgbColor::WHITE)
.set_text_style(text_style_white) .set_text_style(text_style_white)
.set_tick_size(2) .set_tick_size(2)
@ -76,8 +94,11 @@ fn main() -> Result<(), core::convert::Infallible> {
Axis::new(0..200) Axis::new(0..200)
.set_title("Y Fixed 0-200(100)") .set_title("Y Fixed 0-200(100)")
.set_scale(Scale::Fixed(100)) .set_scale(Scale::Fixed(100))
.into_drawable_axis( .into_drawable_axis(Placement::Y {
Placement::Y { y1: 70, y2: 210, x: 260 }) y1: 70,
y2: 210,
x: 260,
})
.set_color(RgbColor::YELLOW) .set_color(RgbColor::YELLOW)
.set_text_style(text_style_yellow_compact) .set_text_style(text_style_yellow_compact)
.set_tick_size(1) .set_tick_size(1)
@ -86,7 +107,11 @@ fn main() -> Result<(), core::convert::Infallible> {
Axis::new(0..100) Axis::new(0..100)
.set_title("Y Frac 0-100(7)") .set_title("Y Frac 0-100(7)")
.set_scale(Scale::RangeFraction(7)) .set_scale(Scale::RangeFraction(7))
.into_drawable_axis(Placement::Y { y1: 60, y2: 180, x: 370 }) .into_drawable_axis(Placement::Y {
y1: 60,
y2: 180,
x: 370,
})
.set_color(RgbColor::BLUE) .set_color(RgbColor::BLUE)
.set_text_style(text_style_white) .set_text_style(text_style_white)
.set_tick_size(3) .set_tick_size(3)
@ -95,7 +120,11 @@ fn main() -> Result<(), core::convert::Infallible> {
Axis::new(0..200) Axis::new(0..200)
.set_title("Y Frac 0-200(4)") .set_title("Y Frac 0-200(4)")
.set_scale(Scale::RangeFraction(4)) .set_scale(Scale::RangeFraction(4))
.into_drawable_axis(Placement::Y { y1: 90, y2: 220, x: 470 }) .into_drawable_axis(Placement::Y {
y1: 90,
y2: 220,
x: 470,
})
.set_color(RgbColor::RED) .set_color(RgbColor::RED)
.set_text_style(text_style_yellow_compact) .set_text_style(text_style_yellow_compact)
.set_tick_size(7) .set_tick_size(7)
@ -104,7 +133,11 @@ fn main() -> Result<(), core::convert::Infallible> {
Axis::new(123..2137) Axis::new(123..2137)
.set_title("X") .set_title("X")
.set_scale(Scale::Fixed(150)) .set_scale(Scale::Fixed(150))
.into_drawable_axis(Placement::X { x1: 30, x2: 470, y: 250 }) .into_drawable_axis(Placement::X {
x1: 30,
x2: 470,
y: 250,
})
.set_color(RgbColor::YELLOW) .set_color(RgbColor::YELLOW)
.set_text_style(text_style_white) .set_text_style(text_style_white)
.set_tick_size(2) .set_tick_size(2)
@ -113,15 +146,17 @@ fn main() -> Result<(), core::convert::Infallible> {
Axis::new(0..2137) Axis::new(0..2137)
.set_title("Y") .set_title("Y")
.set_scale(Scale::RangeFraction(15)) .set_scale(Scale::RangeFraction(15))
.into_drawable_axis(Placement::Y { y1: 10, y2: 250, x: 30 }) .into_drawable_axis(Placement::Y {
y1: 10,
y2: 250,
x: 30,
})
.set_color(RgbColor::WHITE) .set_color(RgbColor::WHITE)
.set_text_style(text_style_white) .set_text_style(text_style_white)
.set_tick_size(2) .set_tick_size(2)
.draw(&mut display)?; .draw(&mut display)?;
let output_settings = OutputSettingsBuilder::new().build();
let output_settings = OutputSettingsBuilder::new()
.build();
Window::new("Free axis", &output_settings).show_static(&display); Window::new("Free axis", &output_settings).show_static(&display);
Ok(()) Ok(())

View file

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

View file

@ -1,42 +1,32 @@
use embedded_graphics::{ use embedded_graphics::{pixelcolor::Rgb565, prelude::*};
pixelcolor::Rgb565,
prelude::*,
};
use embedded_graphics_simulator::{ use embedded_graphics_simulator::{OutputSettingsBuilder, SimulatorDisplay, Window};
SimulatorDisplay,
Window,
OutputSettingsBuilder,
};
use embedded_plots::{ use embedded_plots::{
single_plot::{SinglePlot},
curve::{PlotPoint, Curve},
axis::Scale, axis::Scale,
curve::{Curve, PlotPoint},
single_plot::SinglePlot,
}; };
fn main() -> Result<(), core::convert::Infallible> { fn main() -> Result<(), core::convert::Infallible> {
let mut display: SimulatorDisplay<Rgb565> = SimulatorDisplay::new(Size::new(480, 272)); let mut display: SimulatorDisplay<Rgb565> = SimulatorDisplay::new(Size::new(480, 272));
let data = vec![ let data = vec![
PlotPoint { x: 0, y: 0 }, PlotPoint { x: 0, y: 0 },
PlotPoint { x: 1, y: 2 }, PlotPoint { x: 1, y: 2 },
PlotPoint { x: 2, y: 2 }, PlotPoint { x: 2, y: 2 },
PlotPoint { x: 3, y: 0 }, PlotPoint { x: 3, y: 0 },
]; ];
let curve = Curve::from_data(data.as_slice());
let plot = SinglePlot::new( let curve = Curve::from_data(data.as_slice());
&curve, let plot = SinglePlot::new(&curve, Scale::RangeFraction(3), Scale::RangeFraction(2))
Scale::RangeFraction(3), .into_drawable(Point { x: 50, y: 10 }, Point { x: 430, y: 250 })
Scale::RangeFraction(2)).into_drawable( .set_color(RgbColor::YELLOW)
Point { x: 50, y: 10 }, .set_text_color(RgbColor::WHITE);
Point { x: 430, y: 250 },
).set_color(RgbColor::YELLOW).set_text_color(RgbColor::WHITE);
plot.draw(&mut display)?; plot.draw(&mut display)?;
let output_settings = OutputSettingsBuilder::new()
.build(); let output_settings = OutputSettingsBuilder::new().build();
Window::new("Basic plot", &output_settings).show_static(&display); Window::new("Basic plot", &output_settings).show_static(&display);
Ok(()) Ok(())

View file

@ -1,30 +1,26 @@
use core::ops::Range; use core::{fmt::Write, ops::Range};
use core::fmt::Write; use heapless::String;
use heapless::{consts::*, String};
use embedded_graphics::{ use embedded_graphics::{
prelude::*, prelude::*,
style::{TextStyle, PrimitiveStyle}, primitives::{Line, PrimitiveStyle},
primitives::Line, text::Text,
fonts::Text, text::TextStyle,
}; };
use crate::range_conv::Scalable;
/// Used to provide alignment of an axis, it will be drown exactly on the line marked by the points use crate::range_conv::Scalable;
use embedded_graphics::mono_font::ascii::FONT_5X8;
use embedded_graphics::mono_font::MonoTextStyle;
use embedded_graphics::text::{Alignment, Baseline, TextStyleBuilder};
/// Used to provide alignment of an axis, it will be dsizerown exactly on the line marked by the points
pub enum Placement { pub enum Placement {
X { X { x1: i32, x2: i32, y: i32 },
x1: i32, Y { y1: i32, y2: i32, x: i32 },
x2: i32,
y: i32,
},
Y {
y1: i32,
y2: i32,
x: i32,
},
} }
/// Used to describe how densely ticks should be drawn /// Used to describe how densely ticks should be drawn
#[derive(Clone, Copy)]
pub enum Scale { pub enum Scale {
/// Fixed scale means that ticks will be drawn between each increment of absolute distance provided. /// 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 /// for example, on range 0..30 and Fixed(10), ticks will be drawn for 0, 10 and 20
@ -51,11 +47,14 @@ pub struct Axis<'a> {
} }
/// builder methods to modify axis decoration /// builder methods to modify axis decoration
impl<'a> Axis<'a> impl<'a> Axis<'a> {
{
/// create new axis data /// 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 /// define how scale ticks should be drawn
@ -71,13 +70,12 @@ impl<'a> Axis<'a>
} }
/// turn axis data into drawable object suitable for specific display /// 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>(self, placement: Placement) -> DrawableAxis<'a, C>
where where
C: PixelColor + Default, C: PixelColor + Default,
F: Font, TextStyle: Clone + Default,
TextStyle<C, F>: Clone + Default,
{ {
DrawableAxis{ DrawableAxis {
axis: self, axis: self,
placement, placement,
color: None, color: None,
@ -89,68 +87,64 @@ impl<'a> Axis<'a>
} }
/// Drawable axis object, constructed for specific display /// Drawable axis object, constructed for specific display
pub struct DrawableAxis<'a, C, F> pub struct DrawableAxis<'a, C>
where where
C: PixelColor, C: PixelColor,
F: Font, TextStyle: Clone + Default,
TextStyle<C, F>: Clone + Default,
{ {
axis: Axis<'a>, axis: Axis<'a>,
placement: Placement, placement: Placement,
color: Option<C>, color: Option<C>,
text_style: Option<TextStyle<C, F>>, text_style: Option<MonoTextStyle<'a, C>>,
tick_size: Option<usize>, tick_size: Option<usize>,
thickness: Option<usize>, thickness: Option<usize>,
} }
impl<'a, C, F> DrawableAxis<'a, C, F> impl<'a, C> DrawableAxis<'a, C>
where where
C: PixelColor + Default, C: PixelColor + Default,
F: Font, TextStyle: Clone + Default,
TextStyle<C, F>: Clone + Default,
{ {
pub fn set_color(mut self, val: C) -> DrawableAxis<'a, C, F> { pub fn set_color(mut self, val: C) -> DrawableAxis<'a, C> {
self.color = Some(val); self.color = Some(val);
self self
} }
pub fn set_text_style(mut self, val: TextStyle<C, F>) -> DrawableAxis<'a, C, F> { pub fn set_text_style(mut self, val: MonoTextStyle<'a, C>) -> DrawableAxis<'a, C> {
self.text_style = Some(val); self.text_style = Some(val);
self self
} }
/// set how wide tick should be drawn on the axis /// 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> {
self.tick_size = Some(val); self.tick_size = Some(val);
self self
} }
/// set thickness of the main line of the axis /// 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> {
self.thickness = Some(val); self.thickness = Some(val);
self self
} }
} }
impl<'a, C> Drawable for DrawableAxis<'a, C>
impl<'a, C, F> Drawable<C> for DrawableAxis<'a, C, F> where
where C: PixelColor + Default,
C: PixelColor + Default, TextStyle: Clone + Default,
F: Font + Copy,
TextStyle<C, F>: Clone + Default,
{ {
type Color = C;
type Output = ();
/// most important function - draw the axis on the display /// 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<Color = 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 thickness = self.thickness.unwrap_or(1); let thickness = self.thickness.unwrap_or(1);
let tick_size = self.tick_size.unwrap_or(2); let tick_size = self.tick_size.unwrap_or(2);
let character_style = MonoTextStyle::new(&FONT_5X8, color);
let scale_marks = match self.axis.scale.unwrap_or_default() { let scale_marks = match self.axis.scale.unwrap_or_default() {
Scale::Fixed(interval) => { Scale::Fixed(interval) => self.axis.range.clone().into_iter().step_by(interval),
self.axis.range.clone().into_iter().step_by(interval)
}
Scale::RangeFraction(fraction) => { Scale::RangeFraction(fraction) => {
let len = self.axis.range.len(); let len = self.axis.range.len();
self.axis.range.clone().into_iter().step_by(len / fraction) self.axis.range.clone().into_iter().step_by(len / fraction)
@ -158,49 +152,112 @@ impl<'a, C, F> Drawable<C> for DrawableAxis<'a, C, F>
}; };
match self.placement { match self.placement {
Placement::X { x1, x2, y } => { Placement::X { x1, x2, y } => {
Line { start: Point { x: x1, y }, end: Point { x: x2, y } } let title_text_style = TextStyleBuilder::new()
.into_styled(PrimitiveStyle::with_stroke(color, thickness as u32)) .alignment(Alignment::Center)
.draw(display)?; .baseline(Baseline::Top)
if let Some(title) = self.axis.title { .build();
let title = Text::new(title, Point { x: x1, y: y + 10 }) let tick_text_style = TextStyleBuilder::new()
.into_styled(text_style); .alignment(Alignment::Left)
let title = title.translate(Point { x: (x2 - x1) / 2 - title.size().width as i32 / 2, y: 0 }); .baseline(Baseline::Top)
title.draw(display)?; .build();
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 {
Text::with_text_style(
title,
Point {
x: x1 + (x2 - x1) / 2,
y: y + 10,
},
character_style,
title_text_style,
)
.draw(display)?;
} }
for mark in scale_marks { for mark in scale_marks {
let x = mark.scale_between_ranges(&self.axis.range, &(x1..x2)); 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 } } Line {
.into_styled(PrimitiveStyle::with_stroke(color, thickness as u32)) start: Point {
.draw(display)?; x,
let mut buf: String::<U8> = String::new(); 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<8> = String::new();
write!(buf, "{}", mark).unwrap(); write!(buf, "{}", mark).unwrap();
Text::new(&buf, Point { x: x + 2, y: y + 2 }).into_styled(text_style).draw(display)?; Text::with_text_style(
&buf,
Point { x: x + 2, y: y + 2 },
character_style,
tick_text_style,
)
.draw(display)?;
} }
} }
Placement::Y { y1, y2, x } => { Placement::Y { y1, y2, x } => {
Line { start: Point { x, y: y1 }, end: Point { x, y: y2 } } let title_text_style = TextStyleBuilder::new()
.into_styled(PrimitiveStyle::with_stroke(color, thickness as u32)) .alignment(Alignment::Right)
.draw(display)?; .baseline(Baseline::Middle)
.build();
let tick_text_style = TextStyleBuilder::new()
.alignment(Alignment::Right)
.baseline(Baseline::Top)
.build();
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; let mut tick_text_left_pos_bound = i32::MAX;
for mark in scale_marks { for mark in scale_marks {
let y = mark.scale_between_ranges(&self.axis.range, &(y2..y1)); 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 } } Line {
.into_styled(PrimitiveStyle::with_stroke(color, thickness as u32)) start: Point {
.draw(display)?; x: x - tick_size as i32,
let mut buf: String::<U8> = String::new(); 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<8> = String::new();
write!(buf, "{}", mark).unwrap(); write!(buf, "{}", mark).unwrap();
let tick_val = Text::new(&buf, Point { x, y }).into_styled(text_style); let tick_val = Text::with_text_style(
let tick_val = tick_val.translate(Point { x: -(tick_val.size().width as i32) - 2, y: 2 }); &buf,
if tick_val.size().width > max_tick_text_width { max_tick_text_width = tick_val.size().width } Point { x, y },
character_style,
tick_text_style,
);
if tick_val.bounding_box().top_left.x < tick_text_left_pos_bound {
tick_text_left_pos_bound = tick_val.bounding_box().top_left.x
};
tick_val.draw(display)?; tick_val.draw(display)?;
} }
if let Some(title) = self.axis.title { if let Some(title) = self.axis.title {
let title = Text::new(title, Point { x, y: y1 }) Text::with_text_style(
.into_styled(text_style); title,
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 }); Point {
title.draw(display)?; x: tick_text_left_pos_bound - 1,
y: y1 + (y2 - y1) / 2,
},
character_style,
title_text_style,
)
.draw(display)?;
} }
} }
} }

View file

@ -1,14 +1,13 @@
use core::ops::{Range}; use core::ops::Range;
use crate::range_conv::Scalable; use crate::range_conv::Scalable;
use itertools::{Itertools, MinMaxResult::MinMax, MinMaxResult}; use itertools::{Itertools, MinMaxResult, MinMaxResult::MinMax};
use embedded_graphics::drawable::{Drawable}; use embedded_graphics::{draw_target::DrawTarget, geometry::Point, Drawable};
use embedded_graphics::DrawTarget;
use embedded_graphics::geometry::Point; use embedded_graphics::primitives::Primitive;
use embedded_graphics::pixelcolor::{PixelColor}; use embedded_graphics::{primitives::Line, primitives::PrimitiveStyle};
use embedded_graphics::primitives::{Line, Primitive}; use embedded_graphics::pixelcolor::PixelColor;
use embedded_graphics::style::PrimitiveStyle;
/// representation of the single point on the curve /// representation of the single point on the curve
pub struct PlotPoint { pub struct PlotPoint {
@ -27,15 +26,16 @@ pub struct Curve<'a> {
impl<'a> Curve<'a> { impl<'a> Curve<'a> {
/// create new curve data with manual ranges /// 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 /// 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().map(|p| (p.x)).minmax() {
.iter()
.map(|p| (p.x))
.minmax() {
MinMaxResult::NoElements => 0..0, MinMaxResult::NoElements => 0..0,
MinMaxResult::OneElement(v) => v..v, MinMaxResult::OneElement(v) => v..v,
MinMax(min, max) => min..max, MinMax(min, max) => min..max,
@ -47,32 +47,43 @@ impl<'a> Curve<'a> {
MinMax(min, max) => min..max, MinMax(min, max) => min..max,
}; };
Curve { points, x_range, y_range } Curve {
points,
x_range,
y_range,
}
} }
/// create curve that can be drawed on specific display /// create curve that can be drawed on specific display
pub fn into_drawable_curve<C>(&self, pub fn into_drawable_curve<C>(
top_left: &'a Point, &self,
bottom_right: &'a Point, top_left: &'a Point,
) -> DrawableCurve<C, impl Iterator<Item=Point> + '_> bottom_right: &'a Point,
where C: PixelColor ) -> DrawableCurve<C, impl Iterator<Item = Point> + Clone + '_>
where
C: PixelColor,
{ {
assert!(top_left.x < bottom_right.x); assert!(top_left.x < bottom_right.x);
assert!(top_left.y < bottom_right.y); assert!(top_left.y < bottom_right.y);
assert!(!self.x_range.is_empty()); assert!(!self.x_range.is_empty());
assert!(!self.y_range.is_empty()); assert!(!self.y_range.is_empty());
let it = self.points.iter() let it = self.points.iter().map(move |p| Point {
.map(move |p| Point { x: p.x.scale_between_ranges(
x: p.x.scale_between_ranges( &self.x_range,
&self.x_range, &Range {
&Range { start: top_left.x, end: bottom_right.x }, start: top_left.x,
), end: bottom_right.x,
y: p.y.scale_between_ranges( },
&self.y_range, ),
&Range { start: bottom_right.y, end: top_left.y }, y: p.y.scale_between_ranges(
), &self.y_range,
}); &Range {
start: bottom_right.y,
end: top_left.y,
},
),
});
DrawableCurve { DrawableCurve {
scaled_data: it, scaled_data: it,
color: None, color: None,
@ -82,8 +93,7 @@ impl<'a> Curve<'a> {
} }
/// Drawable curve object, constructed for specific display /// Drawable curve object, constructed for specific display
pub struct DrawableCurve<C, I> pub struct DrawableCurve<C, I> {
{
scaled_data: I, scaled_data: I,
color: Option<C>, color: Option<C>,
thickness: Option<usize>, thickness: Option<usize>,
@ -91,9 +101,9 @@ pub struct DrawableCurve<C, I>
/// builder methods to modify curve decoration /// 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> + Clone,
{ {
/// set curve color /// 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> {
@ -108,28 +118,32 @@ impl<C, I> DrawableCurve<C, I>
} }
} }
impl<C, I> Drawable<C> for DrawableCurve<C, I> impl<C, I> Drawable for DrawableCurve<C, I>
where C: PixelColor + Default, where
I: Iterator<Item=Point>, C: PixelColor + Default,
I: Iterator<Item = Point> + Clone,
{ {
type Color = C;
type Output = ();
/// most important function - draw the curve on the display /// 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<Color = C>>(
let color = match self.color { &self,
display: &mut D,
) -> Result<(), <D as DrawTarget>::Error> {
let color = match &self.color {
None => C::default(), None => C::default(),
Some(c) => c, Some(c) => *c,
}; };
let thickness = match self.thickness { let thickness = match &self.thickness {
None => 2, None => 2,
Some(t) => t, Some(t) => *t,
}; };
let style = PrimitiveStyle::with_stroke(color, thickness as u32); let style = PrimitiveStyle::with_stroke(color, thickness as u32);
self.scaled_data self.scaled_data.clone().tuple_windows().try_for_each(
.tuple_windows() |(prev, point)| -> Result<(), D::Error> {
.try_for_each(|(prev, point)| -> Result<(), D::Error> { Line::new(prev, point).into_styled(style).draw(display)
Line::new(prev, point) },
.into_styled(style) )
.draw(display)
})
} }
} }

View file

@ -27,7 +27,7 @@
//! use embedded_plots::axis::Scale; //! use embedded_plots::axis::Scale;
//! use embedded_graphics::geometry::{Point, Size}; //! use embedded_graphics::geometry::{Point, Size};
//! use embedded_graphics::pixelcolor::{RgbColor, Rgb565}; //! use embedded_graphics::pixelcolor::{RgbColor, Rgb565};
//! use embedded_graphics::drawable::Drawable; //! use embedded_graphics::Drawable;
//! //!
//! //simulator dependencies, aka screen driver //! //simulator dependencies, aka screen driver
//! use embedded_graphics_simulator::SimulatorDisplay; //! use embedded_graphics_simulator::SimulatorDisplay;
@ -61,17 +61,18 @@
//! ```rust //! ```rust
//! use embedded_plots::axis::{Axis, Scale, Placement}; //! use embedded_plots::axis::{Axis, Scale, Placement};
//! use embedded_graphics::pixelcolor::{RgbColor, Rgb565}; //! use embedded_graphics::pixelcolor::{RgbColor, Rgb565};
//! use embedded_graphics::drawable::Drawable; //! use embedded_graphics::Drawable;
//! use embedded_graphics::geometry::Size; //! use embedded_graphics::geometry::Size;
//! //!
//! //simulator dependencies, aka screen driver //! //simulator dependencies, aka screen driver
//! use embedded_graphics_simulator::SimulatorDisplay; //! use embedded_graphics_simulator::SimulatorDisplay;
//! use embedded_graphics::style::TextStyleBuilder; //! use embedded_graphics::mono_font::MonoTextStyleBuilder;
//! use embedded_graphics::fonts::Font6x8; //! use embedded_graphics::mono_font::ascii::FONT_5X8;
//! //!
//! let mut display: SimulatorDisplay<Rgb565> = SimulatorDisplay::new(Size::new(480, 272)); //! let mut display: SimulatorDisplay<Rgb565> = SimulatorDisplay::new(Size::new(480, 272));
//! //!
//! let text_style_white = TextStyleBuilder::new(Font6x8) //! let text_style_white = MonoTextStyleBuilder::new()
//! .font(&FONT_5X8)
//! .text_color(RgbColor::WHITE) //! .text_color(RgbColor::WHITE)
//! .build(); //! .build();
//! Axis::new(0..100) //! Axis::new(0..100)
@ -109,8 +110,8 @@
//! 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. //! 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] #![no_std]
pub mod curve;
pub mod axis; pub mod axis;
pub mod curve;
/// plot that draws single data series /// plot that draws single data series
pub mod single_plot; pub mod single_plot;

View file

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

View file

@ -1,13 +1,11 @@
use crate::axis::{Axis, Placement, Scale};
use crate::curve::Curve; use crate::curve::Curve;
use embedded_graphics::drawable::Drawable; use embedded_graphics::mono_font::MonoTextStyleBuilder;
use embedded_graphics::DrawTarget; use embedded_graphics::{
use embedded_graphics::prelude::Point; draw_target::DrawTarget, pixelcolor::PixelColor, prelude::Point, Drawable,
use embedded_graphics::pixelcolor::PixelColor; };
use crate::axis::{Scale, Placement, Axis};
use embedded_graphics::style::TextStyleBuilder;
use embedded_graphics::fonts::Font6x8;
/// Display agnostic single curve plot object /// Display agnostic single curve plot object
#[derive(Clone, Copy)]
pub struct SinglePlot<'a> { pub struct SinglePlot<'a> {
/// curve to be drawn on the plot /// curve to be drawn on the plot
curve: &'a Curve<'a>, curve: &'a Curve<'a>,
@ -16,25 +14,38 @@ pub struct SinglePlot<'a> {
/// range of Y axis on which curve will be drawn /// 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 /// 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 //TODO: add auto range plot constructor
/// convert to drawable form for specific display /// 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>(
DrawableSinglePlot { plot: self, color: None, text_color: None, axis_color: None, thickness: None, axis_thickness: None, top_left, bottom_right } 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 /// 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,
{ {
plot: SinglePlot<'a>, plot: SinglePlot<'a>,
color: Option<C>, color: Option<C>,
@ -45,85 +56,84 @@ pub struct DrawableSinglePlot<'a, C>
top_left: Point, top_left: Point,
bottom_right: Point, bottom_right: Point,
} }
/// builder methods to modify plot decoration /// 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,
{ {
pub fn set_color(mut self, color: C) -> DrawableSinglePlot<'a, C> { pub fn set_color(mut self, color: C) -> DrawableSinglePlot<'a, C> {
self.color = Some(color); self.color = Some(color);
self self
} }
/// if not set, main color will be used /// 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 /// 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 /// 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 ///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 //TODO: add axis ticks thickness
} }
impl<'a, C> Drawable for DrawableSinglePlot<'a, C>
impl<'a, C> Drawable<C> for DrawableSinglePlot<'a, C> where
where C: PixelColor + Default,
C: PixelColor + Default,
{ {
type Color = C;
type Output = ();
/// most important function - draw the plot on the display /// most important function - draw the plot on the display
fn draw<D: DrawTarget<C>>(self, display: &mut D) -> Result<(), D::Error> { fn draw<D>(&self, display: &mut D) -> Result<Self::Output, D::Error>
where
D: DrawTarget<Color = C>,
{
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);
let axis_color = self.axis_color.unwrap_or(color); let axis_color = self.axis_color.unwrap_or(color);
let thickness = self.thickness.unwrap_or(2); let thickness = self.thickness.unwrap_or(2);
let axis_thickness = self.axis_thickness.unwrap_or(thickness); let axis_thickness = self.axis_thickness.unwrap_or(thickness);
let text_style = MonoTextStyleBuilder::new().text_color(text_color).build();
let text_style = TextStyleBuilder::new(Font6x8) Axis::new(self.plot.curve.x_range.clone())
.text_color(text_color)
.build();
Axis::new( self.plot.curve.x_range.clone())
.set_title("X") .set_title("X")
.set_scale(self.plot.x_scale) .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 }) .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_color(axis_color)
.set_text_style(text_style) .set_text_style(text_style)
.set_tick_size(2) .set_tick_size(2)
.set_thickness(axis_thickness) .set_thickness(axis_thickness)
.draw(display)?; .draw(display)?;
Axis::new(self.plot.curve.y_range.clone()) Axis::new(self.plot.curve.y_range.clone())
.set_title("Y") .set_title("Y")
.set_scale(self.plot.y_scale) .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 }) .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_color(axis_color)
.set_text_style(text_style) .set_text_style(text_style)
.set_tick_size(2) .set_tick_size(2)
.set_thickness(axis_thickness) .set_thickness(axis_thickness)
.draw(display)?; .draw(display)?;
self.plot
self.plot.curve.into_drawable_curve( .curve
&self.top_left, .into_drawable_curve(&self.top_left, &self.bottom_right)
&self.bottom_right, .set_color(color)
).set_color(color)
.set_thickness(thickness) .set_thickness(thickness)
.draw(display)?; .draw(display)?;
Ok(()) Ok(())