1use crate::aes::Aesthetic;
2use crate::data::Value;
3
4use super::color::RGBAColor;
5use super::format::UnwindSafeFormatter;
6use super::util::nice_step;
7use super::Scale;
8
9#[derive(Clone)]
12pub struct ScaleColorGradientN {
13 aesthetic: Aesthetic,
14 name: String,
15 stops: Vec<(f64, RGBAColor)>,
17 min: f64,
18 max: f64,
19 locale: Option<crate::locale::Locale>,
21 limits: Option<(f64, f64)>,
25 oob: super::Oob,
28 na_value: (u8, u8, u8),
32 value_formatter: Option<UnwindSafeFormatter>,
36}
37
38impl std::fmt::Debug for ScaleColorGradientN {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 f.debug_struct("ScaleColorGradientN")
42 .field("aesthetic", &self.aesthetic)
43 .field("name", &self.name)
44 .field("stops", &self.stops)
45 .field("min", &self.min)
46 .field("max", &self.max)
47 .field("locale", &self.locale)
48 .field("limits", &self.limits)
49 .field("oob", &self.oob)
50 .field("na_value", &self.na_value)
51 .field("value_formatter", &self.value_formatter.is_some())
52 .finish()
53 }
54}
55
56impl ScaleColorGradientN {
57 pub fn new(aesthetic: Aesthetic, stops: Vec<(f64, RGBAColor)>) -> Self {
60 let mut stops = stops;
61 stops.sort_by(|a, b| a.0.total_cmp(&b.0));
62 ScaleColorGradientN {
63 aesthetic,
64 name: String::new(),
65 stops,
66 min: f64::INFINITY,
67 max: f64::NEG_INFINITY,
68 locale: None,
69 limits: None,
70 oob: super::Oob::Censor,
71 na_value: super::DEFAULT_NA_VALUE,
72 value_formatter: None,
73 }
74 }
75
76 pub fn with_value_formatter<F>(mut self, f: F) -> Self
81 where
82 F: Fn(f64) -> String + Send + Sync + 'static,
83 {
84 self.value_formatter = Some(UnwindSafeFormatter(std::sync::Arc::new(f)));
85 self
86 }
87
88 pub fn with_limits(mut self, lo: f64, hi: f64) -> Self {
92 self.limits = Some((lo, hi));
93 self
94 }
95
96 pub fn with_oob(mut self, oob: super::Oob) -> Self {
100 self.oob = oob;
101 self
102 }
103
104 pub fn with_na_value(mut self, color: (u8, u8, u8)) -> Self {
108 self.na_value = color;
109 self
110 }
111
112 pub fn viridis(aesthetic: Aesthetic) -> Self {
114 Self::new(aesthetic, viridis_stops())
115 }
116
117 pub fn magma(aesthetic: Aesthetic) -> Self {
119 Self::new(aesthetic, magma_stops())
120 }
121
122 pub fn plasma(aesthetic: Aesthetic) -> Self {
124 Self::new(aesthetic, plasma_stops())
125 }
126
127 pub fn inferno(aesthetic: Aesthetic) -> Self {
129 Self::new(aesthetic, inferno_stops())
130 }
131
132 fn color_at(&self, t: f64) -> RGBAColor {
134 let t = t.clamp(0.0, 1.0);
135 if self.stops.is_empty() {
136 return RGBAColor::new(127, 127, 127);
137 }
138 if self.stops.len() == 1 {
139 return self.stops[0].1;
140 }
141 if t <= self.stops[0].0 {
143 return self.stops[0].1;
144 }
145 if t >= self.stops[self.stops.len() - 1].0 {
146 return self.stops[self.stops.len() - 1].1;
147 }
148 for i in 0..self.stops.len() - 1 {
149 let (p0, c0) = &self.stops[i];
150 let (p1, c1) = &self.stops[i + 1];
151 if t >= *p0 && t <= *p1 {
152 let range = p1 - p0;
153 let local_t = if range.abs() < f64::EPSILON {
154 0.0
155 } else {
156 (t - p0) / range
157 };
158 return c0.lerp(c1, local_t);
159 }
160 }
161 self.stops[self.stops.len() - 1].1
162 }
163}
164
165impl Scale for ScaleColorGradientN {
166 fn set_default_locale(&mut self, locale: &crate::locale::Locale) {
167 if self.locale.is_none() {
168 self.locale = Some(locale.clone());
169 }
170 }
171
172 fn locale(&self) -> Option<&crate::locale::Locale> {
173 self.locale.as_ref()
174 }
175
176 fn format_value(&self, value: &Value) -> Option<String> {
177 let f = value.as_f64()?;
178 if !f.is_finite() {
179 return None;
180 }
181 Some(match &self.value_formatter {
182 Some(fmt) => fmt.call(f),
183 None => super::util::format_number_opt(f, self.locale.as_ref()),
184 })
185 }
186
187 fn aesthetic(&self) -> Aesthetic {
188 self.aesthetic.clone()
189 }
190
191 fn train(&mut self, values: &[Value]) {
192 if self.limits.is_some() {
193 return;
194 }
195 for v in values {
196 if let Some(f) = v.as_f64() {
197 if f.is_finite() {
198 if f < self.min {
199 self.min = f;
200 }
201 if f > self.max {
202 self.max = f;
203 }
204 }
205 }
206 }
207 }
208
209 fn map(&self, value: &Value) -> f64 {
210 let f = match value.as_f64() {
211 Some(f) => f,
212 None => return 0.0,
213 };
214 let (lo, hi) = self.limits.unwrap_or((self.min, self.max));
215 let range = hi - lo;
216 if range.abs() < f64::EPSILON {
217 0.5
218 } else {
219 (f - lo) / range
220 }
221 }
222
223 fn breaks(&self) -> Vec<(f64, String)> {
224 let (lo, hi) = self.limits.unwrap_or((self.min, self.max));
225 if lo > hi || !lo.is_finite() || !hi.is_finite() {
226 return vec![];
227 }
228 let range = hi - lo;
229 if super::util::is_degenerate_range(lo, hi) {
230 return vec![(
231 0.5,
232 super::util::format_number_opt(lo, self.locale.as_ref()),
233 )];
234 }
235 let n_breaks = 5;
236 let raw_step = range / n_breaks as f64;
237 let step = nice_step(raw_step);
238 let start = (lo / step).ceil() * step;
239 let values = super::util::stepped_breaks(start, hi, step);
240 let labels = super::util::format_numbers_opt(&values, self.locale.as_ref());
241 values
242 .iter()
243 .zip(labels)
244 .map(|(&v, l)| (self.map(&Value::Float(v)), l))
245 .collect()
246 }
247
248 fn name(&self) -> &str {
249 &self.name
250 }
251
252 fn set_name(&mut self, name: &str) {
253 self.name = name.to_string();
254 }
255
256 fn map_to_color(&self, value: &Value) -> Option<(u8, u8, u8)> {
257 if value.is_na() {
265 return Some(self.na_value);
266 }
267 if let Some(f) = value.as_f64() {
268 if !f.is_finite() {
269 return Some(self.na_value);
270 }
271 if super::resolve_oob(f, self.limits, self.oob).is_none() {
272 return Some(self.na_value);
273 }
274 }
275 let t = self.map(value);
276 let c = self.color_at(t);
277 Some((c.r, c.g, c.b))
278 }
279
280 fn domain(&self) -> Option<(f64, f64)> {
281 if let Some((lo, hi)) = self.limits {
282 return Some((lo, hi));
283 }
284 if self.min.is_finite() && self.max.is_finite() && self.min <= self.max {
285 Some((self.min, self.max))
286 } else {
287 None
288 }
289 }
290
291 fn clone_box(&self) -> Box<dyn Scale> {
292 Box::new(self.clone())
293 }
294
295 fn reset_training(&mut self) {
296 self.min = f64::INFINITY;
297 self.max = f64::NEG_INFINITY;
298 }
299
300 fn validate(&self, warnings: &mut Vec<String>) {
301 if let Some((lo, hi)) = self.limits {
302 if lo > hi {
303 warnings.push(format!(
304 "scale_color/fill_gradientn: with_limits({lo}, {hi}) has lo > hi; every value maps to na_value"
305 ));
306 }
307 }
308 }
309}
310
311fn c(r: u8, g: u8, b: u8) -> RGBAColor {
314 RGBAColor::new(r, g, b)
315}
316
317fn viridis_stops() -> Vec<(f64, RGBAColor)> {
318 let colors = [
319 c(68, 1, 84),
320 c(72, 26, 108),
321 c(71, 47, 126),
322 c(65, 68, 135),
323 c(57, 86, 140),
324 c(47, 104, 142),
325 c(38, 121, 142),
326 c(31, 138, 141),
327 c(30, 155, 138),
328 c(42, 172, 130),
329 c(70, 188, 115),
330 c(109, 202, 93),
331 c(155, 213, 67),
332 c(200, 222, 39),
333 c(240, 229, 30),
334 c(253, 231, 37),
335 ];
336 evenly_spaced_stops(&colors)
337}
338
339fn magma_stops() -> Vec<(f64, RGBAColor)> {
340 let colors = [
341 c(0, 0, 4),
342 c(16, 12, 50),
343 c(41, 17, 90),
344 c(72, 12, 110),
345 c(101, 19, 110),
346 c(131, 29, 103),
347 c(160, 42, 93),
348 c(187, 55, 84),
349 c(213, 72, 72),
350 c(232, 99, 62),
351 c(247, 131, 57),
352 c(254, 167, 69),
353 c(254, 203, 99),
354 c(252, 235, 141),
355 c(252, 254, 188),
356 c(252, 253, 191),
357 ];
358 evenly_spaced_stops(&colors)
359}
360
361fn plasma_stops() -> Vec<(f64, RGBAColor)> {
362 let colors = [
363 c(13, 8, 135),
364 c(53, 5, 157),
365 c(82, 1, 163),
366 c(109, 1, 159),
367 c(133, 7, 147),
368 c(156, 23, 127),
369 c(175, 42, 106),
370 c(192, 61, 85),
371 c(206, 82, 66),
372 c(218, 105, 46),
373 c(228, 130, 24),
374 c(236, 157, 6),
375 c(240, 185, 11),
376 c(239, 213, 38),
377 c(232, 240, 73),
378 c(240, 249, 33),
379 ];
380 evenly_spaced_stops(&colors)
381}
382
383fn inferno_stops() -> Vec<(f64, RGBAColor)> {
384 let colors = [
385 c(0, 0, 4),
386 c(14, 11, 49),
387 c(39, 15, 90),
388 c(67, 10, 107),
389 c(95, 13, 106),
390 c(122, 21, 97),
391 c(149, 33, 81),
392 c(174, 49, 60),
393 c(196, 69, 38),
394 c(215, 95, 15),
395 c(231, 124, 3),
396 c(243, 155, 7),
397 c(250, 189, 28),
398 c(252, 222, 67),
399 c(247, 252, 118),
400 c(252, 255, 164),
401 ];
402 evenly_spaced_stops(&colors)
403}
404
405fn evenly_spaced_stops(colors: &[RGBAColor]) -> Vec<(f64, RGBAColor)> {
406 let n = colors.len();
407 if n == 0 {
408 return vec![];
409 }
410 if n == 1 {
411 return vec![(0.0, colors[0])];
412 }
413 colors
414 .iter()
415 .enumerate()
416 .map(|(i, c)| (i as f64 / (n - 1) as f64, *c))
417 .collect()
418}
419
420#[cfg(test)]
421mod tests {
422 use super::*;
423
424 #[test]
425 fn format_value_formats_a_finite_number_and_rejects_non_finite() {
426 let g = ScaleColorGradientN::new(Aesthetic::Color, vec![(0.0, RGBAColor::new(0, 0, 0))]);
427 assert_eq!(g.format_value(&Value::Float(42.0)), Some("42".to_string()));
428 assert_eq!(g.format_value(&Value::Float(f64::INFINITY)), None);
429 }
430
431 #[test]
432 fn with_value_formatter_overrides_format_value() {
433 let g = ScaleColorGradientN::new(Aesthetic::Color, vec![(0.0, RGBAColor::new(0, 0, 0))])
434 .with_value_formatter(|v| format!("#{v:.0}"));
435 assert_eq!(g.format_value(&Value::Float(42.0)), Some("#42".to_string()));
436 assert_eq!(g.format_value(&Value::Float(f64::INFINITY)), None);
437 }
438
439 #[test]
440 fn debug_reports_whether_a_formatter_is_set() {
441 let stops = vec![(0.0, RGBAColor::new(0, 0, 0))];
442 let without = format!(
443 "{:?}",
444 ScaleColorGradientN::new(Aesthetic::Color, stops.clone())
445 );
446 assert!(without.contains("ScaleColorGradientN"));
447 assert!(without.contains("value_formatter: false"));
448 let with = format!(
449 "{:?}",
450 ScaleColorGradientN::new(Aesthetic::Color, stops)
451 .with_value_formatter(|v| v.to_string())
452 );
453 assert!(with.contains("value_formatter: true"));
454 }
455
456 #[test]
458 fn scale_color_gradient_n_keeps_every_auto_trait() {
459 fn assert_traits<
460 T: Send + Sync + std::panic::UnwindSafe + std::panic::RefUnwindSafe + Clone,
461 >() {
462 }
463 assert_traits::<ScaleColorGradientN>();
464 }
465
466 #[test]
467 fn with_limits_fixes_the_domain_and_training_does_not_widen_it() {
468 let mut g = ScaleColorGradientN::new(
469 Aesthetic::Color,
470 vec![
471 (0.0, RGBAColor::new(0, 0, 0)),
472 (1.0, RGBAColor::new(255, 255, 255)),
473 ],
474 )
475 .with_limits(0.0, 10.0);
476 g.train(&[Value::Float(-1000.0), Value::Float(1000.0)]);
477 assert_eq!(g.domain(), Some((0.0, 10.0)));
478 }
479
480 #[test]
481 fn test_gradient_n_interpolation() {
482 let g = ScaleColorGradientN::new(
483 Aesthetic::Color,
484 vec![
485 (0.0, RGBAColor::new(0, 0, 0)),
486 (0.5, RGBAColor::new(255, 0, 0)),
487 (1.0, RGBAColor::new(255, 255, 255)),
488 ],
489 );
490 let c0 = g.color_at(0.0);
492 assert_eq!((c0.r, c0.g, c0.b), (0, 0, 0));
493 let c5 = g.color_at(0.5);
495 assert_eq!((c5.r, c5.g, c5.b), (255, 0, 0));
496 let c1 = g.color_at(1.0);
498 assert_eq!((c1.r, c1.g, c1.b), (255, 255, 255));
499 let c25 = g.color_at(0.25);
501 assert_eq!(c25.r, 127); }
503
504 #[test]
505 fn test_viridis_continuous_endpoints() {
506 let g = ScaleColorGradientN::viridis(Aesthetic::Fill);
507 let c0 = g.color_at(0.0);
508 assert_eq!((c0.r, c0.g, c0.b), (68, 1, 84));
509 let c1 = g.color_at(1.0);
510 assert_eq!((c1.r, c1.g, c1.b), (253, 231, 37));
511 }
512}