1use crate::aes::Aesthetic;
2use crate::data::Value;
3use std::collections::HashMap;
4
5use super::util::nice_step;
6use super::Scale;
7
8#[derive(Clone, Debug, Copy)]
10pub struct RGBAColor {
11 pub r: u8,
12 pub g: u8,
13 pub b: u8,
14 pub a: f64,
15}
16
17impl RGBAColor {
18 pub fn new(r: u8, g: u8, b: u8) -> Self {
19 RGBAColor { r, g, b, a: 1.0 }
20 }
21
22 pub fn with_alpha(mut self, a: f64) -> Self {
23 self.a = a;
24 self
25 }
26
27 pub fn lerp(&self, other: &RGBAColor, t: f64) -> RGBAColor {
29 let t = t.clamp(0.0, 1.0);
30 RGBAColor {
31 r: (self.r as f64 * (1.0 - t) + other.r as f64 * t) as u8,
32 g: (self.g as f64 * (1.0 - t) + other.g as f64 * t) as u8,
33 b: (self.b as f64 * (1.0 - t) + other.b as f64 * t) as u8,
34 a: self.a * (1.0 - t) + other.a * t,
35 }
36 }
37}
38
39pub const DEFAULT_PALETTE: &[RGBAColor] = &[
43 RGBAColor {
44 r: 248,
45 g: 118,
46 b: 109,
47 a: 1.0,
48 }, RGBAColor {
50 r: 0,
51 g: 186,
52 b: 56,
53 a: 1.0,
54 }, RGBAColor {
56 r: 97,
57 g: 156,
58 b: 255,
59 a: 1.0,
60 }, RGBAColor {
62 r: 163,
63 g: 103,
64 b: 203,
65 a: 1.0,
66 }, RGBAColor {
68 r: 231,
69 g: 138,
70 b: 0,
71 a: 1.0,
72 }, RGBAColor {
74 r: 0,
75 g: 191,
76 b: 196,
77 a: 1.0,
78 }, RGBAColor {
80 r: 199,
81 g: 124,
82 b: 255,
83 a: 1.0,
84 }, RGBAColor {
86 r: 127,
87 g: 127,
88 b: 127,
89 a: 1.0,
90 }, ];
92
93#[derive(Clone, Debug)]
95pub struct ScaleColorDiscrete {
96 aesthetic: Aesthetic,
97 name: String,
98 levels: Vec<String>,
99 level_index: HashMap<String, usize>,
102 palette: Vec<RGBAColor>,
103 sorted: bool,
106 generated: Option<GeneratedPalette>,
110}
111
112#[derive(Clone, Debug, PartialEq)]
115pub struct HuePalette {
116 pub h: (f64, f64),
117 pub c: f64,
118 pub l: f64,
119 pub h_start: f64,
120 pub direction: i32,
122}
123
124impl Default for HuePalette {
125 fn default() -> Self {
127 HuePalette {
128 h: (15.0, 375.0),
129 c: 100.0,
130 l: 65.0,
131 h_start: 0.0,
132 direction: 1,
133 }
134 }
135}
136
137#[derive(Clone, Debug, PartialEq)]
139pub enum GeneratedPalette {
140 Hue(HuePalette),
141 Viridis {
146 option: char,
147 begin: f64,
148 end: f64,
149 direction: i32,
150 },
151}
152
153impl GeneratedPalette {
154 pub fn color_at(&self, i: usize, n: usize) -> RGBAColor {
158 match self {
159 GeneratedPalette::Hue(p) => {
160 let n = n.max(1);
161 let i = if p.direction == -1 {
162 n - 1 - i % n
163 } else {
164 i % n
165 };
166 hue_color(i, n, p)
167 }
168 _ => {
169 let pal = self.colors(n);
170 pal[i % pal.len()]
171 }
172 }
173 }
174
175 pub fn colors(&self, n: usize) -> Vec<RGBAColor> {
177 let n = n.max(1);
178 match self {
179 GeneratedPalette::Hue(p) => hue_palette(n, p),
180 GeneratedPalette::Viridis {
181 option,
182 begin,
183 end,
184 direction,
185 } => {
186 let opt = option.to_ascii_uppercase();
187 let lut = super::viridis_lut::VIRIDIS_LUT
188 .iter()
189 .find(|(o, _)| *o == opt)
190 .map_or(&super::viridis_lut::VIRIDIS_LUT[3].1, |(_, l)| l);
191 let at = |t: f64| {
194 let f = t.clamp(0.0, 1.0) * 255.0;
195 let (i, j) = (f.floor() as usize, (f.ceil() as usize).min(255));
196 let w = f - i as f64;
197 let mix = |k: usize| {
198 (lut[i][k] as f64 * (1.0 - w) + lut[j][k] as f64 * w).round() as u8
199 };
200 RGBAColor::new(mix(0), mix(1), mix(2))
201 };
202 let mut out: Vec<RGBAColor> = (0..n)
203 .map(|i| {
204 let t = if n == 1 {
205 *begin
206 } else {
207 begin + (end - begin) * i as f64 / (n - 1) as f64
208 };
209 at(t)
210 })
211 .collect();
212 if *direction < 0 {
213 out.reverse();
214 }
215 out
216 }
217 }
218 }
219}
220
221pub fn hcl_color(h: f64, c: f64, l: f64) -> RGBAColor {
223 const WHITE_Y: f64 = 100.0;
224 const WHITE_U: f64 = 0.1978398;
225 const WHITE_V: f64 = 0.4683363;
226 let hr = h.to_radians();
227 let (u_, v_) = (c * hr.cos(), c * hr.sin());
228 let (x, y, z) = if l <= 0.0 && u_ == 0.0 && v_ == 0.0 {
229 (0.0, 0.0, 0.0)
230 } else {
231 let y = WHITE_Y
232 * if l > 7.999592 {
233 ((l + 16.0) / 116.0).powi(3)
234 } else {
235 l / 903.3
236 };
237 let u = u_ / (13.0 * l) + WHITE_U;
238 let v = v_ / (13.0 * l) + WHITE_V;
239 let x = 9.0 * y * u / (4.0 * v);
240 let z = -x / 3.0 - 5.0 * y + 3.0 * y / v;
241 (x, y, z)
242 };
243 let gamma = |u: f64| {
244 let u = u / WHITE_Y;
245 if u > 0.00304 {
246 1.055 * u.powf(1.0 / 2.4) - 0.055
247 } else {
248 12.92 * u
249 }
250 };
251 let byte = |v: f64| (255.0 * v.clamp(0.0, 1.0) + 0.5) as u8;
252 RGBAColor::new(
253 byte(gamma(3.240479 * x - 1.537150 * y - 0.498535 * z)),
254 byte(gamma(-0.969256 * x + 1.875992 * y + 0.041556 * z)),
255 byte(gamma(0.055648 * x - 0.204043 * y + 1.057311 * z)),
256 )
257}
258
259pub fn hue_palette(n: usize, p: &HuePalette) -> Vec<RGBAColor> {
261 let n = n.max(1);
262 let mut out: Vec<RGBAColor> = (0..n).map(|i| hue_color(i, n, p)).collect();
263 if p.direction == -1 {
264 out.reverse();
265 }
266 out
267}
268
269fn hue_color(i: usize, n: usize, p: &HuePalette) -> RGBAColor {
271 let (h0, mut h1) = p.h;
272 if (h1 - h0).rem_euclid(360.0) < 1.0 {
273 h1 -= 360.0 / n as f64;
274 }
275 let h = if n == 1 {
276 h0
277 } else {
278 h0 + (h1 - h0) * i as f64 / (n - 1) as f64
279 };
280 hcl_color((h + p.h_start).rem_euclid(360.0), p.c, p.l)
281}
282
283impl ScaleColorDiscrete {
284 pub fn new(aesthetic: Aesthetic) -> Self {
285 ScaleColorDiscrete {
286 aesthetic,
287 name: String::new(),
288 levels: Vec::new(),
289 level_index: HashMap::new(),
290 palette: DEFAULT_PALETTE.to_vec(),
291 sorted: false,
292 generated: Some(GeneratedPalette::Hue(HuePalette::default())),
294 }
295 }
296
297 pub fn with_generated(mut self, p: GeneratedPalette) -> Self {
301 self.generated = Some(p);
302 self
303 }
304
305 pub fn sorted(mut self) -> Self {
310 self.sorted = true;
311 self.resort();
312 self
313 }
314
315 fn resort(&mut self) {
316 if !self.sorted {
317 return;
318 }
319 self.levels.sort();
320 self.level_index = self
321 .levels
322 .iter()
323 .enumerate()
324 .map(|(i, l)| (l.clone(), i))
325 .collect();
326 }
327
328 pub fn with_palette(mut self, colors: Vec<RGBAColor>) -> Self {
329 self.palette = colors;
330 self.generated = None;
331 self
332 }
333
334 pub fn with_named_palette(mut self, name: &super::palettes::PaletteName) -> Self {
335 self.palette = super::palettes::palette(name).to_vec();
336 self.generated = None;
337 self
338 }
339
340 pub fn with_levels(mut self, levels: Vec<String>) -> Self {
344 self.levels.clear();
345 self.level_index.clear();
346 for l in levels {
347 self.push_level(l);
348 }
349 self.resort();
350 self
351 }
352
353 fn push_level(&mut self, key: String) {
354 if !self.level_index.contains_key(&key) {
355 self.level_index.insert(key.clone(), self.levels.len());
356 self.levels.push(key);
357 }
358 }
359
360 fn level_position(&self, key: &str) -> Option<usize> {
361 self.level_index.get(key).copied()
362 }
363
364 pub fn color_for_index(&self, idx: usize) -> RGBAColor {
366 if let Some(g) = &self.generated {
367 return g.color_at(idx, self.levels.len().max(idx + 1));
368 }
369 if self.palette.is_empty() {
370 return DEFAULT_PALETTE[idx % DEFAULT_PALETTE.len()];
371 }
372 self.palette[idx % self.palette.len()]
373 }
374
375 pub fn color_for_value(&self, value: &Value) -> RGBAColor {
377 let key = value.key_str();
378 let idx = self.level_position(&key).unwrap_or(0);
379 self.color_for_index(idx)
380 }
381
382 pub fn levels(&self) -> &[String] {
383 &self.levels
384 }
385}
386
387impl Scale for ScaleColorDiscrete {
388 fn aesthetic(&self) -> Aesthetic {
389 self.aesthetic.clone()
390 }
391
392 fn train(&mut self, values: &[Value]) {
393 let before = self.levels.len();
394 for v in values {
395 let key = v.key_str();
396 if !self.level_index.contains_key(key.as_ref()) {
397 self.push_level(key.into_owned());
398 }
399 }
400 if self.levels.len() != before {
401 self.resort();
402 }
403 }
404
405 fn map(&self, value: &Value) -> f64 {
406 let key = value.key_str();
407 self.level_position(&key).map(|i| i as f64).unwrap_or(0.0)
408 }
409
410 fn breaks(&self) -> Vec<(f64, String)> {
411 self.levels
412 .iter()
413 .enumerate()
414 .map(|(i, label)| (i as f64, label.clone()))
415 .collect()
416 }
417
418 fn name(&self) -> &str {
419 &self.name
420 }
421
422 fn set_name(&mut self, name: &str) {
423 self.name = name.to_string();
424 }
425
426 fn is_discrete(&self) -> bool {
427 true
428 }
429
430 fn map_to_color(&self, value: &Value) -> Option<(u8, u8, u8)> {
431 let c = self.color_for_value(value);
432 Some((c.r, c.g, c.b))
433 }
434
435 fn clone_box(&self) -> Box<dyn Scale> {
436 Box::new(self.clone())
437 }
438
439 fn reset_training(&mut self) {
440 self.levels.clear();
441 self.level_index.clear();
442 }
443}
444
445#[derive(Clone)]
447pub struct ScaleColorContinuous {
448 aesthetic: Aesthetic,
449 name: String,
450 low: RGBAColor,
451 high: RGBAColor,
452 min: f64,
453 max: f64,
454 locale: Option<crate::locale::Locale>,
456 limits: Option<(f64, f64)>,
460 oob: super::Oob,
463 na_value: (u8, u8, u8),
467 value_formatter: Option<super::format::UnwindSafeFormatter>,
474}
475
476impl std::fmt::Debug for ScaleColorContinuous {
479 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
480 f.debug_struct("ScaleColorContinuous")
481 .field("aesthetic", &self.aesthetic)
482 .field("name", &self.name)
483 .field("low", &self.low)
484 .field("high", &self.high)
485 .field("min", &self.min)
486 .field("max", &self.max)
487 .field("locale", &self.locale)
488 .field("limits", &self.limits)
489 .field("oob", &self.oob)
490 .field("na_value", &self.na_value)
491 .field("value_formatter", &self.value_formatter.is_some())
492 .finish()
493 }
494}
495
496impl ScaleColorContinuous {
497 pub fn new(aesthetic: Aesthetic) -> Self {
498 ScaleColorContinuous {
499 aesthetic,
500 name: String::new(),
501 low: RGBAColor::new(0, 0, 255),
502 high: RGBAColor::new(255, 0, 0),
503 min: f64::INFINITY,
504 max: f64::NEG_INFINITY,
505 locale: None,
506 limits: None,
507 oob: super::Oob::Censor,
508 na_value: super::DEFAULT_NA_VALUE,
509 value_formatter: None,
510 }
511 }
512
513 pub fn with_value_formatter<F>(mut self, f: F) -> Self
518 where
519 F: Fn(f64) -> String + Send + Sync + 'static,
520 {
521 self.value_formatter = Some(super::format::UnwindSafeFormatter(std::sync::Arc::new(f)));
522 self
523 }
524
525 pub fn with_colors(mut self, low: RGBAColor, high: RGBAColor) -> Self {
526 self.low = low;
527 self.high = high;
528 self
529 }
530
531 pub fn with_limits(mut self, lo: f64, hi: f64) -> Self {
536 self.limits = Some((lo, hi));
537 self
538 }
539
540 pub fn with_oob(mut self, oob: super::Oob) -> Self {
544 self.oob = oob;
545 self
546 }
547
548 pub fn with_na_value(mut self, color: (u8, u8, u8)) -> Self {
552 self.na_value = color;
553 self
554 }
555
556 pub fn color_at(&self, t: f64) -> RGBAColor {
557 self.low.lerp(&self.high, t)
558 }
559}
560
561impl Scale for ScaleColorContinuous {
562 fn set_default_locale(&mut self, locale: &crate::locale::Locale) {
563 if self.locale.is_none() {
564 self.locale = Some(locale.clone());
565 }
566 }
567
568 fn locale(&self) -> Option<&crate::locale::Locale> {
569 self.locale.as_ref()
570 }
571
572 fn format_value(&self, value: &Value) -> Option<String> {
573 let f = value.as_f64()?;
574 if !f.is_finite() {
575 return None;
576 }
577 Some(match &self.value_formatter {
578 Some(fmt) => fmt.call(f),
579 None => super::util::format_number_opt(f, self.locale.as_ref()),
580 })
581 }
582
583 fn aesthetic(&self) -> Aesthetic {
584 self.aesthetic.clone()
585 }
586
587 fn train(&mut self, values: &[Value]) {
588 if self.limits.is_some() {
589 return;
590 }
591 for v in values {
592 if let Some(f) = v.as_f64() {
593 if f.is_finite() {
594 if f < self.min {
595 self.min = f;
596 }
597 if f > self.max {
598 self.max = f;
599 }
600 }
601 }
602 }
603 }
604
605 fn map(&self, value: &Value) -> f64 {
606 let f = match value.as_f64() {
607 Some(f) => f,
608 None => return 0.0,
609 };
610 let (lo, hi) = self.limits.unwrap_or((self.min, self.max));
611 let range = hi - lo;
612 if range.abs() < f64::EPSILON {
613 0.5
614 } else {
615 (f - lo) / range
616 }
617 }
618
619 fn breaks(&self) -> Vec<(f64, String)> {
620 let (lo, hi) = self.limits.unwrap_or((self.min, self.max));
621 if lo > hi || !lo.is_finite() || !hi.is_finite() {
622 return vec![];
623 }
624
625 let range = hi - lo;
626 if super::util::is_degenerate_range(lo, hi) {
627 return vec![(
628 0.5,
629 super::util::format_number_opt(lo, self.locale.as_ref()),
630 )];
631 }
632
633 let n_breaks = 5;
634 let raw_step = range / n_breaks as f64;
635 let step = nice_step(raw_step);
636
637 let start = (lo / step).ceil() * step;
638 let values = super::util::stepped_breaks(start, hi, step);
639 let labels = super::util::format_numbers_opt(&values, self.locale.as_ref());
640 values
641 .iter()
642 .zip(labels)
643 .map(|(&v, l)| (self.map(&Value::Float(v)), l))
644 .collect()
645 }
646
647 fn name(&self) -> &str {
648 &self.name
649 }
650
651 fn set_name(&mut self, name: &str) {
652 self.name = name.to_string();
653 }
654
655 fn map_to_color(&self, value: &Value) -> Option<(u8, u8, u8)> {
656 if value.is_na() {
665 return Some(self.na_value);
666 }
667 if let Some(f) = value.as_f64() {
668 if !f.is_finite() {
669 return Some(self.na_value);
670 }
671 if super::resolve_oob(f, self.limits, self.oob).is_none() {
672 return Some(self.na_value);
673 }
674 }
675 let t = self.map(value);
676 let c = self.color_at(t);
677 Some((c.r, c.g, c.b))
678 }
679
680 fn domain(&self) -> Option<(f64, f64)> {
681 if let Some((lo, hi)) = self.limits {
682 return Some((lo, hi));
683 }
684 if self.min.is_finite() && self.max.is_finite() && self.min <= self.max {
685 Some((self.min, self.max))
686 } else {
687 None
688 }
689 }
690
691 fn clone_box(&self) -> Box<dyn Scale> {
692 Box::new(self.clone())
693 }
694
695 fn reset_training(&mut self) {
696 self.min = f64::INFINITY;
697 self.max = f64::NEG_INFINITY;
698 }
699
700 fn validate(&self, warnings: &mut Vec<String>) {
701 if let Some((lo, hi)) = self.limits {
702 if lo > hi {
703 warnings.push(format!(
704 "scale_color/fill_gradient: with_limits({lo}, {hi}) has lo > hi; every value maps to na_value"
705 ));
706 }
707 }
708 }
709}
710
711#[cfg(test)]
712mod tests {
713 use super::*;
714 use crate::aes::Aesthetic;
715
716 #[test]
717 fn continuous_format_value_rejects_non_finite() {
718 let s = ScaleColorContinuous::new(Aesthetic::Fill);
719 assert_eq!(s.format_value(&Value::Float(3.5)), Some("3.5".to_string()));
720 assert_eq!(s.format_value(&Value::Float(f64::NAN)), None);
721 assert_eq!(s.format_value(&Value::Na), None);
722 }
723
724 #[test]
725 fn continuous_with_value_formatter_overrides_format_value() {
726 let s =
727 ScaleColorContinuous::new(Aesthetic::Fill).with_value_formatter(|v| format!("${v:.2}"));
728 assert_eq!(
729 s.format_value(&Value::Float(1234.5)),
730 Some("$1234.50".to_string())
731 );
732 assert_eq!(s.format_value(&Value::Float(f64::NAN)), None);
734 }
735
736 #[test]
737 fn continuous_debug_reports_whether_a_formatter_is_set() {
738 let without = format!("{:?}", ScaleColorContinuous::new(Aesthetic::Fill));
739 assert!(without.contains("ScaleColorContinuous"));
740 assert!(without.contains("value_formatter: false"));
741 let with = format!(
742 "{:?}",
743 ScaleColorContinuous::new(Aesthetic::Fill).with_value_formatter(|v| v.to_string())
744 );
745 assert!(with.contains("value_formatter: true"));
746 }
747
748 #[test]
753 fn scale_color_continuous_keeps_every_auto_trait() {
754 fn assert_traits<
755 T: Send + Sync + std::panic::UnwindSafe + std::panic::RefUnwindSafe + Clone,
756 >() {
757 }
758 assert_traits::<ScaleColorContinuous>();
759 }
760}
761
762#[cfg(test)]
763mod hue_tests {
764 use super::*;
765
766 fn hex(c: RGBAColor) -> String {
767 format!("#{:02X}{:02X}{:02X}", c.r, c.g, c.b)
768 }
769
770 #[test]
771 fn hue_pal_matches_scales() {
772 let d = HuePalette::default();
774 let got: Vec<String> = hue_palette(2, &d).into_iter().map(hex).collect();
775 assert_eq!(got, ["#F8766D", "#00BFC4"]);
776 let got: Vec<String> = hue_palette(5, &d).into_iter().map(hex).collect();
777 assert_eq!(got, ["#F8766D", "#A3A500", "#00BF7D", "#00B0F6", "#E76BF3"]);
778 let p = HuePalette {
779 h: (0.0, 180.0),
780 c: 50.0,
781 l: 80.0,
782 h_start: 30.0,
783 direction: -1,
784 };
785 let got: Vec<String> = hue_palette(4, &p).into_iter().map(hex).collect();
786 assert_eq!(got, ["#70D5E7", "#7BD9AD", "#C6CC82", "#F7B8A1"]);
787 }
788
789 #[test]
790 fn default_discrete_scale_is_hue_pal() {
791 let mut s = ScaleColorDiscrete::new(Aesthetic::Color);
794 s = s.with_levels(["a", "b", "c", "d", "e"].map(String::from).to_vec());
795 let got: Vec<String> = (0..5).map(|i| hex(s.color_for_index(i))).collect();
796 assert_eq!(got, ["#F8766D", "#A3A500", "#00BF7D", "#00B0F6", "#E76BF3"]);
797 let g = GeneratedPalette::Hue(HuePalette {
799 direction: -1,
800 ..HuePalette::default()
801 });
802 let all = g.colors(4);
803 assert!((0..4).all(|i| hex(g.color_at(i, 4)) == hex(all[i])));
804 }
805
806 #[test]
807 fn viridis_d_is_close_to_scales() {
808 let want = [
811 (0x44, 0x01, 0x54),
812 (0x31, 0x68, 0x8E),
813 (0x35, 0xB7, 0x79),
814 (0xFD, 0xE7, 0x25),
815 ];
816 let got = GeneratedPalette::Viridis {
817 option: 'D',
818 begin: 0.0,
819 end: 1.0,
820 direction: 1,
821 }
822 .colors(4);
823 for (g, w) in got.iter().zip(want) {
824 for (a, b) in [(g.r, w.0), (g.g, w.1), (g.b, w.2)] {
825 assert!((a as i32 - b).abs() <= 1, "{} vs {w:?}", hex(*g));
826 }
827 }
828 let want = [(0xFE, 0x9F, 0x6D), (0xB6, 0x36, 0x79), (0x3B, 0x0F, 0x70)];
830 let got = GeneratedPalette::Viridis {
831 option: 'A',
832 begin: 0.2,
833 end: 0.8,
834 direction: -1,
835 }
836 .colors(3);
837 for (g, w) in got.iter().zip(want) {
838 for (a, b) in [(g.r, w.0), (g.g, w.1), (g.b, w.2)] {
839 assert!((a as i32 - b).abs() <= 1, "{} vs {w:?}", hex(*g));
840 }
841 }
842 }
843}