Skip to main content

ggplot_rs/scale/
color.rs

1use crate::aes::Aesthetic;
2use crate::data::Value;
3
4use super::util::{format_number, nice_step};
5use super::Scale;
6
7/// RGBA color representation.
8#[derive(Clone, Debug, Copy)]
9pub struct RGBAColor {
10    pub r: u8,
11    pub g: u8,
12    pub b: u8,
13    pub a: f64,
14}
15
16impl RGBAColor {
17    pub fn new(r: u8, g: u8, b: u8) -> Self {
18        RGBAColor { r, g, b, a: 1.0 }
19    }
20
21    pub fn with_alpha(mut self, a: f64) -> Self {
22        self.a = a;
23        self
24    }
25
26    /// Interpolate between two colors.
27    pub fn lerp(&self, other: &RGBAColor, t: f64) -> RGBAColor {
28        let t = t.clamp(0.0, 1.0);
29        RGBAColor {
30            r: (self.r as f64 * (1.0 - t) + other.r as f64 * t) as u8,
31            g: (self.g as f64 * (1.0 - t) + other.g as f64 * t) as u8,
32            b: (self.b as f64 * (1.0 - t) + other.b as f64 * t) as u8,
33            a: self.a * (1.0 - t) + other.a * t,
34        }
35    }
36}
37
38/// Default discrete color palette (8 colors, similar to ggplot2 default).
39pub const DEFAULT_PALETTE: &[RGBAColor] = &[
40    RGBAColor {
41        r: 248,
42        g: 118,
43        b: 109,
44        a: 1.0,
45    }, // red
46    RGBAColor {
47        r: 0,
48        g: 186,
49        b: 56,
50        a: 1.0,
51    }, // green
52    RGBAColor {
53        r: 97,
54        g: 156,
55        b: 255,
56        a: 1.0,
57    }, // blue
58    RGBAColor {
59        r: 163,
60        g: 103,
61        b: 203,
62        a: 1.0,
63    }, // purple
64    RGBAColor {
65        r: 231,
66        g: 138,
67        b: 0,
68        a: 1.0,
69    }, // orange
70    RGBAColor {
71        r: 0,
72        g: 191,
73        b: 196,
74        a: 1.0,
75    }, // cyan
76    RGBAColor {
77        r: 199,
78        g: 124,
79        b: 255,
80        a: 1.0,
81    }, // violet
82    RGBAColor {
83        r: 127,
84        g: 127,
85        b: 127,
86        a: 1.0,
87    }, // gray
88];
89
90/// Discrete color scale — maps categories to distinct colors.
91#[derive(Clone, Debug)]
92pub struct ScaleColorDiscrete {
93    aesthetic: Aesthetic,
94    name: String,
95    levels: Vec<String>,
96    palette: Vec<RGBAColor>,
97}
98
99impl ScaleColorDiscrete {
100    pub fn new(aesthetic: Aesthetic) -> Self {
101        ScaleColorDiscrete {
102            aesthetic,
103            name: String::new(),
104            levels: Vec::new(),
105            palette: DEFAULT_PALETTE.to_vec(),
106        }
107    }
108
109    pub fn with_palette(mut self, colors: Vec<RGBAColor>) -> Self {
110        self.palette = colors;
111        self
112    }
113
114    pub fn with_named_palette(mut self, name: &super::palettes::PaletteName) -> Self {
115        self.palette = super::palettes::palette(name).to_vec();
116        self
117    }
118
119    /// Get color for a given level index.
120    pub fn color_for_index(&self, idx: usize) -> RGBAColor {
121        self.palette[idx % self.palette.len()]
122    }
123
124    /// Get color for a value.
125    pub fn color_for_value(&self, value: &Value) -> RGBAColor {
126        let key = value.to_group_key();
127        let idx = self.levels.iter().position(|l| l == &key).unwrap_or(0);
128        self.color_for_index(idx)
129    }
130
131    pub fn levels(&self) -> &[String] {
132        &self.levels
133    }
134}
135
136impl Scale for ScaleColorDiscrete {
137    fn aesthetic(&self) -> Aesthetic {
138        self.aesthetic.clone()
139    }
140
141    fn train(&mut self, values: &[Value]) {
142        for v in values {
143            let key = v.to_group_key();
144            if !self.levels.contains(&key) {
145                self.levels.push(key);
146            }
147        }
148    }
149
150    fn map(&self, value: &Value) -> f64 {
151        let key = value.to_group_key();
152        self.levels
153            .iter()
154            .position(|l| l == &key)
155            .map(|i| i as f64)
156            .unwrap_or(0.0)
157    }
158
159    fn breaks(&self) -> Vec<(f64, String)> {
160        self.levels
161            .iter()
162            .enumerate()
163            .map(|(i, label)| (i as f64, label.clone()))
164            .collect()
165    }
166
167    fn name(&self) -> &str {
168        &self.name
169    }
170
171    fn set_name(&mut self, name: &str) {
172        self.name = name.to_string();
173    }
174
175    fn is_discrete(&self) -> bool {
176        true
177    }
178
179    fn map_to_color(&self, value: &Value) -> Option<(u8, u8, u8)> {
180        let c = self.color_for_value(value);
181        Some((c.r, c.g, c.b))
182    }
183
184    fn clone_box(&self) -> Box<dyn Scale> {
185        Box::new(self.clone())
186    }
187
188    fn reset_training(&mut self) {
189        self.levels.clear();
190    }
191}
192
193/// Continuous gradient color scale.
194#[derive(Clone, Debug)]
195pub struct ScaleColorContinuous {
196    aesthetic: Aesthetic,
197    name: String,
198    low: RGBAColor,
199    high: RGBAColor,
200    min: f64,
201    max: f64,
202}
203
204impl ScaleColorContinuous {
205    pub fn new(aesthetic: Aesthetic) -> Self {
206        ScaleColorContinuous {
207            aesthetic,
208            name: String::new(),
209            low: RGBAColor::new(0, 0, 255),
210            high: RGBAColor::new(255, 0, 0),
211            min: f64::INFINITY,
212            max: f64::NEG_INFINITY,
213        }
214    }
215
216    pub fn with_colors(mut self, low: RGBAColor, high: RGBAColor) -> Self {
217        self.low = low;
218        self.high = high;
219        self
220    }
221
222    pub fn color_at(&self, t: f64) -> RGBAColor {
223        self.low.lerp(&self.high, t)
224    }
225}
226
227impl Scale for ScaleColorContinuous {
228    fn aesthetic(&self) -> Aesthetic {
229        self.aesthetic.clone()
230    }
231
232    fn train(&mut self, values: &[Value]) {
233        for v in values {
234            if let Some(f) = v.as_f64() {
235                if f.is_finite() {
236                    if f < self.min {
237                        self.min = f;
238                    }
239                    if f > self.max {
240                        self.max = f;
241                    }
242                }
243            }
244        }
245    }
246
247    fn map(&self, value: &Value) -> f64 {
248        let f = match value.as_f64() {
249            Some(f) => f,
250            None => return 0.0,
251        };
252        let range = self.max - self.min;
253        if range.abs() < f64::EPSILON {
254            0.5
255        } else {
256            (f - self.min) / range
257        }
258    }
259
260    fn breaks(&self) -> Vec<(f64, String)> {
261        if self.min > self.max || !self.min.is_finite() || !self.max.is_finite() {
262            return vec![];
263        }
264
265        let range = self.max - self.min;
266        if range.abs() < f64::EPSILON {
267            return vec![(0.5, format_number(self.min))];
268        }
269
270        let n_breaks = 5;
271        let raw_step = range / n_breaks as f64;
272        let step = nice_step(raw_step);
273
274        let start = (self.min / step).ceil() * step;
275        let mut breaks = Vec::new();
276        let mut v = start;
277        while v <= self.max + step * 0.001 {
278            let pos = self.map(&Value::Float(v));
279            breaks.push((pos, format_number(v)));
280            v += step;
281        }
282        breaks
283    }
284
285    fn name(&self) -> &str {
286        &self.name
287    }
288
289    fn set_name(&mut self, name: &str) {
290        self.name = name.to_string();
291    }
292
293    fn map_to_color(&self, value: &Value) -> Option<(u8, u8, u8)> {
294        let t = self.map(value);
295        let c = self.color_at(t);
296        Some((c.r, c.g, c.b))
297    }
298
299    fn domain(&self) -> Option<(f64, f64)> {
300        if self.min.is_finite() && self.max.is_finite() && self.min <= self.max {
301            Some((self.min, self.max))
302        } else {
303            None
304        }
305    }
306
307    fn clone_box(&self) -> Box<dyn Scale> {
308        Box::new(self.clone())
309    }
310
311    fn reset_training(&mut self) {
312        self.min = f64::INFINITY;
313        self.max = f64::NEG_INFINITY;
314    }
315}