mirror of
https://gitlab.com/feliix42/embedded-plots.git
synced 2024-11-24 18:46:29 +00:00
Port to embedded_graphics 0.7.1
Co-authored-by: Rico1502 <rico.ahlbaeumer@tu-dortmund.de>
This commit is contained in:
parent
5fd842c0cd
commit
df0b244604
9 changed files with 371 additions and 272 deletions
|
@ -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.7.3"
|
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"
|
||||||
|
|
|
@ -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,16 +146,18 @@ 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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +1,33 @@
|
||||||
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(())
|
||||||
}
|
}
|
||||||
|
|
221
src/axis.rs
221
src/axis.rs
|
@ -1,30 +1,26 @@
|
||||||
use core::ops::Range;
|
use core::{fmt::Write, ops::Range};
|
||||||
use core::fmt::Write;
|
|
||||||
use heapless::String;
|
use heapless::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,52 +152,115 @@ 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::<8> = 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::<8> = 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)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
116
src/curve.rs
116
src/curve.rs
|
@ -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)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
15
src/lib.rs
15
src/lib.rs
|
@ -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,9 +110,9 @@
|
||||||
//! 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;
|
||||||
|
|
||||||
mod range_conv;
|
mod range_conv;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,87 +56,86 @@ 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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue