Skip to main content

egui_charts/drawings/domain/
coordinates.rs

1//! Chart coordinate types
2//!
3//! ChartPoint exposes editable chart coordinates (bar_idx, price) which remain
4//! stable across pan/zoom operations, unlike screen coordinates which change.
5
6/// A persistent point stored as (bar_idx, price) for pan/zoom stability
7///
8/// A chart retains the timestamp behind each projected index across data changes. Construct
9/// points with `ChartPoint::new`; editing `bar_idx` reanchors at that index. Screen coordinates
10/// (`Pos2`) are derived from ChartPoint each frame using the current transforms.
11///
12/// # Example
13///
14/// ```
15/// use egui_charts::drawings::ChartPoint;
16///
17/// let point = ChartPoint::new(100.0, 150.50);
18/// assert_eq!(point.bar_idx, 100.0);
19/// assert_eq!(point.price, 150.50);
20/// ```
21#[derive(Clone, Copy)]
22pub struct ChartPoint {
23    /// Bar index (can be fractional for points between bars)
24    pub bar_idx: f32,
25    /// Price level
26    pub price: f64,
27    /// Time and last projected index; editing the index invalidates the cached time.
28    pub(crate) time_anchor: Option<(chrono::DateTime<chrono::Utc>, f32)>,
29}
30
31impl ChartPoint {
32    /// Create a new chart point
33    #[inline]
34    pub fn new(bar_idx: f32, price: f64) -> Self {
35        Self {
36            bar_idx,
37            price,
38            time_anchor: None,
39        }
40    }
41
42    /// Create a chart point at origin (0, 0)
43    #[inline]
44    pub fn zero() -> Self {
45        Self {
46            bar_idx: 0.0,
47            price: 0.0,
48            time_anchor: None,
49        }
50    }
51
52    /// Shift the bar index by a delta (used when historical data is prepended)
53    #[inline]
54    pub fn shift_bar_idx(&mut self, delta: f32) {
55        self.bar_idx += delta;
56    }
57
58    /// Create a new ChartPoint with shifted bar index
59    #[inline]
60    pub fn with_shifted_bar_idx(self, delta: f32) -> Self {
61        Self {
62            bar_idx: self.bar_idx + delta,
63            price: self.price,
64            time_anchor: None,
65        }
66    }
67
68    /// Interpolate between two points
69    pub fn lerp(a: ChartPoint, b: ChartPoint, t: f32) -> ChartPoint {
70        ChartPoint {
71            bar_idx: a.bar_idx + (b.bar_idx - a.bar_idx) * t,
72            price: a.price + (b.price - a.price) * t as f64,
73            time_anchor: None,
74        }
75    }
76}
77
78impl std::fmt::Debug for ChartPoint {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        f.debug_struct("ChartPoint")
81            .field("bar_idx", &self.bar_idx)
82            .field("price", &self.price)
83            .finish()
84    }
85}
86
87impl PartialEq for ChartPoint {
88    fn eq(&self, other: &Self) -> bool {
89        self.bar_idx == other.bar_idx && self.price == other.price
90    }
91}
92
93impl Default for ChartPoint {
94    fn default() -> Self {
95        Self::zero()
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_lerp() {
105        let a = ChartPoint::new(0.0, 100.0);
106        let b = ChartPoint::new(10.0, 200.0);
107        let mid = ChartPoint::lerp(a, b, 0.5);
108        assert_eq!(mid.bar_idx, 5.0);
109        assert_eq!(mid.price, 150.0);
110    }
111}