Skip to main content

ggplot_rs/position/
stack.rs

1use crate::data::{DataFrame, Value};
2
3use super::{Position, PositionParams};
4
5/// Stack bars/areas on top of each other.
6pub struct PositionStack;
7
8impl Position for PositionStack {
9    fn compute(&self, data: &mut DataFrame, _params: &PositionParams) {
10        // Group by x, accumulate y values
11        let x_col = match data.column("x") {
12            Some(c) => c.to_vec(),
13            None => return,
14        };
15        let y_col = match data.column("y") {
16            Some(c) => c.to_vec(),
17            None => return,
18        };
19
20        // ggplot2 stacks the first group at the TOP (so the stack order top-to-
21        // bottom matches the legend), so accumulate downward from each x's total
22        // rather than upward from 0.
23        let mut totals: Vec<(String, f64)> = Vec::new();
24        for (x, y) in x_col.iter().zip(y_col.iter()) {
25            let x_key = x.to_group_key();
26            let y_val = y.as_f64().unwrap_or(0.0);
27            if let Some(entry) = totals.iter_mut().find(|(k, _)| k == &x_key) {
28                entry.1 += y_val;
29            } else {
30                totals.push((x_key, y_val));
31            }
32        }
33
34        let mut consumed: Vec<(String, f64)> = Vec::new();
35        let mut new_y = Vec::with_capacity(y_col.len());
36        let mut ymin_vals = Vec::with_capacity(y_col.len());
37
38        for (x, y) in x_col.iter().zip(y_col.iter()) {
39            let x_key = x.to_group_key();
40            let y_val = y.as_f64().unwrap_or(0.0);
41            let total = totals
42                .iter()
43                .find(|(k, _)| k == &x_key)
44                .map(|(_, v)| *v)
45                .unwrap_or(0.0);
46            let run = consumed
47                .iter()
48                .find(|(k, _)| k == &x_key)
49                .map(|(_, v)| *v)
50                .unwrap_or(0.0);
51
52            // This group occupies [total - run - y, total - run] (top-down).
53            new_y.push(Value::Float(total - run));
54            ymin_vals.push(Value::Float(total - run - y_val));
55
56            if let Some(entry) = consumed.iter_mut().find(|(k, _)| k == &x_key) {
57                entry.1 += y_val;
58            } else {
59                consumed.push((x_key, y_val));
60            }
61        }
62
63        if let Some(col) = data.column_mut("y") {
64            *col = new_y;
65        }
66        if !data.has_column("ymin") {
67            data.add_column("ymin".to_string(), ymin_vals);
68        } else if let Some(col) = data.column_mut("ymin") {
69            *col = ymin_vals;
70        }
71    }
72
73    fn name(&self) -> &str {
74        "stack"
75    }
76}