1use plotters::style::RGBColor;
14
15#[derive(Debug, Clone)]
17pub struct GradientColorMap {
18 stops: Vec<(f64, RGBColor)>,
20}
21
22impl GradientColorMap {
23 pub fn new(mut stops: Vec<(f64, RGBColor)>) -> Self {
27 if stops.is_empty() {
28 stops.push((0.0, RGBColor(128, 128, 128)));
29 }
30 for s in &mut stops {
31 s.0 = s.0.clamp(0.0, 1.0);
32 }
33 stops.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
34 Self { stops }
35 }
36
37 pub fn color(&self, t: f64) -> RGBColor {
39 let t = t.clamp(0.0, 1.0);
40 if t <= self.stops[0].0 {
41 return self.stops[0].1;
42 }
43 let last = self.stops.len() - 1;
44 if t >= self.stops[last].0 {
45 return self.stops[last].1;
46 }
47 let mut hi = 1;
49 while hi < self.stops.len() && self.stops[hi].0 < t {
50 hi += 1;
51 }
52 let (p0, c0) = self.stops[hi - 1];
53 let (p1, c1) = self.stops[hi];
54 let f = if p1 > p0 { (t - p0) / (p1 - p0) } else { 0.0 };
55 RGBColor(
56 lerp_u8(c0.0, c1.0, f),
57 lerp_u8(c0.1, c1.1, f),
58 lerp_u8(c0.2, c1.2, f),
59 )
60 }
61
62 pub fn viridis() -> Self {
64 Self::new(vec![
65 (0.0, RGBColor(68, 1, 84)),
66 (0.125, RGBColor(72, 40, 120)),
67 (0.25, RGBColor(62, 74, 137)),
68 (0.375, RGBColor(49, 104, 142)),
69 (0.5, RGBColor(38, 130, 142)),
70 (0.625, RGBColor(31, 158, 137)),
71 (0.75, RGBColor(53, 183, 121)),
72 (0.875, RGBColor(110, 206, 88)),
73 (1.0, RGBColor(253, 231, 37)),
74 ])
75 }
76
77 pub fn magma() -> Self {
79 Self::new(vec![
80 (0.0, RGBColor(0, 0, 4)),
81 (0.25, RGBColor(81, 18, 124)),
82 (0.5, RGBColor(183, 55, 121)),
83 (0.75, RGBColor(252, 137, 97)),
84 (1.0, RGBColor(252, 253, 191)),
85 ])
86 }
87
88 pub fn blues() -> Self {
90 Self::new(vec![
91 (0.0, RGBColor(247, 251, 255)),
92 (0.25, RGBColor(198, 219, 239)),
93 (0.5, RGBColor(107, 174, 214)),
94 (0.75, RGBColor(33, 113, 181)),
95 (1.0, RGBColor(8, 48, 107)),
96 ])
97 }
98
99 pub fn reds() -> Self {
101 Self::new(vec![
102 (0.0, RGBColor(255, 245, 240)),
103 (0.25, RGBColor(252, 187, 161)),
104 (0.5, RGBColor(251, 106, 74)),
105 (0.75, RGBColor(203, 24, 29)),
106 (1.0, RGBColor(103, 0, 13)),
107 ])
108 }
109
110 pub fn rd_bu() -> Self {
113 Self::new(vec![
114 (0.0, RGBColor(178, 24, 43)),
115 (0.25, RGBColor(239, 138, 98)),
116 (0.5, RGBColor(247, 247, 247)),
117 (0.75, RGBColor(103, 169, 207)),
118 (1.0, RGBColor(33, 102, 172)),
119 ])
120 }
121
122 pub fn coolwarm() -> Self {
124 Self::new(vec![
125 (0.0, RGBColor(59, 76, 192)),
126 (0.5, RGBColor(221, 221, 221)),
127 (1.0, RGBColor(180, 4, 38)),
128 ])
129 }
130
131 pub fn grayscale() -> Self {
133 Self::new(vec![
134 (0.0, RGBColor(255, 255, 255)),
135 (1.0, RGBColor(0, 0, 0)),
136 ])
137 }
138}
139
140impl Default for GradientColorMap {
141 fn default() -> Self {
142 Self::viridis()
143 }
144}
145
146fn lerp_u8(a: u8, b: u8, f: f64) -> u8 {
147 (a as f64 + (b as f64 - a as f64) * f)
148 .round()
149 .clamp(0.0, 255.0) as u8
150}
151
152#[derive(Debug, Clone, Copy, PartialEq)]
154pub enum Normalization {
155 Linear {
157 min: f64,
159 max: f64,
161 },
162 Symmetric {
165 center: f64,
167 half: f64,
169 },
170}
171
172impl Normalization {
173 pub fn t(&self, v: f64) -> f64 {
176 if !v.is_finite() {
177 return f64::NAN;
178 }
179 match *self {
180 Normalization::Linear { min, max } => {
181 if max > min {
182 ((v - min) / (max - min)).clamp(0.0, 1.0)
183 } else {
184 0.5
185 }
186 }
187 Normalization::Symmetric { center, half } => {
188 if half > 0.0 {
189 (0.5 + (v - center) / (2.0 * half)).clamp(0.0, 1.0)
190 } else {
191 0.5
192 }
193 }
194 }
195 }
196
197 pub fn value(&self, t: f64) -> f64 {
200 match *self {
201 Normalization::Linear { min, max } => min + t * (max - min),
202 Normalization::Symmetric { center, half } => center + (2.0 * t - 1.0) * half,
203 }
204 }
205}