Skip to main content

ggplot_rs/scale/
gradient.rs

1use crate::aes::Aesthetic;
2use crate::data::Value;
3
4use super::color::RGBAColor;
5use super::util::{format_number, nice_step};
6use super::Scale;
7
8/// Diverging gradient color scale (low → mid → high with midpoint).
9#[derive(Clone, Debug)]
10pub struct ScaleColorGradient2 {
11    aesthetic: Aesthetic,
12    name: String,
13    low: RGBAColor,
14    mid: RGBAColor,
15    high: RGBAColor,
16    midpoint: f64,
17    min: f64,
18    max: f64,
19}
20
21impl ScaleColorGradient2 {
22    pub fn new(aesthetic: Aesthetic) -> Self {
23        ScaleColorGradient2 {
24            aesthetic,
25            name: String::new(),
26            low: RGBAColor::new(0, 0, 255),
27            mid: RGBAColor::new(255, 255, 255),
28            high: RGBAColor::new(255, 0, 0),
29            midpoint: 0.0,
30            min: f64::INFINITY,
31            max: f64::NEG_INFINITY,
32        }
33    }
34
35    pub fn with_colors(mut self, low: RGBAColor, mid: RGBAColor, high: RGBAColor) -> Self {
36        self.low = low;
37        self.mid = mid;
38        self.high = high;
39        self
40    }
41
42    pub fn with_midpoint(mut self, midpoint: f64) -> Self {
43        self.midpoint = midpoint;
44        self
45    }
46}
47
48impl Scale for ScaleColorGradient2 {
49    fn aesthetic(&self) -> Aesthetic {
50        self.aesthetic.clone()
51    }
52
53    fn train(&mut self, values: &[Value]) {
54        for v in values {
55            if let Some(f) = v.as_f64() {
56                if f.is_finite() {
57                    if f < self.min {
58                        self.min = f;
59                    }
60                    if f > self.max {
61                        self.max = f;
62                    }
63                }
64            }
65        }
66    }
67
68    fn map(&self, value: &Value) -> f64 {
69        let f = match value.as_f64() {
70            Some(f) => f,
71            None => return 0.0,
72        };
73        let range = self.max - self.min;
74        if range.abs() < f64::EPSILON {
75            0.5
76        } else {
77            (f - self.min) / range
78        }
79    }
80
81    fn breaks(&self) -> Vec<(f64, String)> {
82        if self.min > self.max || !self.min.is_finite() || !self.max.is_finite() {
83            return vec![];
84        }
85        let range = self.max - self.min;
86        if range.abs() < f64::EPSILON {
87            return vec![(0.5, format_number(self.min))];
88        }
89        let n_breaks = 5;
90        let raw_step = range / n_breaks as f64;
91        let step = nice_step(raw_step);
92        let start = (self.min / step).ceil() * step;
93        let mut breaks = Vec::new();
94        let mut v = start;
95        while v <= self.max + step * 0.001 {
96            let pos = self.map(&Value::Float(v));
97            breaks.push((pos, format_number(v)));
98            v += step;
99        }
100        breaks
101    }
102
103    fn name(&self) -> &str {
104        &self.name
105    }
106
107    fn set_name(&mut self, name: &str) {
108        self.name = name.to_string();
109    }
110
111    fn map_to_color(&self, value: &Value) -> Option<(u8, u8, u8)> {
112        let f = value.as_f64()?;
113
114        // Map relative to midpoint
115        let c = if f <= self.midpoint {
116            let range = self.midpoint - self.min;
117            let t = if range.abs() < f64::EPSILON {
118                0.0
119            } else {
120                (f - self.min) / range
121            };
122            self.low.lerp(&self.mid, t)
123        } else {
124            let range = self.max - self.midpoint;
125            let t = if range.abs() < f64::EPSILON {
126                1.0
127            } else {
128                (f - self.midpoint) / range
129            };
130            self.mid.lerp(&self.high, t)
131        };
132
133        Some((c.r, c.g, c.b))
134    }
135
136    fn domain(&self) -> Option<(f64, f64)> {
137        if self.min.is_finite() && self.max.is_finite() && self.min <= self.max {
138            Some((self.min, self.max))
139        } else {
140            None
141        }
142    }
143
144    fn clone_box(&self) -> Box<dyn Scale> {
145        Box::new(self.clone())
146    }
147
148    fn reset_training(&mut self) {
149        self.min = f64::INFINITY;
150        self.max = f64::NEG_INFINITY;
151    }
152}