Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-grid/src/condfmt.rs

//! **Conditional formatting** — per-column rules that tint a numeric cell. Two
//! flagship styles: **data bars** (an in-cell bar whose length ∝ value) and a
//! **colour scale** heat (low→high lerped through the theme ramp). Both normalise
//! against the column's `numeric_range`, so they're scale-invariant.

use egui::Color32;

use facett_core::Theme;

/// A conditional-format rule attached to a column index.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CondFmt {
    /// In-cell horizontal bar, length ∝ normalised value, tinted from `accent`.
    DataBar,
    /// Cell background lerped low→high through the theme ramp (`text_dim`→`glow`).
    ColorScale,
}

/// Normalise `x` into 0..1 over `(lo, hi)`; degenerate range → 0.5.
pub fn normalize(x: f64, lo: f64, hi: f64) -> f32 {
    if hi <= lo {
        return 0.5;
    }
    (((x - lo) / (hi - lo)) as f32).clamp(0.0, 1.0)
}

// The colour lerp and the heat ramp are the canonical `facett_core::look`
// primitives; re-exported here so `facett_grid::condfmt::{lerp_color,heat_color}`
// keeps working while there is exactly one implementation (no copy-paste twin).
pub use facett_core::look::color::{heat_color, lerp_color};

/// The data-bar fill colour under `theme` (accent at a moderate alpha).
pub fn bar_color(theme: &Theme) -> Color32 {
    Color32::from_rgba_unmultiplied(theme.accent.r(), theme.accent.g(), theme.accent.b(), 90)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn normalize_maps_endpoints() {
        assert_eq!(normalize(0.0, 0.0, 10.0), 0.0);
        assert_eq!(normalize(10.0, 0.0, 10.0), 1.0);
        assert_eq!(normalize(5.0, 0.0, 10.0), 0.5);
        // degenerate range
        assert_eq!(normalize(7.0, 3.0, 3.0), 0.5);
        // clamps out-of-range
        assert_eq!(normalize(-5.0, 0.0, 10.0), 0.0);
        assert_eq!(normalize(99.0, 0.0, 10.0), 1.0);
    }

    #[test]
    fn lerp_color_endpoints_and_mid() {
        let a = Color32::from_rgb(0, 0, 0);
        let b = Color32::from_rgb(100, 200, 50);
        assert_eq!(lerp_color(a, b, 0.0), a);
        assert_eq!(lerp_color(a, b, 1.0), b);
        let m = lerp_color(a, b, 0.5);
        assert_eq!((m.r(), m.g(), m.b()), (50, 100, 25));
    }

    #[test]
    fn heat_color_warms_with_value() {
        let th = Theme::default();
        let cold = heat_color(&th, 0.0);
        let hot = heat_color(&th, 1.0);
        assert!(hot.a() > cold.a(), "hotter cells are more opaque");
    }
}