1 | use crate::style::{HSLColor, RGBAColor, RGBColor}; |
2 | |
3 | use num_traits::{Float, FromPrimitive, ToPrimitive}; |
4 | |
5 | /// Converts scalar values to colors. |
6 | pub trait ColorMap<ColorType: crate::prelude::Color, FloatType = f32> |
7 | where |
8 | FloatType: Float, |
9 | { |
10 | /// Takes a scalar value 0.0 <= h <= 1.0 and returns the corresponding color. |
11 | /// Typically color-scales are named according to which color-type they return. |
12 | /// To use upper and lower bounds with ths function see [get_color_normalized](ColorMap::get_color_normalized). |
13 | fn get_color(&self, h: FloatType) -> ColorType { |
14 | self.get_color_normalized(h, FloatType::zero(), FloatType::one()) |
15 | } |
16 | |
17 | /// A slight abstraction over [get_color](ColorMap::get_color) function where lower and upper bound can be specified. |
18 | fn get_color_normalized(&self, h: FloatType, min: FloatType, max: FloatType) -> ColorType; |
19 | } |
20 | |
21 | /// This struct is used to dynamically construct colormaps by giving it a slice of colors. |
22 | /// It can then be used when being intantiated, but not with associated functions. |
23 | /// ``` |
24 | /// use plotters::prelude::{BLACK,BLUE,WHITE,DerivedColorMap,ColorMap}; |
25 | /// |
26 | /// let derived_colormap = DerivedColorMap::new( |
27 | /// &[BLACK, |
28 | /// BLUE, |
29 | /// WHITE] |
30 | /// ); |
31 | /// |
32 | /// assert_eq!(derived_colormap.get_color(0.0), BLACK); |
33 | /// assert_eq!(derived_colormap.get_color(0.5), BLUE); |
34 | /// assert_eq!(derived_colormap.get_color(1.0), WHITE); |
35 | /// ``` |
36 | pub struct DerivedColorMap<ColorType> { |
37 | colors: Vec<ColorType>, |
38 | } |
39 | |
40 | impl<ColorType: crate::style::Color + Clone> DerivedColorMap<ColorType> { |
41 | /// This function lets the user define a new colormap by simply specifying colors in the correct order. |
42 | /// For calculation of the color values, the colors will be spaced evenly apart. |
43 | pub fn new(colors: &[ColorType]) -> Self { |
44 | DerivedColorMap { |
45 | colors: colors.to_vec(), |
46 | } |
47 | } |
48 | } |
49 | |
50 | macro_rules! calculate_new_color_value( |
51 | ($relative_difference:expr, $colors:expr, $index_upper:expr, $index_lower:expr, RGBColor) => { |
52 | RGBColor( |
53 | // These equations are a very complicated way of writing a simple linear extrapolation with lots of casting between numerical values |
54 | // In principle every cast should be safe which is why we choose to unwrap |
55 | // (1.0 - r) * color_value_1 + r * color_value_2 |
56 | ((FloatType::one() - $relative_difference) * FloatType::from_u8($colors[$index_upper].0).unwrap() + $relative_difference * FloatType::from_u8($colors[$index_lower].0).unwrap()).round().to_u8().unwrap(), |
57 | ((FloatType::one() - $relative_difference) * FloatType::from_u8($colors[$index_upper].1).unwrap() + $relative_difference * FloatType::from_u8($colors[$index_lower].1).unwrap()).round().to_u8().unwrap(), |
58 | ((FloatType::one() - $relative_difference) * FloatType::from_u8($colors[$index_upper].2).unwrap() + $relative_difference * FloatType::from_u8($colors[$index_lower].2).unwrap()).round().to_u8().unwrap() |
59 | ) |
60 | }; |
61 | ($relative_difference:expr, $colors:expr, $index_upper:expr, $index_lower:expr, RGBAColor) => { |
62 | RGBAColor( |
63 | // These equations are a very complicated way of writing a simple linear extrapolation with lots of casting between numerical values |
64 | // In principle every cast should be safe which is why we choose to unwrap |
65 | // (1.0 - r) * color_value_1 + r * color_value_2 |
66 | ((FloatType::one() - $relative_difference) * FloatType::from_u8($colors[$index_upper].0).unwrap() + $relative_difference * FloatType::from_u8($colors[$index_lower].0).unwrap()).round().to_u8().unwrap(), |
67 | ((FloatType::one() - $relative_difference) * FloatType::from_u8($colors[$index_upper].1).unwrap() + $relative_difference * FloatType::from_u8($colors[$index_lower].1).unwrap()).round().to_u8().unwrap(), |
68 | ((FloatType::one() - $relative_difference) * FloatType::from_u8($colors[$index_upper].2).unwrap() + $relative_difference * FloatType::from_u8($colors[$index_lower].2).unwrap()).round().to_u8().unwrap(), |
69 | ((FloatType::one() - $relative_difference) * FloatType::from_f64($colors[$index_upper].3).unwrap() + $relative_difference * FloatType::from_f64($colors[$index_lower].3).unwrap()).to_f64().unwrap() |
70 | ) |
71 | }; |
72 | ($relative_difference:expr, $colors:expr, $index_upper:expr, $index_lower:expr, HSLColor) => { |
73 | HSLColor( |
74 | // These equations are a very complicated way of writing a simple linear extrapolation with lots of casting between numerical values |
75 | // In principle every cast should be safe which is why we choose to unwrap |
76 | // (1.0 - r) * color_value_1 + r * color_value_2 |
77 | ((FloatType::one() - $relative_difference) * FloatType::from_f64($colors[$index_upper].0).unwrap() + $relative_difference * FloatType::from_f64($colors[$index_lower].0).unwrap()).to_f64().unwrap(), |
78 | ((FloatType::one() - $relative_difference) * FloatType::from_f64($colors[$index_upper].1).unwrap() + $relative_difference * FloatType::from_f64($colors[$index_lower].1).unwrap()).to_f64().unwrap(), |
79 | ((FloatType::one() - $relative_difference) * FloatType::from_f64($colors[$index_upper].2).unwrap() + $relative_difference * FloatType::from_f64($colors[$index_lower].2).unwrap()).to_f64().unwrap(), |
80 | ) |
81 | }; |
82 | ); |
83 | |
84 | fn calculate_relative_difference_index_lower_upper< |
85 | FloatType: Float + FromPrimitive + ToPrimitive, |
86 | >( |
87 | h: FloatType, |
88 | min: FloatType, |
89 | max: FloatType, |
90 | n_colors: usize, |
91 | ) -> (FloatType, usize, usize) { |
92 | // Ensure that we do have a value in bounds |
93 | let h = num_traits::clamp(h, min, max); |
94 | // Next calculate a normalized value between 0.0 and 1.0 |
95 | let t = (h - min) / (max - min); |
96 | let approximate_index = |
97 | t * (FloatType::from_usize(n_colors).unwrap() - FloatType::one()).max(FloatType::zero()); |
98 | // Calculate which index are the two most nearest of the supplied value |
99 | let index_lower = approximate_index.floor().to_usize().unwrap(); |
100 | let index_upper = approximate_index.ceil().to_usize().unwrap(); |
101 | // Calculate the relative difference, ie. is the actual value more towards the color of index_upper or index_lower? |
102 | let relative_difference = approximate_index.ceil() - approximate_index; |
103 | (relative_difference, index_lower, index_upper) |
104 | } |
105 | |
106 | macro_rules! implement_color_scale_for_derived_color_map{ |
107 | ($($color_type:ident),+) => { |
108 | $( |
109 | impl<FloatType: Float + FromPrimitive + ToPrimitive> ColorMap<$color_type, FloatType> for DerivedColorMap<$color_type> { |
110 | fn get_color_normalized(&self, h: FloatType, min: FloatType, max: FloatType) -> $color_type { |
111 | let ( |
112 | relative_difference, |
113 | index_lower, |
114 | index_upper |
115 | ) = calculate_relative_difference_index_lower_upper( |
116 | h, |
117 | min, |
118 | max, |
119 | self.colors.len() |
120 | ); |
121 | // Interpolate the final color linearly |
122 | calculate_new_color_value!( |
123 | relative_difference, |
124 | self.colors, |
125 | index_upper, |
126 | index_lower, |
127 | $color_type |
128 | ) |
129 | } |
130 | } |
131 | )+ |
132 | } |
133 | } |
134 | |
135 | implement_color_scale_for_derived_color_map! {RGBAColor, RGBColor, HSLColor} |
136 | |
137 | macro_rules! count { |
138 | () => (0usize); |
139 | ($x:tt $($xs:tt)* ) => (1usize + count!($($xs)*)); |
140 | } |
141 | |
142 | macro_rules! define_colors_from_list_of_values_or_directly{ |
143 | ($color_type:ident, $(($($color_value:expr),+)),+) => { |
144 | [$($color_type($($color_value),+)),+] |
145 | }; |
146 | ($($color_complete:tt),+) => { |
147 | [$($color_complete),+] |
148 | }; |
149 | } |
150 | |
151 | macro_rules! implement_linear_interpolation_color_map { |
152 | ($color_scale_name:ident, $color_type:ident) => { |
153 | impl<FloatType: std::fmt::Debug + Float + FromPrimitive + ToPrimitive> |
154 | ColorMap<$color_type, FloatType> for $color_scale_name |
155 | { |
156 | fn get_color_normalized( |
157 | &self, |
158 | h: FloatType, |
159 | min: FloatType, |
160 | max: FloatType, |
161 | ) -> $color_type { |
162 | let ( |
163 | relative_difference, |
164 | index_lower, |
165 | index_upper |
166 | ) = calculate_relative_difference_index_lower_upper( |
167 | h, |
168 | min, |
169 | max, |
170 | Self::COLORS.len() |
171 | ); |
172 | // Interpolate the final color linearly |
173 | calculate_new_color_value!( |
174 | relative_difference, |
175 | Self::COLORS, |
176 | index_upper, |
177 | index_lower, |
178 | $color_type |
179 | ) |
180 | } |
181 | } |
182 | |
183 | impl $color_scale_name { |
184 | #[doc = "Get color value from `" ] |
185 | #[doc = stringify!($color_scale_name)] |
186 | #[doc = "` by supplying a parameter 0.0 <= h <= 1.0" ] |
187 | pub fn get_color<FloatType: std::fmt::Debug + Float + FromPrimitive + ToPrimitive>( |
188 | h: FloatType, |
189 | ) -> $color_type { |
190 | let color_scale = $color_scale_name {}; |
191 | color_scale.get_color(h) |
192 | } |
193 | |
194 | #[doc = "Get color value from `" ] |
195 | #[doc = stringify!($color_scale_name)] |
196 | #[doc = "` by supplying lower and upper bounds min, max and a parameter h where min <= h <= max" ] |
197 | pub fn get_color_normalized< |
198 | FloatType: std::fmt::Debug + Float + FromPrimitive + ToPrimitive, |
199 | >( |
200 | h: FloatType, |
201 | min: FloatType, |
202 | max: FloatType, |
203 | ) -> $color_type { |
204 | let color_scale = $color_scale_name {}; |
205 | color_scale.get_color_normalized(h, min, max) |
206 | } |
207 | } |
208 | }; |
209 | } |
210 | |
211 | #[macro_export ] |
212 | /// Macro to create a new colormap with evenly spaced colors at compile-time. |
213 | macro_rules! define_linear_interpolation_color_map{ |
214 | ($color_scale_name:ident, $color_type:ident, $doc:expr, $(($($color_value:expr),+)),*) => { |
215 | #[doc = $doc] |
216 | pub struct $color_scale_name {} |
217 | |
218 | impl $color_scale_name { |
219 | // const COLORS: [$color_type; $number_colors] = [$($color_type($($color_value),+)),+]; |
220 | // const COLORS: [$color_type; count!($(($($color_value:expr),+))*)] = [$($color_type($($color_value),+)),+]; |
221 | const COLORS: [$color_type; count!($(($($color_value:expr),+))*)] = define_colors_from_list_of_values_or_directly!{$color_type, $(($($color_value),+)),*}; |
222 | } |
223 | |
224 | implement_linear_interpolation_color_map!{$color_scale_name, $color_type} |
225 | }; |
226 | ($color_scale_name:ident, $color_type:ident, $doc:expr, $($color_complete:tt),+) => { |
227 | #[doc = $doc] |
228 | pub struct $color_scale_name {} |
229 | |
230 | impl $color_scale_name { |
231 | const COLORS: [$color_type; count!($($color_complete)*)] = define_colors_from_list_of_values_or_directly!{$($color_complete),+}; |
232 | } |
233 | |
234 | implement_linear_interpolation_color_map!{$color_scale_name, $color_type} |
235 | } |
236 | } |
237 | |
238 | define_linear_interpolation_color_map! { |
239 | ViridisRGBA, |
240 | RGBAColor, |
241 | "A colormap optimized for visually impaired people (RGBA format). |
242 | It is currently the default colormap also used by [matplotlib](https://matplotlib.org/stable/tutorials/colors/colormaps.html). |
243 | Read more in this [paper](https://doi.org/10.1371/journal.pone.0199239)" , |
244 | ( 68, 1, 84, 1.0), |
245 | ( 70, 50, 127, 1.0), |
246 | ( 54, 92, 141, 1.0), |
247 | ( 39, 127, 143, 1.0), |
248 | ( 31, 162, 136, 1.0), |
249 | ( 74, 194, 110, 1.0), |
250 | (160, 219, 57, 1.0), |
251 | (254, 232, 37, 1.0) |
252 | } |
253 | |
254 | define_linear_interpolation_color_map! { |
255 | ViridisRGB, |
256 | RGBColor, |
257 | "A colormap optimized for visually impaired people (RGB Format). |
258 | It is currently the default colormap also used by [matplotlib](https://matplotlib.org/stable/tutorials/colors/colormaps.html). |
259 | Read more in this [paper](https://doi.org/10.1371/journal.pone.0199239)" , |
260 | ( 68, 1, 84), |
261 | ( 70, 50, 127), |
262 | ( 54, 92, 141), |
263 | ( 39, 127, 143), |
264 | ( 31, 162, 136), |
265 | ( 74, 194, 110), |
266 | (160, 219, 57), |
267 | (254, 232, 37) |
268 | } |
269 | |
270 | define_linear_interpolation_color_map! { |
271 | BlackWhite, |
272 | RGBColor, |
273 | "Simple chromatic colormap from black to white." , |
274 | ( 0, 0, 0), |
275 | (255, 255, 255) |
276 | } |
277 | |
278 | define_linear_interpolation_color_map! { |
279 | MandelbrotHSL, |
280 | HSLColor, |
281 | "Colormap created to replace the one used in the mandelbrot example." , |
282 | (0.0, 1.0, 0.5), |
283 | (1.0, 1.0, 0.5) |
284 | } |
285 | |
286 | define_linear_interpolation_color_map! { |
287 | VulcanoHSL, |
288 | HSLColor, |
289 | "A vulcanic colormap that display red/orange and black colors" , |
290 | (2.0/3.0, 1.0, 0.7), |
291 | ( 0.0, 1.0, 0.7) |
292 | } |
293 | |
294 | use super::full_palette::*; |
295 | define_linear_interpolation_color_map! { |
296 | Bone, |
297 | RGBColor, |
298 | "Dark colormap going from black over blue to white." , |
299 | BLACK, |
300 | BLUE, |
301 | WHITE |
302 | } |
303 | |
304 | define_linear_interpolation_color_map! { |
305 | Copper, |
306 | RGBColor, |
307 | "Friendly black to brown colormap." , |
308 | BLACK, |
309 | BROWN, |
310 | ORANGE |
311 | } |
312 | |