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 #![allow(clippy::unwrap_used)]
207 use super::*;
208
209 #[test]
210 fn stack_basic() {
211 let keys = vec!["Jan".to_string(), "Feb".to_string(), "Mar".to_string()];
212 let series = vec!["A".to_string(), "B".to_string()];
213 let values = vec![
214 vec![10.0, 20.0, 30.0], vec![5.0, 10.0, 15.0], ];
217
218 let layout = StackLayout::new();
219 let points = layout.layout(&keys, &series, &values);
220
221 assert_eq!(points.len(), 6);
222
223 assert_eq!(points[0].key, "Jan");
225 assert_eq!(points[0].series, "A");
226 assert_eq!(points[0].y0, 0.0);
227 assert_eq!(points[0].y1, 10.0);
228 assert_eq!(points[0].value, 10.0);
229
230 assert_eq!(points[1].key, "Jan");
231 assert_eq!(points[1].series, "B");
232 assert_eq!(points[1].y0, 10.0);
233 assert_eq!(points[1].y1, 15.0);
234 assert_eq!(points[1].value, 5.0);
235
236 assert_eq!(points[2].y0, 0.0);
238 assert_eq!(points[2].y1, 20.0);
239 assert_eq!(points[3].y0, 20.0);
240 assert_eq!(points[3].y1, 30.0);
241
242 assert_eq!(points[4].y0, 0.0);
244 assert_eq!(points[4].y1, 30.0);
245 assert_eq!(points[5].y0, 30.0);
246 assert_eq!(points[5].y1, 45.0);
247 }
248
249 #[test]
250 fn stack_y0_y1_chain() {
251 let keys = vec!["X".to_string()];
252 let series = vec!["A".to_string(), "B".to_string(), "C".to_string()];
253 let values = vec![vec![10.0], vec![20.0], vec![30.0]];
254
255 let layout = StackLayout::new();
256 let points = layout.layout(&keys, &series, &values);
257
258 assert_eq!(points.len(), 3);
259 assert_eq!(points[0].y0, 0.0);
261 assert_eq!(points[0].y1, 10.0);
262 assert_eq!(points[1].y0, points[0].y1);
263 assert_eq!(points[1].y1, 30.0);
264 assert_eq!(points[2].y0, points[1].y1);
265 assert_eq!(points[2].y1, 60.0);
266 }
267
268 #[test]
269 fn stack_normalize() {
270 let keys = vec!["Jan".to_string(), "Feb".to_string()];
271 let series = vec!["A".to_string(), "B".to_string()];
272 let values = vec![
273 vec![30.0, 40.0], vec![70.0, 60.0], ];
276
277 let layout = StackLayout::new().offset(StackOffset::Normalize);
278 let points = layout.layout(&keys, &series, &values);
279
280 assert_eq!(points.len(), 4);
281
282 assert!((points[0].y0 - 0.0).abs() < 1e-10);
284 assert!((points[0].y1 - 0.3).abs() < 1e-10);
285 assert!((points[1].y0 - 0.3).abs() < 1e-10);
286 assert!((points[1].y1 - 1.0).abs() < 1e-10);
287
288 assert!((points[2].y0 - 0.0).abs() < 1e-10);
290 assert!((points[2].y1 - 0.4).abs() < 1e-10);
291 assert!((points[3].y0 - 0.4).abs() < 1e-10);
292 assert!((points[3].y1 - 1.0).abs() < 1e-10);
293 }
294
295 #[test]
296 fn stack_empty() {
297 let layout = StackLayout::new();
298
299 let points = layout.layout(&[], &[], &[]);
300 assert!(points.is_empty());
301
302 let points = layout.layout(&["A".to_string()], &[], &[]);
303 assert!(points.is_empty());
304
305 let points = layout.layout(&[], &["S".to_string()], &[vec![1.0]]);
306 assert!(points.is_empty());
307 }
308
309 #[test]
310 fn stack_single_series() {
311 let keys = vec!["A".to_string(), "B".to_string(), "C".to_string()];
312 let series = vec!["Only".to_string()];
313 let values = vec![vec![10.0, 20.0, 30.0]];
314
315 let layout = StackLayout::new();
316 let points = layout.layout(&keys, &series, &values);
317
318 assert_eq!(points.len(), 3);
319 for point in &points {
320 assert_eq!(point.y0, 0.0);
321 assert_eq!(point.series, "Only");
322 }
323 assert_eq!(points[0].y1, 10.0);
324 assert_eq!(points[1].y1, 20.0);
325 assert_eq!(points[2].y1, 30.0);
326 }
327}