Skip to main content

ggplot_rs/aes/
mapping.rs

1use crate::aes::{Aes, MappingStage};
2use crate::data::DataFrame;
3
4/// Evaluate aesthetic mappings: extract columns from source data
5/// and rename them to canonical aesthetic names (x, y, color, etc.).
6/// Only resolves BeforeStat mappings.
7pub fn resolve_mappings(data: &DataFrame, mapping: &Aes) -> DataFrame {
8    let mut result = DataFrame::new();
9    let nrows = data.nrows();
10
11    if nrows == 0 {
12        return result;
13    }
14
15    for m in &mapping.mappings {
16        if m.stage != MappingStage::BeforeStat {
17            continue;
18        }
19        let col_name = m.aesthetic.col_name();
20        if let Some(values) = data.column(&m.column) {
21            result.add_column(col_name.to_string(), values.to_vec());
22        } else if let Some(values) = super::expr::eval_expression(&m.column, data) {
23            // Computed aesthetic — `m.column` is an expression like "a / b" or
24            // "log(gdp)" rather than a bare column name.
25            result.add_column(col_name.to_string(), values);
26        }
27    }
28
29    // Also carry over any columns that already have canonical names and aren't mapped
30    for name in data.column_names() {
31        if !result.has_column(name) {
32            // Keep original columns available for stats that need them
33            if let Some(values) = data.column(name) {
34                if result.nrows() == 0 || values.len() == result.nrows() {
35                    result.add_column(name.to_string(), values.to_vec());
36                }
37            }
38        }
39    }
40
41    result
42}
43
44/// Apply after_stat mappings: rename stat-computed columns to canonical aesthetic names.
45/// Called after the stat step in the build pipeline.
46pub fn apply_after_stat(data: &mut DataFrame, mapping: &Aes) {
47    for m in &mapping.mappings {
48        if m.stage != MappingStage::AfterStat {
49            continue;
50        }
51        let target = m.aesthetic.col_name();
52        let source = &m.column;
53
54        // If the stat produced the source column, rename it to the target
55        // aesthetic; otherwise evaluate `source` as an expression over the
56        // stat output (e.g. after_stat_y("count / sum(count)") for proportions).
57        let values = if let Some(values) = data.column(source) {
58            Some(values.to_vec())
59        } else {
60            super::expr::eval_expression(source, data)
61        };
62        if let Some(values) = values {
63            // Remove existing target column if any, then add the new mapping
64            if data.has_column(target) {
65                if let Some(col) = data.column_mut(target) {
66                    *col = values;
67                }
68            } else {
69                data.add_column(target.to_string(), values);
70            }
71        }
72    }
73}