1#[derive(Debug, Clone)]
3pub struct StackedPoint {
4 pub key: String,
6 pub series: String,
8 pub y0: f64,
10 pub y1: f64,
12 pub value: f64,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum StackOrder {
19 None,
21 Ascending,
23 Descending,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum StackOffset {
30 None,
32 Normalize,
34}
35
36pub struct StackLayout {
39 order: StackOrder,
40 offset: StackOffset,
41}
42
43impl StackLayout {
44 pub fn new() -> Self {
46 Self {
47 order: StackOrder::None,
48 offset: StackOffset::None,
49 }
50 }
51
52 pub fn order(mut self, order: StackOrder) -> Self {
54 self.order = order;
55 self
56 }
57
58 pub fn offset(mut self, offset: StackOffset) -> Self {
60 self.offset = offset;
61 self
62 }
63
64 pub fn layout(
75 &self,
76 keys: &[String],
77 series_names: &[String],
78 values: &[Vec<f64>],
79 ) -> Vec<StackedPoint> {
80 if keys.is_empty() || series_names.is_empty() || values.is_empty() {
81 return Vec::new();
82 }
83
84 let num_series = series_names.len();
85 let num_keys = keys.len();
86
87 let ordered_indices = self.compute_order(series_names, values, num_keys);
89
90 let totals: Vec<f64> = (0..num_keys)
92 .map(|k| {
93 (0..num_series)
94 .map(|s| {
95 values
96 .get(s)
97 .and_then(|v| v.get(k))
98 .copied()
99 .unwrap_or(0.0)
100 })
101 .sum()
102 })
103 .collect();
104
105 let mut results = Vec::with_capacity(num_series * num_keys);
106
107 for k in 0..num_keys {
108 let mut y_base = 0.0;
109
110 for &s in &ordered_indices {
111 let raw_value = values
112 .get(s)
113 .and_then(|v| v.get(k))
114 .copied()
115 .unwrap_or(0.0);
116
117 let value = match self.offset {
118 StackOffset::None => raw_value,
119 StackOffset::Normalize => {
120 let total = totals[k];
121 if total == 0.0 {
122 0.0
123 } else {
124 raw_value / total
125 }
126 }
127 };
128
129 let y0 = y_base;
130 let y1 = y_base + value;
131 y_base = y1;
132
133 results.push(StackedPoint {
134 key: keys[k].clone(),
135 series: series_names[s].clone(),
136 y0,
137 y1,
138 value,
139 });
140 }
141 }
142
143 results
144 }
145
146 fn compute_order(
148 &self,
149 series_names: &[String],
150 values: &[Vec<f64>],
151 num_keys: usize,
152 ) -> Vec<usize> {
153 let num_series = series_names.len();
154 let mut indices: Vec<usize> = (0..num_series).collect();
155
156 match self.order {
157 StackOrder::None => {
158 }
160 StackOrder::Ascending => {
161 let sums: Vec<f64> = (0..num_series)
162 .map(|s| {
163 (0..num_keys)
164 .map(|k| {
165 values
166 .get(s)
167 .and_then(|v| v.get(k))
168 .copied()
169 .unwrap_or(0.0)
170 })
171 .sum()
172 })
173 .collect();
174 indices.sort_by(|&a, &b| sums[a].partial_cmp(&sums[b]).unwrap_or(std::cmp::Ordering::Equal));
175 }
176 StackOrder::Descending => {
177 let sums: Vec<f64> = (0..num_series)
178 .map(|s| {
179 (0..num_keys)
180 .map(|k| {
181 values
182 .get(s)
183 .and_then(|v| v.get(k))
184 .copied()
185 .unwrap_or(0.0)
186 })
187 .sum()
188 })
189 .collect();
190 indices.sort_by(|&a, &b| sums[b].partial_cmp(&sums[a]).unwrap_or(std::cmp::Ordering::Equal));
191 }
192 }
193
194 indices
195 }
196}
197
198impl Default for StackLayout {
199 fn default() -> Self {
200 Self::new()
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn stack_basic() {
210 let keys = vec!["Jan".to_string(), "Feb".to_string(), "Mar".to_string()];
211 let series = vec!["A".to_string(), "B".to_string()];
212 let values = vec![
213 vec![10.0, 20.0, 30.0], vec![5.0, 10.0, 15.0], ];
216
217 let layout = StackLayout::new();
218 let points = layout.layout(&keys, &series, &values);
219
220 assert_eq!(points.len(), 6);
221
222 assert_eq!(points[0].key, "Jan");
224 assert_eq!(points[0].series, "A");
225 assert_eq!(points[0].y0, 0.0);
226 assert_eq!(points[0].y1, 10.0);
227 assert_eq!(points[0].value, 10.0);
228
229 assert_eq!(points[1].key, "Jan");
230 assert_eq!(points[1].series, "B");
231 assert_eq!(points[1].y0, 10.0);
232 assert_eq!(points[1].y1, 15.0);
233 assert_eq!(points[1].value, 5.0);
234
235 assert_eq!(points[2].y0, 0.0);
237 assert_eq!(points[2].y1, 20.0);
238 assert_eq!(points[3].y0, 20.0);
239 assert_eq!(points[3].y1, 30.0);
240
241 assert_eq!(points[4].y0, 0.0);
243 assert_eq!(points[4].y1, 30.0);
244 assert_eq!(points[5].y0, 30.0);
245 assert_eq!(points[5].y1, 45.0);
246 }
247
248 #[test]
249 fn stack_y0_y1_chain() {
250 let keys = vec!["X".to_string()];
251 let series = vec!["A".to_string(), "B".to_string(), "C".to_string()];
252 let values = vec![vec![10.0], vec![20.0], vec![30.0]];
253
254 let layout = StackLayout::new();
255 let points = layout.layout(&keys, &series, &values);
256
257 assert_eq!(points.len(), 3);
258 assert_eq!(points[0].y0, 0.0);
260 assert_eq!(points[0].y1, 10.0);
261 assert_eq!(points[1].y0, points[0].y1);
262 assert_eq!(points[1].y1, 30.0);
263 assert_eq!(points[2].y0, points[1].y1);
264 assert_eq!(points[2].y1, 60.0);
265 }
266
267 #[test]
268 fn stack_normalize() {
269 let keys = vec!["Jan".to_string(), "Feb".to_string()];
270 let series = vec!["A".to_string(), "B".to_string()];
271 let values = vec![
272 vec![30.0, 40.0], vec![70.0, 60.0], ];
275
276 let layout = StackLayout::new().offset(StackOffset::Normalize);
277 let points = layout.layout(&keys, &series, &values);
278
279 assert_eq!(points.len(), 4);
280
281 assert!((points[0].y0 - 0.0).abs() < 1e-10);
283 assert!((points[0].y1 - 0.3).abs() < 1e-10);
284 assert!((points[1].y0 - 0.3).abs() < 1e-10);
285 assert!((points[1].y1 - 1.0).abs() < 1e-10);
286
287 assert!((points[2].y0 - 0.0).abs() < 1e-10);
289 assert!((points[2].y1 - 0.4).abs() < 1e-10);
290 assert!((points[3].y0 - 0.4).abs() < 1e-10);
291 assert!((points[3].y1 - 1.0).abs() < 1e-10);
292 }
293
294 #[test]
295 fn stack_empty() {
296 let layout = StackLayout::new();
297
298 let points = layout.layout(&[], &[], &[]);
299 assert!(points.is_empty());
300
301 let points = layout.layout(&["A".to_string()], &[], &[]);
302 assert!(points.is_empty());
303
304 let points = layout.layout(&[], &["S".to_string()], &[vec![1.0]]);
305 assert!(points.is_empty());
306 }
307
308 #[test]
309 fn stack_single_series() {
310 let keys = vec!["A".to_string(), "B".to_string(), "C".to_string()];
311 let series = vec!["Only".to_string()];
312 let values = vec![vec![10.0, 20.0, 30.0]];
313
314 let layout = StackLayout::new();
315 let points = layout.layout(&keys, &series, &values);
316
317 assert_eq!(points.len(), 3);
318 for point in &points {
319 assert_eq!(point.y0, 0.0);
320 assert_eq!(point.series, "Only");
321 }
322 assert_eq!(points[0].y1, 10.0);
323 assert_eq!(points[1].y1, 20.0);
324 assert_eq!(points[2].y1, 30.0);
325 }
326}