Skip to main content

gpui_base/plot/shape/
stack.rs

1// @reference: https://d3js.org/d3-shape/stack
2
3/// Represents a stacked series data point with lower and upper values
4#[derive(Clone, Debug)]
5#[non_exhaustive]
6pub struct StackPoint<T> {
7    /// The lower value (baseline)
8    pub y0: f32,
9    /// The upper value (topline)
10    pub y1: f32,
11    /// Reference to the original data
12    pub data: T,
13}
14
15/// Represents a stacked series
16#[derive(Clone, Debug)]
17#[non_exhaustive]
18pub struct StackSeries<T> {
19    /// The key for this series
20    pub key: String,
21    /// The index of this series
22    pub index: usize,
23    /// The points in this series
24    pub points: Vec<StackPoint<T>>,
25}
26
27#[allow(clippy::type_complexity)]
28pub struct Stack<T> {
29    data: Vec<T>,
30    keys: Vec<String>,
31    value: Box<dyn Fn(&T, &str) -> Option<f32>>,
32}
33
34impl<T: Clone> Default for Stack<T> {
35    fn default() -> Self {
36        Self {
37            data: Vec::new(),
38            keys: Vec::new(),
39            value: Box::new(|_, _| None),
40        }
41    }
42}
43
44impl<T: Clone> Stack<T> {
45    pub fn new() -> Self {
46        Self::default()
47    }
48
49    /// Set the data to be stacked
50    pub fn data<I>(mut self, data: I) -> Self
51    where
52        I: IntoIterator<Item = T>,
53    {
54        self.data = data.into_iter().collect();
55        self
56    }
57
58    /// Set the keys (series) for stacking
59    pub fn keys<I, S>(mut self, keys: I) -> Self
60    where
61        I: IntoIterator<Item = S>,
62        S: Into<String>,
63    {
64        self.keys = keys.into_iter().map(|s| s.into()).collect();
65        self
66    }
67
68    /// Set the value accessor function
69    pub fn value<F>(mut self, value: F) -> Self
70    where
71        F: Fn(&T, &str) -> Option<f32> + 'static,
72    {
73        self.value = Box::new(value);
74        self
75    }
76
77    /// Compute the stacked series
78    pub fn series(&self) -> Vec<StackSeries<T>> {
79        if self.data.is_empty() || self.keys.is_empty() {
80            return Vec::new();
81        }
82
83        let n = self.data.len(); // number of data points
84        let m = self.keys.len(); // number of series
85
86        // Extract values into a 2D matrix: series x data points
87        let mut matrix: Vec<Vec<f32>> = Vec::with_capacity(m);
88        for key in &self.keys {
89            let mut series_values = Vec::with_capacity(n);
90            for datum in &self.data {
91                let value = (self.value)(datum, key).unwrap_or(0.0);
92                series_values.push(value);
93            }
94            matrix.push(series_values);
95        }
96
97        // Use the natural key order for stacking
98        let order: Vec<usize> = (0..m).collect();
99
100        // Initialize stacks with zeros
101        let mut stacks: Vec<Vec<(f32, f32)>> = vec![vec![(0.0, 0.0); n]; m];
102
103        // Compute the stacks based on order
104        for j in 0..n {
105            let mut y0 = 0.0;
106            for &i in &order {
107                let y1 = y0 + matrix[i][j];
108                stacks[i][j] = (y0, y1);
109                y0 = y1;
110            }
111        }
112
113        // Build the result series
114        let mut result = Vec::with_capacity(m);
115        for (i, key) in self.keys.iter().enumerate() {
116            let points = self
117                .data
118                .iter()
119                .enumerate()
120                .map(|(j, datum)| StackPoint {
121                    y0: stacks[i][j].0,
122                    y1: stacks[i][j].1,
123                    data: datum.clone(),
124                })
125                .collect();
126
127            result.push(StackSeries {
128                key: key.clone(),
129                index: i,
130                points,
131            });
132        }
133
134        result
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[derive(Clone, Debug)]
143    struct SalesData {
144        #[allow(dead_code)]
145        date: String,
146        apples: f32,
147        bananas: f32,
148        cherries: f32,
149    }
150
151    #[test]
152    fn test_basic_stack() {
153        let data = vec![
154            SalesData {
155                date: "Jan".to_string(),
156                apples: 10.0,
157                bananas: 20.0,
158                cherries: 30.0,
159            },
160            SalesData {
161                date: "Feb".to_string(),
162                apples: 15.0,
163                bananas: 25.0,
164                cherries: 35.0,
165            },
166        ];
167
168        let stack = Stack::new()
169            .data(data)
170            .keys(vec!["apples", "bananas", "cherries"])
171            .value(|d, key| match key {
172                "apples" => Some(d.apples),
173                "bananas" => Some(d.bananas),
174                "cherries" => Some(d.cherries),
175                _ => None,
176            });
177
178        let series = stack.series();
179
180        assert_eq!(series.len(), 3);
181        assert_eq!(series[0].key, "apples");
182        assert_eq!(series[0].points[0].y0, 0.0);
183        assert_eq!(series[0].points[0].y1, 10.0);
184
185        assert_eq!(series[1].key, "bananas");
186        assert_eq!(series[1].points[0].y0, 10.0);
187        assert_eq!(series[1].points[0].y1, 30.0);
188
189        assert_eq!(series[2].key, "cherries");
190        assert_eq!(series[2].points[0].y0, 30.0);
191        assert_eq!(series[2].points[0].y1, 60.0);
192    }
193}