1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use core::ops::{Range};

use crate::range_conv::Scalable;
use itertools::{Itertools, MinMaxResult::MinMax, MinMaxResult};

use embedded_graphics::drawable::{Drawable};
use embedded_graphics::DrawTarget;
use embedded_graphics::geometry::Point;
use embedded_graphics::pixelcolor::{PixelColor};
use embedded_graphics::primitives::{Line, Primitive};
use embedded_graphics::style::PrimitiveStyle;

/// representation of the single point on the curve
pub struct PlotPoint {
    pub x: i32,
    pub y: i32,
}

/// curve object that contains data to be plotted
pub struct Curve<'a> {
    /// slice of points to be drawn
    points: &'a [PlotPoint],
    pub x_range: Range<i32>,
    pub y_range: Range<i32>,
}

impl<'a> Curve<'a> {
    /// create new curve data with manual ranges
    pub fn new(points: &'a [PlotPoint], x_range: Range<i32>, y_range: Range<i32>) -> Curve {
        Curve { points, x_range, y_range }
    }

    /// create new curve data with ranges automatically deducted based on provided points
    pub fn from_data(points: &'a [PlotPoint]) -> Curve {
        let x_range = match points
            .iter()
            .map(|p| (p.x))
            .minmax() {
            MinMaxResult::NoElements => 0..0,
            MinMaxResult::OneElement(v) => v..v,
            MinMax(min, max) => min..max,
        };

        let y_range = match points.iter().map(|p| (p.y)).minmax() {
            MinMaxResult::NoElements => 0..0,
            MinMaxResult::OneElement(v) => v..v,
            MinMax(min, max) => min..max,
        };

        Curve { points, x_range, y_range }
    }

    /// create curve that can be drawed on specific display
    pub fn into_drawable_curve<C>(&self,
                                  top_left: &'a Point,
                                  bottom_right: &'a Point,
    ) -> DrawableCurve<C, impl Iterator<Item=Point> + '_>
        where C: PixelColor
    {
        assert!(top_left.x < bottom_right.x);
        assert!(top_left.y < bottom_right.y);
        assert!(!self.x_range.is_empty());
        assert!(!self.y_range.is_empty());

        let it = self.points.iter()
            .map(move |p| Point {
                x: p.x.scale_between_ranges(
                    &self.x_range,
                    &Range { 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 },
                ),
            });
        DrawableCurve {
            scaled_data: it,
            color: None,
            thickness: None,
        }
    }
}

/// Drawable curve object, constructed for specific display
pub struct DrawableCurve<C, I>
{
    scaled_data: I,
    color: Option<C>,
    thickness: Option<usize>,
}

/// builder methods to modify curve decoration
impl<C, I> DrawableCurve<C, I>
    where
        C: PixelColor,
        I: Iterator<Item=Point>,
{
    /// set curve color
    pub fn set_color(mut self, color: C) -> DrawableCurve<C, I> {
        self.color = Some(color);
        self
    }

    /// set curve line thickness
    pub fn set_thickness(mut self, thickness: usize) -> DrawableCurve<C, I> {
        self.thickness = Some(thickness);
        self
    }
}

impl<C, I> Drawable<C> for DrawableCurve<C, I>
    where C: PixelColor + Default,
          I: Iterator<Item=Point>,
{
    /// most important function - draw the curve on the display
    fn draw<D: DrawTarget<C>>(self, display: &mut D) -> Result<(), D::Error> {
        let color = match self.color {
            None => C::default(),
            Some(c) => c,
        };
        let thickness = match self.thickness {
            None => 2,
            Some(t) => t,
        };
        let style = PrimitiveStyle::with_stroke(color, thickness as u32);
        self.scaled_data
            .tuple_windows()
            .try_for_each(|(prev, point)| -> Result<(), D::Error> {
                Line::new(prev, point)
                    .into_styled(style)
                    .draw(display)
            })
    }
}