ggplot-rs 0.6.0

A Rust implementation of ggplot2's Grammar of Graphics
Documentation
use crate::aes::{Aes, MappingStage};
use crate::data::DataFrame;

/// Evaluate aesthetic mappings: extract columns from source data
/// and rename them to canonical aesthetic names (x, y, color, etc.).
/// Only resolves BeforeStat mappings.
pub fn resolve_mappings(data: &DataFrame, mapping: &Aes) -> DataFrame {
    let mut result = DataFrame::new();
    let nrows = data.nrows();

    if nrows == 0 {
        return result;
    }

    for m in &mapping.mappings {
        if m.stage != MappingStage::BeforeStat {
            continue;
        }
        let col_name = m.aesthetic.col_name();
        if let Some(values) = data.column(&m.column) {
            result.add_column(col_name.to_string(), values.to_vec());
        } else if let Some(values) = super::expr::eval_expression(&m.column, data) {
            // Computed aesthetic — `m.column` is an expression like "a / b" or
            // "log(gdp)" rather than a bare column name.
            result.add_column(col_name.to_string(), values);
        }
    }

    // Also carry over any columns that already have canonical names and aren't mapped
    for name in data.column_names() {
        if !result.has_column(name) {
            // Keep original columns available for stats that need them
            if let Some(values) = data.column(name) {
                if result.nrows() == 0 || values.len() == result.nrows() {
                    result.add_column(name.to_string(), values.to_vec());
                }
            }
        }
    }

    result
}

/// Apply after_stat mappings: rename stat-computed columns to canonical aesthetic names.
/// Called after the stat step in the build pipeline.
pub fn apply_after_stat(data: &mut DataFrame, mapping: &Aes) {
    for m in &mapping.mappings {
        if m.stage != MappingStage::AfterStat {
            continue;
        }
        let target = m.aesthetic.col_name();
        let source = &m.column;

        // If the stat produced the source column, rename it to the target
        // aesthetic; otherwise evaluate `source` as an expression over the
        // stat output (e.g. after_stat_y("count / sum(count)") for proportions).
        let values = if let Some(values) = data.column(source) {
            Some(values.to_vec())
        } else {
            super::expr::eval_expression(source, data)
        };
        if let Some(values) = values {
            // Remove existing target column if any, then add the new mapping
            if data.has_column(target) {
                if let Some(col) = data.column_mut(target) {
                    *col = values;
                }
            } else {
                data.add_column(target.to_string(), values);
            }
        }
    }
}