ggplot_rs/scale/
gradient.rs1use crate::aes::Aesthetic;
2use crate::data::Value;
3
4use super::color::RGBAColor;
5use super::util::nice_step;
6use super::Scale;
7
8#[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 locale: Option<crate::locale::Locale>,
21}
22
23impl ScaleColorGradient2 {
24 pub fn new(aesthetic: Aesthetic) -> Self {
25 ScaleColorGradient2 {
26 aesthetic,
27 name: String::new(),
28 low: RGBAColor::new(0, 0, 255),
29 mid: RGBAColor::new(255, 255, 255),
30 high: RGBAColor::new(255, 0, 0),
31 midpoint: 0.0,
32 min: f64::INFINITY,
33 max: f64::NEG_INFINITY,
34 locale: None,
35 }
36 }
37
38 pub fn with_colors(mut self, low: RGBAColor, mid: RGBAColor, high: RGBAColor) -> Self {
39 self.low = low;
40 self.mid = mid;
41 self.high = high;
42 self
43 }
44
45 pub fn with_midpoint(mut self, midpoint: f64) -> Self {
46 self.midpoint = midpoint;
47 self
48 }
49}
50
51impl Scale for ScaleColorGradient2 {
52 fn set_default_locale(&mut self, locale: &crate::locale::Locale) {
53 if self.locale.is_none() {
54 self.locale = Some(locale.clone());
55 }
56 }
57
58 fn locale(&self) -> Option<&crate::locale::Locale> {
59 self.locale.as_ref()
60 }
61
62 fn aesthetic(&self) -> Aesthetic {
63 self.aesthetic.clone()
64 }
65
66 fn train(&mut self, values: &[Value]) {
67 for v in values {
68 if let Some(f) = v.as_f64() {
69 if f.is_finite() {
70 if f < self.min {
71 self.min = f;
72 }
73 if f > self.max {
74 self.max = f;
75 }
76 }
77 }
78 }
79 }
80
81 fn map(&self, value: &Value) -> f64 {
82 let f = match value.as_f64() {
83 Some(f) => f,
84 None => return 0.0,
85 };
86 let range = self.max - self.min;
87 if range.abs() < f64::EPSILON {
88 0.5
89 } else {
90 (f - self.min) / range
91 }
92 }
93
94 fn breaks(&self) -> Vec<(f64, String)> {
95 if self.min > self.max || !self.min.is_finite() || !self.max.is_finite() {
96 return vec![];
97 }
98 let range = self.max - self.min;
99 if super::util::is_degenerate_range(self.min, self.max) {
100 return vec![(
101 0.5,
102 super::util::format_number_opt(self.min, self.locale.as_ref()),
103 )];
104 }
105 let n_breaks = 5;
106 let raw_step = range / n_breaks as f64;
107 let step = nice_step(raw_step);
108 let start = (self.min / step).ceil() * step;
109 let values = super::util::stepped_breaks(start, self.max, step);
110 let labels = super::util::format_numbers_opt(&values, self.locale.as_ref());
111 values
112 .iter()
113 .zip(labels)
114 .map(|(&v, l)| (self.map(&Value::Float(v)), l))
115 .collect()
116 }
117
118 fn name(&self) -> &str {
119 &self.name
120 }
121
122 fn set_name(&mut self, name: &str) {
123 self.name = name.to_string();
124 }
125
126 fn map_to_color(&self, value: &Value) -> Option<(u8, u8, u8)> {
127 let f = value.as_f64()?;
128
129 let c = if f <= self.midpoint {
131 let range = self.midpoint - self.min;
132 let t = if range.abs() < f64::EPSILON {
133 0.0
134 } else {
135 (f - self.min) / range
136 };
137 self.low.lerp(&self.mid, t)
138 } else {
139 let range = self.max - self.midpoint;
140 let t = if range.abs() < f64::EPSILON {
141 1.0
142 } else {
143 (f - self.midpoint) / range
144 };
145 self.mid.lerp(&self.high, t)
146 };
147
148 Some((c.r, c.g, c.b))
149 }
150
151 fn domain(&self) -> Option<(f64, f64)> {
152 if self.min.is_finite() && self.max.is_finite() && self.min <= self.max {
153 Some((self.min, self.max))
154 } else {
155 None
156 }
157 }
158
159 fn clone_box(&self) -> Box<dyn Scale> {
160 Box::new(self.clone())
161 }
162
163 fn reset_training(&mut self) {
164 self.min = f64::INFINITY;
165 self.max = f64::NEG_INFINITY;
166 }
167}