embedded_charts/data/
series.rs

1//! Data series implementations for chart data management.
2
3use crate::data::bounds::DataBounds;
4use crate::data::point::DataPoint;
5use crate::error::{DataError, DataResult};
6use heapless::Vec;
7
8/// Memory-efficient iterator for StaticDataSeries that uses index-based access
9pub struct StaticDataSeriesIter<T, const N: usize> {
10    data: heapless::Vec<T, N>,
11    index: usize,
12}
13
14impl<T: Clone, const N: usize> Iterator for StaticDataSeriesIter<T, N> {
15    type Item = T;
16
17    fn next(&mut self) -> Option<Self::Item> {
18        if self.index < self.data.len() {
19            let item = self.data.get(self.index)?.clone();
20            self.index += 1;
21            Some(item)
22        } else {
23            None
24        }
25    }
26
27    fn size_hint(&self) -> (usize, Option<usize>) {
28        let remaining = self.data.len() - self.index;
29        (remaining, Some(remaining))
30    }
31}
32
33impl<T: Clone, const N: usize> ExactSizeIterator for StaticDataSeriesIter<T, N> {}
34
35/// Reference iterator for StaticDataSeries that yields references to avoid cloning
36pub struct StaticDataSeriesRefIter<'a, T> {
37    data: &'a [T],
38    index: usize,
39}
40
41impl<'a, T> Iterator for StaticDataSeriesRefIter<'a, T> {
42    type Item = &'a T;
43
44    fn next(&mut self) -> Option<Self::Item> {
45        if self.index < self.data.len() {
46            let item = &self.data[self.index];
47            self.index += 1;
48            Some(item)
49        } else {
50            None
51        }
52    }
53
54    fn size_hint(&self) -> (usize, Option<usize>) {
55        let remaining = self.data.len() - self.index;
56        (remaining, Some(remaining))
57    }
58}
59
60impl<'a, T> ExactSizeIterator for StaticDataSeriesRefIter<'a, T> {}
61
62/// Trait for data series that can be used in charts
63pub trait DataSeries {
64    /// The type of data points in this series
65    type Item: DataPoint;
66    /// Iterator type for iterating over data points (cloning)
67    type Iter: Iterator<Item = Self::Item>;
68
69    /// Get an iterator over the data points (clones items)
70    fn iter(&self) -> Self::Iter;
71
72    /// Get the number of data points in the series
73    fn len(&self) -> usize;
74
75    /// Check if the series is empty
76    fn is_empty(&self) -> bool {
77        self.len() == 0
78    }
79
80    /// Calculate the bounds of this data series
81    fn calculate_bounds(&self) -> DataResult<()> {
82        // This is a placeholder - bounds calculation will be implemented
83        // in concrete implementations where the types are known
84        Ok(())
85    }
86
87    /// Get a specific data point by index
88    fn get(&self, index: usize) -> Option<Self::Item>;
89}
90
91/// A static data series with compile-time capacity bounds
92#[derive(Debug, Clone)]
93pub struct StaticDataSeries<T, const N: usize>
94where
95    T: DataPoint,
96{
97    data: Vec<T, N>,
98    label: Option<heapless::String<32>>,
99}
100
101impl<T, const N: usize> StaticDataSeries<T, N>
102where
103    T: DataPoint,
104{
105    /// Create a new empty static data series
106    pub fn new() -> Self {
107        Self {
108            data: Vec::new(),
109            label: None,
110        }
111    }
112
113    /// Create a new static data series with a label
114    pub fn with_label(label: &str) -> Self {
115        let mut series = Self::new();
116        series.set_label(label);
117        series
118    }
119
120    /// Set the label for this series
121    pub fn set_label(&mut self, label: &str) {
122        let mut string = heapless::String::new();
123        if string.push_str(label).is_ok() {
124            self.label = Some(string);
125        }
126    }
127
128    /// Get the label for this series
129    pub fn label(&self) -> Option<&str> {
130        self.label.as_ref().map(|s| s.as_str())
131    }
132
133    /// Add a data point to the series
134    pub fn push(&mut self, point: T) -> DataResult<()> {
135        self.data
136            .push(point)
137            .map_err(|_| DataError::buffer_full("push data point", N))
138    }
139
140    /// Add multiple data points from an iterator
141    pub fn extend<I>(&mut self, points: I) -> DataResult<()>
142    where
143        I: IntoIterator<Item = T>,
144    {
145        for point in points {
146            self.push(point)?;
147        }
148        Ok(())
149    }
150
151    /// Add data points from a slice of tuples
152    pub fn from_tuples(tuples: &[(T::X, T::Y)]) -> DataResult<Self>
153    where
154        T: DataPoint,
155    {
156        let mut series = Self::new();
157        for &(x, y) in tuples {
158            series.push(T::new(x, y))?;
159        }
160        Ok(series)
161    }
162
163    /// Clear all data points
164    pub fn clear(&mut self) {
165        self.data.clear();
166    }
167
168    /// Get the capacity of this series
169    pub fn capacity(&self) -> usize {
170        N
171    }
172
173    /// Get the remaining capacity
174    pub fn remaining_capacity(&self) -> usize {
175        N - self.data.len()
176    }
177
178    /// Check if the series is at capacity
179    pub fn is_full(&self) -> bool {
180        self.data.len() == N
181    }
182
183    /// Get a slice of all data points
184    pub fn as_slice(&self) -> &[T] {
185        &self.data
186    }
187
188    /// Sort the data points by X coordinate
189    pub fn sort_by_x(&mut self)
190    where
191        T::X: Ord,
192        T: Clone,
193    {
194        // Use efficient insertion sort for small arrays, or merge sort for larger ones
195        if self.data.len() <= 16 {
196            self.insertion_sort_by_x();
197        } else {
198            self.merge_sort_by_x();
199        }
200    }
201
202    /// Insertion sort by X coordinate (efficient for small arrays)
203    fn insertion_sort_by_x(&mut self)
204    where
205        T::X: Ord,
206        T: Clone,
207    {
208        for i in 1..self.data.len() {
209            let key = self.data[i];
210            let mut j = i;
211
212            while j > 0 && self.data[j - 1].x() > key.x() {
213                self.data[j] = self.data[j - 1];
214                j -= 1;
215            }
216            self.data[j] = key;
217        }
218    }
219
220    /// Merge sort by X coordinate (efficient for larger arrays)
221    fn merge_sort_by_x(&mut self)
222    where
223        T::X: Ord,
224        T: Clone,
225    {
226        let len = self.data.len();
227        if len <= 1 {
228            return;
229        }
230
231        // Use a temporary buffer for merging
232        let mut temp = heapless::Vec::<T, N>::new();
233        for _ in 0..len {
234            if temp.push(self.data[0]).is_err() {
235                // If we can't allocate temp buffer, fall back to insertion sort
236                self.insertion_sort_by_x();
237                return;
238            }
239        }
240
241        self.merge_sort_recursive(0, len, &mut temp);
242    }
243
244    /// Recursive merge sort helper
245    fn merge_sort_recursive(&mut self, start: usize, end: usize, temp: &mut heapless::Vec<T, N>)
246    where
247        T::X: Ord,
248        T: Clone,
249    {
250        if end - start <= 1 {
251            return;
252        }
253
254        let mid = start + (end - start) / 2;
255        self.merge_sort_recursive(start, mid, temp);
256        self.merge_sort_recursive(mid, end, temp);
257
258        // Merge the two sorted halves
259        let mut i = start;
260        let mut j = mid;
261        let mut k = start;
262
263        // Copy data to temp buffer for merging
264        for idx in start..end {
265            temp[idx] = self.data[idx];
266        }
267
268        while i < mid && j < end {
269            if temp[i].x() <= temp[j].x() {
270                self.data[k] = temp[i];
271                i += 1;
272            } else {
273                self.data[k] = temp[j];
274                j += 1;
275            }
276            k += 1;
277        }
278
279        // Copy remaining elements
280        while i < mid {
281            self.data[k] = temp[i];
282            i += 1;
283            k += 1;
284        }
285
286        while j < end {
287            self.data[k] = temp[j];
288            j += 1;
289            k += 1;
290        }
291    }
292
293    /// Sort the data points by Y coordinate
294    pub fn sort_by_y(&mut self)
295    where
296        T::Y: Ord,
297        T: Clone,
298    {
299        // Use efficient insertion sort for small arrays, or merge sort for larger ones
300        if self.data.len() <= 16 {
301            self.insertion_sort_by_y();
302        } else {
303            self.merge_sort_by_y();
304        }
305    }
306
307    /// Insertion sort by Y coordinate (efficient for small arrays)
308    fn insertion_sort_by_y(&mut self)
309    where
310        T::Y: Ord,
311        T: Clone,
312    {
313        for i in 1..self.data.len() {
314            let key = self.data[i];
315            let mut j = i;
316
317            while j > 0 && self.data[j - 1].y() > key.y() {
318                self.data[j] = self.data[j - 1];
319                j -= 1;
320            }
321            self.data[j] = key;
322        }
323    }
324
325    /// Merge sort by Y coordinate (efficient for larger arrays)
326    fn merge_sort_by_y(&mut self)
327    where
328        T::Y: Ord,
329        T: Clone,
330    {
331        let len = self.data.len();
332        if len <= 1 {
333            return;
334        }
335
336        // Use a temporary buffer for merging
337        let mut temp = heapless::Vec::<T, N>::new();
338        for _ in 0..len {
339            if temp.push(self.data[0]).is_err() {
340                // If we can't allocate temp buffer, fall back to insertion sort
341                self.insertion_sort_by_y();
342                return;
343            }
344        }
345
346        self.merge_sort_by_y_recursive(0, len, &mut temp);
347    }
348
349    /// Recursive merge sort helper for Y coordinate
350    fn merge_sort_by_y_recursive(
351        &mut self,
352        start: usize,
353        end: usize,
354        temp: &mut heapless::Vec<T, N>,
355    ) where
356        T::Y: Ord,
357        T: Clone,
358    {
359        if end - start <= 1 {
360            return;
361        }
362
363        let mid = start + (end - start) / 2;
364        self.merge_sort_by_y_recursive(start, mid, temp);
365        self.merge_sort_by_y_recursive(mid, end, temp);
366
367        // Merge the two sorted halves
368        let mut i = start;
369        let mut j = mid;
370        let mut k = start;
371
372        // Copy data to temp buffer for merging
373        for idx in start..end {
374            temp[idx] = self.data[idx];
375        }
376
377        while i < mid && j < end {
378            if temp[i].y() <= temp[j].y() {
379                self.data[k] = temp[i];
380                i += 1;
381            } else {
382                self.data[k] = temp[j];
383                j += 1;
384            }
385            k += 1;
386        }
387
388        // Copy remaining elements
389        while i < mid {
390            self.data[k] = temp[i];
391            i += 1;
392            k += 1;
393        }
394
395        while j < end {
396            self.data[k] = temp[j];
397            j += 1;
398            k += 1;
399        }
400    }
401}
402
403impl<T, const N: usize> Default for StaticDataSeries<T, N>
404where
405    T: DataPoint,
406{
407    fn default() -> Self {
408        Self::new()
409    }
410}
411
412impl<T, const N: usize> StaticDataSeries<T, N>
413where
414    T: DataPoint + Clone,
415{
416    /// Get a zero-copy reference iterator (recommended for performance)
417    pub fn iter_ref(&self) -> StaticDataSeriesRefIter<'_, T> {
418        StaticDataSeriesRefIter {
419            data: self.data.as_slice(),
420            index: 0,
421        }
422    }
423
424    /// Get the underlying data as a slice (zero-copy access)
425    pub fn data(&self) -> &[T] {
426        self.data.as_slice()
427    }
428}
429
430impl<T, const N: usize> DataSeries for StaticDataSeries<T, N>
431where
432    T: DataPoint + Clone,
433{
434    type Item = T;
435    type Iter = StaticDataSeriesIter<T, N>;
436
437    fn iter(&self) -> Self::Iter {
438        // Note: This clones the data vector for backwards compatibility.
439        // For better performance, use iter_ref() or data() methods which provide
440        // zero-copy access to the underlying data.
441        StaticDataSeriesIter {
442            data: self.data.clone(),
443            index: 0,
444        }
445    }
446
447    fn len(&self) -> usize {
448        self.data.len()
449    }
450
451    fn get(&self, index: usize) -> Option<Self::Item> {
452        self.data.get(index).copied()
453    }
454}
455
456impl<T, const N: usize> StaticDataSeries<T, N>
457where
458    T: DataPoint + Clone,
459    T::X: PartialOrd + Copy,
460    T::Y: PartialOrd + Copy,
461{
462    /// Get the bounds of this data series
463    pub fn bounds(&self) -> DataResult<crate::data::bounds::DataBounds<T::X, T::Y>> {
464        use crate::data::bounds::calculate_bounds;
465        calculate_bounds(self.iter())
466    }
467}
468
469/// A multi-series container for holding multiple data series
470#[derive(Debug, Clone)]
471pub struct MultiSeries<T, const SERIES: usize, const POINTS: usize>
472where
473    T: DataPoint,
474{
475    series: Vec<StaticDataSeries<T, POINTS>, SERIES>,
476}
477
478impl<T, const SERIES: usize, const POINTS: usize> MultiSeries<T, SERIES, POINTS>
479where
480    T: DataPoint,
481{
482    /// Create a new empty multi-series container
483    pub fn new() -> Self {
484        Self { series: Vec::new() }
485    }
486
487    /// Add a new data series
488    pub fn add_series(&mut self, series: StaticDataSeries<T, POINTS>) -> DataResult<usize> {
489        let index = self.series.len();
490        self.series
491            .push(series)
492            .map_err(|_| DataError::buffer_full("add data series", SERIES))?;
493        Ok(index)
494    }
495
496    /// Get a reference to a series by index
497    pub fn get_series(&self, index: usize) -> Option<&StaticDataSeries<T, POINTS>> {
498        self.series.get(index)
499    }
500
501    /// Get a mutable reference to a series by index
502    pub fn get_series_mut(&mut self, index: usize) -> Option<&mut StaticDataSeries<T, POINTS>> {
503        self.series.get_mut(index)
504    }
505
506    /// Get the number of series
507    pub fn series_count(&self) -> usize {
508        self.series.len()
509    }
510
511    /// Check if there are no series
512    pub fn is_empty(&self) -> bool {
513        self.series.is_empty()
514    }
515
516    /// Get an iterator over all series
517    pub fn iter_series(&self) -> core::slice::Iter<StaticDataSeries<T, POINTS>> {
518        self.series.iter()
519    }
520
521    /// Calculate combined bounds for all series
522    pub fn combined_bounds(&self) -> DataResult<DataBounds<T::X, T::Y>>
523    where
524        T: DataPoint + Clone,
525        T::X: PartialOrd + Copy,
526        T::Y: PartialOrd + Copy,
527    {
528        if self.series.is_empty() {
529            return Err(DataError::insufficient_data(
530                "calculate combined bounds",
531                1,
532                0,
533            ));
534        }
535
536        let mut combined_bounds = self.series[0].bounds()?;
537
538        for series in self.series.iter().skip(1) {
539            let series_bounds = series.bounds()?;
540            combined_bounds = combined_bounds.merge(&series_bounds);
541        }
542
543        Ok(combined_bounds)
544    }
545
546    /// Clear all series
547    pub fn clear(&mut self) {
548        self.series.clear();
549    }
550}
551
552impl<T, const SERIES: usize, const POINTS: usize> Default for MultiSeries<T, SERIES, POINTS>
553where
554    T: DataPoint,
555{
556    fn default() -> Self {
557        Self::new()
558    }
559}
560
561/// A sliding window data series for real-time data
562#[cfg(feature = "animations")]
563#[derive(Debug, Clone)]
564pub struct SlidingWindowSeries<T, const N: usize>
565where
566    T: DataPoint + Copy,
567{
568    buffer: [Option<T>; N],
569    head: usize,
570    count: usize,
571    full: bool,
572    label: Option<heapless::String<32>>,
573}
574
575#[cfg(feature = "animations")]
576impl<T, const N: usize> SlidingWindowSeries<T, N>
577where
578    T: DataPoint + Copy,
579{
580    /// Create a new sliding window series
581    pub fn new() -> Self {
582        Self {
583            buffer: [None; N],
584            head: 0,
585            count: 0,
586            full: false,
587            label: None,
588        }
589    }
590
591    /// Create a new sliding window series with a label
592    pub fn with_label(label: &str) -> Self {
593        let mut series = Self::new();
594        series.set_label(label);
595        series
596    }
597
598    /// Set the label for this series
599    pub fn set_label(&mut self, label: &str) {
600        let mut string = heapless::String::new();
601        if string.push_str(label).is_ok() {
602            self.label = Some(string);
603        }
604    }
605
606    /// Get the label for this series
607    pub fn label(&self) -> Option<&str> {
608        self.label.as_ref().map(|s| s.as_str())
609    }
610
611    /// Push a new data point (may overwrite old data)
612    pub fn push(&mut self, point: T) {
613        self.buffer[self.head] = Some(point);
614        self.head = (self.head + 1) % N;
615
616        if self.full {
617            // Overwriting old data
618        } else {
619            self.count += 1;
620            if self.count == N {
621                self.full = true;
622            }
623        }
624    }
625
626    /// Get the current number of data points
627    pub fn current_len(&self) -> usize {
628        self.count
629    }
630
631    /// Check if the buffer is full
632    pub fn is_full(&self) -> bool {
633        self.full
634    }
635
636    /// Get the capacity of the sliding window
637    pub fn capacity(&self) -> usize {
638        N
639    }
640
641    /// Clear all data
642    pub fn clear(&mut self) {
643        self.buffer = [None; N];
644        self.head = 0;
645        self.count = 0;
646        self.full = false;
647    }
648
649    /// Get an iterator over the current data points in chronological order
650    pub fn iter_chronological(&self) -> impl Iterator<Item = T> + '_ {
651        let start_idx = if self.full { self.head } else { 0 };
652        let len = if self.full { N } else { self.count };
653
654        (0..len).filter_map(move |i| {
655            let idx = (start_idx + i) % N;
656            self.buffer[idx]
657        })
658    }
659}
660
661#[cfg(feature = "animations")]
662impl<T, const N: usize> Default for SlidingWindowSeries<T, N>
663where
664    T: DataPoint + Copy,
665{
666    fn default() -> Self {
667        Self::new()
668    }
669}
670
671#[cfg(feature = "animations")]
672impl<T, const N: usize> DataSeries for SlidingWindowSeries<T, N>
673where
674    T: DataPoint + Copy,
675{
676    type Item = T;
677    type Iter = <heapless::Vec<T, N> as IntoIterator>::IntoIter;
678
679    fn iter(&self) -> Self::Iter {
680        let mut vec = heapless::Vec::new();
681        for point in self.iter_chronological() {
682            let _ = vec.push(point);
683        }
684        vec.into_iter()
685    }
686
687    fn len(&self) -> usize {
688        self.current_len()
689    }
690
691    fn get(&self, index: usize) -> Option<Self::Item> {
692        if index >= self.current_len() {
693            return None;
694        }
695
696        let start_idx = if self.full { self.head } else { 0 };
697        let actual_idx = (start_idx + index) % N;
698        self.buffer[actual_idx]
699    }
700}
701
702#[cfg(test)]
703mod tests {
704    use super::*;
705    use crate::data::point::Point2D;
706
707    #[test]
708    fn test_static_series_creation() {
709        let series: StaticDataSeries<Point2D, 10> = StaticDataSeries::new();
710        assert_eq!(series.len(), 0);
711        assert!(series.is_empty());
712        assert_eq!(series.capacity(), 10);
713    }
714
715    #[test]
716    fn test_static_series_push() {
717        let mut series: StaticDataSeries<Point2D, 10> = StaticDataSeries::new();
718        let point = Point2D::new(1.0, 2.0);
719
720        series.push(point).unwrap();
721        assert_eq!(series.len(), 1);
722        assert_eq!(series.get(0), Some(point));
723    }
724
725    #[test]
726    fn test_static_series_from_tuples() {
727        let tuples = [(1.0, 2.0), (3.0, 4.0), (5.0, 6.0)];
728        let series: StaticDataSeries<Point2D, 10> = StaticDataSeries::from_tuples(&tuples).unwrap();
729
730        assert_eq!(series.len(), 3);
731        assert_eq!(series.get(0), Some(Point2D::new(1.0, 2.0)));
732        assert_eq!(series.get(1), Some(Point2D::new(3.0, 4.0)));
733        assert_eq!(series.get(2), Some(Point2D::new(5.0, 6.0)));
734    }
735
736    #[test]
737    fn test_multi_series() {
738        let mut multi: MultiSeries<Point2D, 5, 10> = MultiSeries::new();
739        let mut series1 = StaticDataSeries::with_label("Series 1");
740        series1.push(Point2D::new(1.0, 2.0)).unwrap();
741
742        let index = multi.add_series(series1).unwrap();
743        assert_eq!(index, 0);
744        assert_eq!(multi.series_count(), 1);
745
746        let retrieved_series = multi.get_series(0).unwrap();
747        assert_eq!(retrieved_series.label(), Some("Series 1"));
748        assert_eq!(retrieved_series.len(), 1);
749    }
750
751    #[cfg(feature = "animations")]
752    #[test]
753    fn test_sliding_window_series() {
754        let mut series: SlidingWindowSeries<Point2D, 3> = SlidingWindowSeries::new();
755
756        series.push(Point2D::new(1.0, 1.0));
757        series.push(Point2D::new(2.0, 2.0));
758        series.push(Point2D::new(3.0, 3.0));
759
760        assert_eq!(series.current_len(), 3);
761        assert!(series.is_full());
762
763        // Push one more to test overwriting
764        series.push(Point2D::new(4.0, 4.0));
765        assert_eq!(series.current_len(), 3);
766
767        // Check that the oldest point was overwritten
768        let points: Vec<Point2D, 3> = series.iter().collect();
769        assert_eq!(points.len(), 3);
770        assert_eq!(points[0], Point2D::new(2.0, 2.0));
771        assert_eq!(points[1], Point2D::new(3.0, 3.0));
772        assert_eq!(points[2], Point2D::new(4.0, 4.0));
773    }
774}