embedded_plots/
curve.rs

1use core::ops::Range;
2
3use crate::range_conv::Scalable;
4use itertools::{Itertools, MinMaxResult, MinMaxResult::MinMax};
5
6use embedded_graphics::{draw_target::DrawTarget, geometry::Point, Drawable};
7
8use embedded_graphics::primitives::Primitive;
9use embedded_graphics::{primitives::Line, primitives::PrimitiveStyle};
10use embedded_graphics::pixelcolor::PixelColor;
11
12/// representation of the single point on the curve
13pub struct PlotPoint {
14    pub x: i32,
15    pub y: i32,
16}
17
18/// curve object that contains data to be plotted
19pub struct Curve<'a> {
20    /// slice of points to be drawn
21    points: &'a [PlotPoint],
22    pub x_range: Range<i32>,
23    pub y_range: Range<i32>,
24}
25
26impl<'a> Curve<'a> {
27    /// create new curve data with manual ranges
28    pub fn new(points: &'a [PlotPoint], x_range: Range<i32>, y_range: Range<i32>) -> Curve {
29        Curve {
30            points,
31            x_range,
32            y_range,
33        }
34    }
35
36    /// create new curve data with ranges automatically deducted based on provided points
37    pub fn from_data(points: &'a [PlotPoint]) -> Curve {
38        let x_range = match points.iter().map(|p| (p.x)).minmax() {
39            MinMaxResult::NoElements => 0..0,
40            MinMaxResult::OneElement(v) => v..v,
41            MinMax(min, max) => min..max,
42        };
43
44        let y_range = match points.iter().map(|p| (p.y)).minmax() {
45            MinMaxResult::NoElements => 0..0,
46            MinMaxResult::OneElement(v) => v..v,
47            MinMax(min, max) => min..max,
48        };
49
50        Curve {
51            points,
52            x_range,
53            y_range,
54        }
55    }
56
57    /// create curve that can be drawed on specific display
58    pub fn into_drawable_curve<C>(
59        &self,
60        top_left: &'a Point,
61        bottom_right: &'a Point,
62    ) -> DrawableCurve<C, impl Iterator<Item = Point> + Clone + '_>
63    where
64        C: PixelColor,
65    {
66        assert!(top_left.x < bottom_right.x);
67        assert!(top_left.y < bottom_right.y);
68        assert!(!self.x_range.is_empty());
69        assert!(!self.y_range.is_empty());
70
71        let it = self.points.iter().map(move |p| Point {
72            x: p.x.scale_between_ranges(
73                &self.x_range,
74                &Range {
75                    start: top_left.x,
76                    end: bottom_right.x,
77                },
78            ),
79            y: p.y.scale_between_ranges(
80                &self.y_range,
81                &Range {
82                    start: bottom_right.y,
83                    end: top_left.y,
84                },
85            ),
86        });
87        DrawableCurve {
88            scaled_data: it,
89            color: None,
90            thickness: None,
91        }
92    }
93}
94
95/// Drawable curve object, constructed for specific display
96pub struct DrawableCurve<C, I> {
97    scaled_data: I,
98    color: Option<C>,
99    thickness: Option<usize>,
100}
101
102/// builder methods to modify curve decoration
103impl<C, I> DrawableCurve<C, I>
104where
105    C: PixelColor,
106    I: Iterator<Item = Point> + Clone,
107{
108    /// set curve color
109    pub fn set_color(mut self, color: C) -> DrawableCurve<C, I> {
110        self.color = Some(color);
111        self
112    }
113
114    /// set curve line thickness
115    pub fn set_thickness(mut self, thickness: usize) -> DrawableCurve<C, I> {
116        self.thickness = Some(thickness);
117        self
118    }
119}
120
121impl<C, I> Drawable for DrawableCurve<C, I>
122where
123    C: PixelColor + Default,
124    I: Iterator<Item = Point> + Clone,
125{
126    type Color = C;
127    type Output = ();
128
129    /// most important function - draw the curve on the display
130    fn draw<D: DrawTarget<Color = C>>(
131        &self,
132        display: &mut D,
133    ) -> Result<(), <D as DrawTarget>::Error> {
134        let color = match &self.color {
135            None => C::default(),
136            Some(c) => *c,
137        };
138        let thickness = match &self.thickness {
139            None => 2,
140            Some(t) => *t,
141        };
142        let style = PrimitiveStyle::with_stroke(color, thickness as u32);
143        self.scaled_data.clone().tuple_windows().try_for_each(
144            |(prev, point)| -> Result<(), D::Error> {
145                Line::new(prev, point).into_styled(style).draw(display)
146            },
147        )
148    }
149}