embedded_charts/data/
point.rs

1//! Data point types and traits for chart data.
2
3use crate::error::DataResult;
4
5/// Trait for data points that can be used in charts
6pub trait DataPoint: Copy + Clone + PartialEq {
7    /// The type of the X coordinate
8    type X: PartialOrd + Copy + Clone;
9    /// The type of the Y coordinate  
10    type Y: PartialOrd + Copy + Clone;
11
12    /// Get the X coordinate of this data point
13    fn x(&self) -> Self::X;
14
15    /// Get the Y coordinate of this data point
16    fn y(&self) -> Self::Y;
17
18    /// Create a new data point from X and Y coordinates
19    fn new(x: Self::X, y: Self::Y) -> Self;
20}
21
22/// A simple 2D data point with floating point coordinates
23#[derive(Debug, Clone, Copy, PartialEq)]
24pub struct Point2D {
25    /// X coordinate
26    pub x: f32,
27    /// Y coordinate
28    pub y: f32,
29}
30
31impl Point2D {
32    /// Create a new 2D point
33    pub const fn new(x: f32, y: f32) -> Self {
34        Self { x, y }
35    }
36
37    /// Get the distance from this point to another point
38    pub fn distance_to(&self, other: &Self) -> f32 {
39        let dx = self.x - other.x;
40        let dy = self.y - other.y;
41        #[cfg(feature = "floating-point")]
42        {
43            micromath::F32Ext::sqrt(dx * dx + dy * dy)
44        }
45        #[cfg(not(feature = "floating-point"))]
46        {
47            // Simple approximation for distance without sqrt
48            let abs_dx = if dx < 0.0 { -dx } else { dx };
49            let abs_dy = if dy < 0.0 { -dy } else { dy };
50            abs_dx + abs_dy
51        }
52    }
53}
54
55impl DataPoint for Point2D {
56    type X = f32;
57    type Y = f32;
58
59    fn x(&self) -> Self::X {
60        self.x
61    }
62
63    fn y(&self) -> Self::Y {
64        self.y
65    }
66
67    fn new(x: Self::X, y: Self::Y) -> Self {
68        Self::new(x, y)
69    }
70}
71
72impl From<(f32, f32)> for Point2D {
73    fn from((x, y): (f32, f32)) -> Self {
74        Self::new(x, y)
75    }
76}
77
78impl From<Point2D> for (f32, f32) {
79    fn from(point: Point2D) -> Self {
80        (point.x, point.y)
81    }
82}
83
84/// A data point with integer coordinates for memory-constrained environments
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub struct IntPoint {
87    /// X coordinate
88    pub x: i32,
89    /// Y coordinate
90    pub y: i32,
91}
92
93impl IntPoint {
94    /// Create a new integer point
95    pub const fn new(x: i32, y: i32) -> Self {
96        Self { x, y }
97    }
98
99    /// Convert to floating point representation
100    pub fn to_f32(self) -> Point2D {
101        Point2D::new(self.x as f32, self.y as f32)
102    }
103}
104
105impl DataPoint for IntPoint {
106    type X = i32;
107    type Y = i32;
108
109    fn x(&self) -> Self::X {
110        self.x
111    }
112
113    fn y(&self) -> Self::Y {
114        self.y
115    }
116
117    fn new(x: Self::X, y: Self::Y) -> Self {
118        Self::new(x, y)
119    }
120}
121
122impl From<(i32, i32)> for IntPoint {
123    fn from((x, y): (i32, i32)) -> Self {
124        Self::new(x, y)
125    }
126}
127
128impl From<IntPoint> for (i32, i32) {
129    fn from(point: IntPoint) -> Self {
130        (point.x, point.y)
131    }
132}
133
134/// A data point with a timestamp for time-series data
135#[derive(Debug, Clone, Copy, PartialEq)]
136pub struct TimestampedPoint {
137    /// Timestamp (typically seconds since epoch or relative time)
138    pub timestamp: f32,
139    /// Value at this timestamp
140    pub value: f32,
141}
142
143impl TimestampedPoint {
144    /// Create a new timestamped point
145    pub const fn new(timestamp: f32, value: f32) -> Self {
146        Self { timestamp, value }
147    }
148}
149
150impl DataPoint for TimestampedPoint {
151    type X = f32;
152    type Y = f32;
153
154    fn x(&self) -> Self::X {
155        self.timestamp
156    }
157
158    fn y(&self) -> Self::Y {
159        self.value
160    }
161
162    fn new(x: Self::X, y: Self::Y) -> Self {
163        Self::new(x, y)
164    }
165}
166
167impl From<(f32, f32)> for TimestampedPoint {
168    fn from((timestamp, value): (f32, f32)) -> Self {
169        Self::new(timestamp, value)
170    }
171}
172
173/// Trait for interpolating between data points (used in animations)
174#[cfg(feature = "animations")]
175pub trait Interpolatable: DataPoint {
176    /// Interpolate between this point and another point
177    ///
178    /// # Arguments
179    /// * `other` - The target point to interpolate towards
180    /// * `t` - Interpolation factor (0.0 = self, 1.0 = other)
181    fn interpolate(&self, other: &Self, t: f32) -> Self;
182}
183
184#[cfg(feature = "animations")]
185impl Interpolatable for TimestampedPoint {
186    fn interpolate(&self, other: &Self, t: f32) -> Self {
187        let timestamp = self.timestamp + (other.timestamp - self.timestamp) * t;
188        let value = self.value + (other.value - self.value) * t;
189        Self::new(timestamp, value)
190    }
191}
192
193/// Validate that a data point has valid coordinates
194pub fn validate_point<P: DataPoint>(_point: &P) -> DataResult<()>
195where
196    P::X: PartialOrd,
197    P::Y: PartialOrd,
198{
199    // For floating point types, check for NaN and infinity
200    #[cfg(feature = "floating-point")]
201    {
202        let (_x, _y) = (_point.x(), _point.y());
203        // This is a simplified check - in a real implementation you'd need
204        // to handle the specific numeric types properly
205        // For now, we'll just return Ok since we can't easily check for NaN
206        // without knowing the exact type
207    }
208
209    Ok(())
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215
216    #[test]
217    fn test_point2d_creation() {
218        let point = Point2D::new(1.0, 2.0);
219        assert_eq!(point.x(), 1.0);
220        assert_eq!(point.y(), 2.0);
221    }
222
223    #[test]
224    fn test_point2d_from_tuple() {
225        let point: Point2D = (3.0, 4.0).into();
226        assert_eq!(point.x(), 3.0);
227        assert_eq!(point.y(), 4.0);
228    }
229
230    #[test]
231    fn test_int_point_creation() {
232        let point = IntPoint::new(10, 20);
233        assert_eq!(point.x(), 10);
234        assert_eq!(point.y(), 20);
235    }
236
237    #[test]
238    fn test_timestamped_point() {
239        let point = TimestampedPoint::new(100.0, 25.5);
240        assert_eq!(point.x(), 100.0);
241        assert_eq!(point.y(), 25.5);
242    }
243
244    #[cfg(feature = "animations")]
245    #[test]
246    fn test_interpolation() {
247        use crate::animation::Interpolatable;
248
249        let p1 = Point2D::new(0.0, 0.0);
250        let p2 = Point2D::new(10.0, 20.0);
251
252        let mid = p1.interpolate(p2, 0.5).unwrap();
253        assert_eq!(mid.x(), 5.0);
254        assert_eq!(mid.y(), 10.0);
255    }
256}