| 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 | |