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    /// Pre-seed the factor levels (order), so each level keeps a fixed palette
120    /// color regardless of which levels are present in the data — e.g. for a
121    /// legend whose series can be toggled without the colors reshuffling.
122    pub fn with_levels(mut self, levels: Vec<String>) -> Self {
123        self.levels = levels;
124        self
125    }
126
127    /// Get color for a given level index.
128    pub fn color_for_index(&self, idx: usize) -> RGBAColor {
129        self.palette[idx % self.palette.len()]
130    }
131
132    /// Get color for a value.
133    pub fn color_for_value(&self, value: &Value) -> RGBAColor {
134        let key = value.to_group_key();
135        let idx = self.levels.iter().position(|l| l == &key).unwrap_or(0);
136        self.color_for_index(idx)
137    }
138
139    pub fn levels(&self) -> &[String] {
140        &self.levels
141    }
142}
143
144impl Scale for ScaleColorDiscrete {
145    fn aesthetic(&self) -> Aesthetic {
146        self.aesthetic.clone()
147    }
148
149    fn train(&mut self, values: &[Value]) {
150        for v in values {
151            let key = v.to_group_key();
152            if !self.levels.contains(&key) {
153                self.levels.push(key);
154            }
155        }
156    }
157
158    fn map(&self, value: &Value) -> f64 {
159        let key = value.to_group_key();
160        self.levels
161            .iter()
162            .position(|l| l == &key)
163            .map(|i| i as f64)
164            .unwrap_or(0.0)
165    }
166
167    fn breaks(&self) -> Vec<(f64, String)> {
168        self.levels
169            .iter()
170            .enumerate()
171            .map(|(i, label)| (i as f64, label.clone()))
172            .collect()
173    }
174
175    fn name(&self) -> &str {
176        &self.name
177    }
178
179    fn set_name(&mut self, name: &str) {
180        self.name = name.to_string();
181    }
182
183    fn is_discrete(&self) -> bool {
184        true
185    }
186
187    fn map_to_color(&self, value: &Value) -> Option<(u8, u8, u8)> {
188        let c = self.color_for_value(value);
189        Some((c.r, c.g, c.b))
190    }
191
192    fn clone_box(&self) -> Box<dyn Scale> {
193        Box::new(self.clone())
194    }
195
196    fn reset_training(&mut self) {
197        self.levels.clear();
198    }
199}
200
201/// Continuous gradient color scale.
202#[derive(Clone, Debug)]
203pub struct ScaleColorContinuous {
204    aesthetic: Aesthetic,
205    name: String,
206    low: RGBAColor,
207    high: RGBAColor,
208    min: f64,
209    max: f64,
210}
211
212impl ScaleColorContinuous {
213    pub fn new(aesthetic: Aesthetic) -> Self {
214        ScaleColorContinuous {
215            aesthetic,
216            name: String::new(),
217            low: RGBAColor::new(0, 0, 255),
218            high: RGBAColor::new(255, 0, 0),
219            min: f64::INFINITY,
220            max: f64::NEG_INFINITY,
221        }
222    }
223
224    pub fn with_colors(mut self, low: RGBAColor, high: RGBAColor) -> Self {
225        self.low = low;
226        self.high = high;
227        self
228    }
229
230    pub fn color_at(&self, t: f64) -> RGBAColor {
231        self.low.lerp(&self.high, t)
232    }
233}
234
235impl Scale for ScaleColorContinuous {
236    fn aesthetic(&self) -> Aesthetic {
237        self.aesthetic.clone()
238    }
239
240    fn train(&mut self, values: &[Value]) {
241        for v in values {
242            if let Some(f) = v.as_f64() {
243                if f.is_finite() {
244                    if f < self.min {
245                        self.min = f;
246                    }
247                    if f > self.max {
248                        self.max = f;
249                    }
250                }
251            }
252        }
253    }
254
255    fn map(&self, value: &Value) -> f64 {
256        let f = match value.as_f64() {
257            Some(f) => f,
258            None => return 0.0,
259        };
260        let range = self.max - self.min;
261        if range.abs() < f64::EPSILON {
262            0.5
263        } else {
264            (f - self.min) / range
265        }
266    }
267
268    fn breaks(&self) -> Vec<(f64, String)> {
269        if self.min > self.max || !self.min.is_finite() || !self.max.is_finite() {
270            return vec![];
271        }
272
273        let range = self.max - self.min;
274        if range.abs() < f64::EPSILON {
275            return vec![(0.5, format_number(self.min))];
276        }
277
278        let n_breaks = 5;
279        let raw_step = range / n_breaks as f64;
280        let step = nice_step(raw_step);
281
282        let start = (self.min / step).ceil() * step;
283        let mut breaks = Vec::new();
284        let mut v = start;
285        while v <= self.max + step * 0.001 {
286            let pos = self.map(&Value::Float(v));
287            breaks.push((pos, format_number(v)));
288            v += step;
289        }
290        breaks
291    }
292
293    fn name(&self) -> &str {
294        &self.name
295    }
296
297    fn set_name(&mut self, name: &str) {
298        self.name = name.to_string();
299    }
300
301    fn map_to_color(&self, value: &Value) -> Option<(u8, u8, u8)> {
302        let t = self.map(value);
303        let c = self.color_at(t);
304        Some((c.r, c.g, c.b))
305    }
306
307    fn domain(&self) -> Option<(f64, f64)> {
308        if self.min.is_finite() && self.max.is_finite() && self.min <= self.max {
309            Some((self.min, self.max))
310        } else {
311            None
312        }
313    }
314
315    fn clone_box(&self) -> Box<dyn Scale> {
316        Box::new(self.clone())
317    }
318
319    fn reset_training(&mut self) {
320        self.min = f64::INFINITY;
321        self.max = f64::NEG_INFINITY;
322    }
323}