1use crate::style::{HSLColor, RGBAColor, RGBColor};
2
3use num_traits::{Float, FromPrimitive, ToPrimitive};
4
5/// Converts scalar values to colors.
6pub trait ColorMap<ColorType: crate::prelude::Color, FloatType = f32>
7where
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/// ```
36pub struct DerivedColorMap<ColorType> {
37 colors: Vec<ColorType>,
38}
39
40impl<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
50macro_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
84fn 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
106macro_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
135implement_color_scale_for_derived_color_map! {RGBAColor, RGBColor, HSLColor}
136
137macro_rules! count {
138 () => (0usize);
139 ($x:tt $($xs:tt)* ) => (1usize + count!($($xs)*));
140}
141
142macro_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
151macro_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.
213macro_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
238define_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
254define_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
270define_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
278define_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
286define_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
294use super::full_palette::*;
295define_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
304define_linear_interpolation_color_map! {
305 Copper,
306 RGBColor,
307 "Friendly black to brown colormap.",
308 BLACK,
309 BROWN,
310 ORANGE
311}
312