Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable macro usage for users to define colormaps #481

Merged
merged 7 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions plotters/blub.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
119 changes: 89 additions & 30 deletions plotters/src/style/colors/colormaps.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
use crate::style::{HSLColor, RGBAColor, RGBColor};

use num_traits::{Float, FromPrimitive, ToPrimitive};

/// Converts scalar values to colors.
pub trait ColorMap<ColorType: crate::prelude::Color, FloatType = f32>
where
FloatType: Float,
FloatType: num_traits::Float,
{
/// Takes a scalar value 0.0 <= h <= 1.0 and returns the corresponding color.
/// Typically color-scales are named according to which color-type they return.
Expand Down Expand Up @@ -47,6 +45,8 @@ impl<ColorType: crate::style::Color + Clone> DerivedColorMap<ColorType> {
}
}

#[macro_export]
#[doc(hidden)]
macro_rules! calculate_new_color_value(
($relative_difference:expr, $colors:expr, $index_upper:expr, $index_lower:expr, RGBColor) => {
RGBColor(
Expand Down Expand Up @@ -81,20 +81,34 @@ macro_rules! calculate_new_color_value(
};
);

fn calculate_relative_difference_index_lower_upper<
FloatType: Float + FromPrimitive + ToPrimitive,
// Helper function to calculate the lower and upper index nearest to a provided float value.
//
// Used to obtain colors from a colorscale given a value h between 0.0 and 1.0.
// It also returns the relative difference which can then be used to calculate a linear interpolation between the two nearest colors.
// ```
// # use plotters::prelude::*;
// let r = calculate_relative_difference_index_lower_upper(1.2, 1.0, 3.0, 4);
// let (relative_difference, lower_index, upper_index) = r;
//
// assert_eq!(relative_difference, 0.7000000000000001);
// assert_eq!(lower_index, 0);
// assert_eq!(upper_index, 1);
// ```
#[doc(hidden)]
pub fn calculate_relative_difference_index_lower_upper<
FloatType: num_traits::Float + num_traits::FromPrimitive + num_traits::ToPrimitive,
>(
h: FloatType,
min: FloatType,
max: FloatType,
n_colors: usize,
n_steps: usize,
) -> (FloatType, usize, usize) {
// Ensure that we do have a value in bounds
let h = num_traits::clamp(h, min, max);
// Next calculate a normalized value between 0.0 and 1.0
let t = (h - min) / (max - min);
let approximate_index =
t * (FloatType::from_usize(n_colors).unwrap() - FloatType::one()).max(FloatType::zero());
t * (FloatType::from_usize(n_steps).unwrap() - FloatType::one()).max(FloatType::zero());
// Calculate which index are the two most nearest of the supplied value
let index_lower = approximate_index.floor().to_usize().unwrap();
let index_upper = approximate_index.ceil().to_usize().unwrap();
Expand All @@ -106,7 +120,7 @@ fn calculate_relative_difference_index_lower_upper<
macro_rules! implement_color_scale_for_derived_color_map{
($($color_type:ident),+) => {
$(
impl<FloatType: Float + FromPrimitive + ToPrimitive> ColorMap<$color_type, FloatType> for DerivedColorMap<$color_type> {
impl<FloatType: num_traits::Float + num_traits::FromPrimitive + num_traits::ToPrimitive> ColorMap<$color_type, FloatType> for DerivedColorMap<$color_type> {
fn get_color_normalized(&self, h: FloatType, min: FloatType, max: FloatType) -> $color_type {
let (
relative_difference,
Expand All @@ -119,7 +133,7 @@ macro_rules! implement_color_scale_for_derived_color_map{
self.colors.len()
);
// Interpolate the final color linearly
calculate_new_color_value!(
$crate::calculate_new_color_value!(
relative_difference,
self.colors,
index_upper,
Expand All @@ -134,11 +148,30 @@ macro_rules! implement_color_scale_for_derived_color_map{

implement_color_scale_for_derived_color_map! {RGBAColor, RGBColor, HSLColor}

#[macro_export]
#[doc(hidden)]
// Counts the number of arguments which are separated by spaces
//
// This macro is used internally to determine the size of an array to hold all new colors.
// ```
// # use plotters::count;
// let counted = count!{Plotting is fun};
// assert_eq!(counted, 3);
//
// let counted2 = count!{0_usize was my favourite 1_f64 last century};
// assert_eq!(counted2, 7);
//
// let new_array = ["Hello"; count!(Plotting is fun)];
// assert_eq!(new_array, ["Hello"; 3]);
// ```
macro_rules! count {
() => (0usize);
($x:tt $($xs:tt)* ) => (1usize + count!($($xs)*));
($x:tt $($xs:tt)* ) => (1usize + $crate::count!($($xs)*));
}

#[macro_export]
#[doc(hidden)]
/// Converts a given color identifier and a sequence of colors to an array of them.
macro_rules! define_colors_from_list_of_values_or_directly{
($color_type:ident, $(($($color_value:expr),+)),+) => {
[$($color_type($($color_value),+)),+]
Expand All @@ -148,9 +181,12 @@ macro_rules! define_colors_from_list_of_values_or_directly{
};
}

#[macro_export]
#[doc(hidden)]
/// Implements the [ColorMap] trait on a given color scale.
macro_rules! implement_linear_interpolation_color_map {
($color_scale_name:ident, $color_type:ident) => {
impl<FloatType: std::fmt::Debug + Float + FromPrimitive + ToPrimitive>
impl<FloatType: std::fmt::Debug + num_traits::Float + num_traits::FromPrimitive + num_traits::ToPrimitive>
ColorMap<$color_type, FloatType> for $color_scale_name
{
fn get_color_normalized(
Expand All @@ -170,7 +206,7 @@ macro_rules! implement_linear_interpolation_color_map {
Self::COLORS.len()
);
// Interpolate the final color linearly
calculate_new_color_value!(
$crate::calculate_new_color_value!(
relative_difference,
Self::COLORS,
index_upper,
Expand All @@ -184,7 +220,7 @@ macro_rules! implement_linear_interpolation_color_map {
#[doc = "Get color value from `"]
#[doc = stringify!($color_scale_name)]
#[doc = "` by supplying a parameter 0.0 <= h <= 1.0"]
pub fn get_color<FloatType: std::fmt::Debug + Float + FromPrimitive + ToPrimitive>(
pub fn get_color<FloatType: std::fmt::Debug + num_traits::Float + num_traits::FromPrimitive + num_traits::ToPrimitive>(
h: FloatType,
) -> $color_type {
let color_scale = $color_scale_name {};
Expand All @@ -195,7 +231,7 @@ macro_rules! implement_linear_interpolation_color_map {
#[doc = stringify!($color_scale_name)]
#[doc = "` by supplying lower and upper bounds min, max and a parameter h where min <= h <= max"]
pub fn get_color_normalized<
FloatType: std::fmt::Debug + Float + FromPrimitive + ToPrimitive,
FloatType: std::fmt::Debug + num_traits::Float + num_traits::FromPrimitive + num_traits::ToPrimitive,
>(
h: FloatType,
min: FloatType,
Expand All @@ -208,34 +244,57 @@ macro_rules! implement_linear_interpolation_color_map {
};
}

#[doc(inline)]
pub use crate::def_linear_colormap;

#[macro_export]
/// Macro to create a new colormap with evenly spaced colors at compile-time.
macro_rules! define_linear_interpolation_color_map{
#[doc(hidden)]
/// Create a new colormap with evenly spaced colors and interpolates between them automatically.
///
/// This macro works by taking a identifier (name) for the colormap, the type of color to specify, a
/// docstring and a list of colors and constructs an empty struct on which it implements the [ColorMap] trait.
///
/// ```
/// use plotters::prelude::*;
/// def_linear_colormap! {
/// BlackWhite,
/// RGBColor,
/// "Simple chromatic colormap from black to white.",
/// ( 0, 0, 0),
/// (255, 255, 255)
/// }
///
/// assert_eq!(BlackWhite::get_color(0.0), RGBColor(0,0,0));
/// ```
///
/// Hint: Some helper macros and functions have been deliberately hidden from end users.
/// Look for them in the source code if you are interested.
macro_rules! def_linear_colormap{
($color_scale_name:ident, $color_type:ident, $doc:expr, $(($($color_value:expr),+)),*) => {
#[doc = $doc]
pub struct $color_scale_name {}
pub struct $color_scale_name;

impl $color_scale_name {
// const COLORS: [$color_type; $number_colors] = [$($color_type($($color_value),+)),+];
// const COLORS: [$color_type; count!($(($($color_value:expr),+))*)] = [$($color_type($($color_value),+)),+];
const COLORS: [$color_type; count!($(($($color_value:expr),+))*)] = define_colors_from_list_of_values_or_directly!{$color_type, $(($($color_value),+)),*};
// const COLORS: [$color_type; $crate::count!($(($($color_value:expr),+))*)] = [$($color_type($($color_value),+)),+];
const COLORS: [$color_type; $crate::count!($(($($color_value:expr),+))*)] = $crate::define_colors_from_list_of_values_or_directly!{$color_type, $(($($color_value),+)),*};
}

implement_linear_interpolation_color_map!{$color_scale_name, $color_type}
$crate::implement_linear_interpolation_color_map!{$color_scale_name, $color_type}
};
($color_scale_name:ident, $color_type:ident, $doc:expr, $($color_complete:tt),+) => {
#[doc = $doc]
pub struct $color_scale_name {}
pub struct $color_scale_name;

impl $color_scale_name {
const COLORS: [$color_type; count!($($color_complete)*)] = define_colors_from_list_of_values_or_directly!{$($color_complete),+};
const COLORS: [$color_type; $crate::count!($($color_complete)*)] = $crate::define_colors_from_list_of_values_or_directly!{$($color_complete),+};
}

implement_linear_interpolation_color_map!{$color_scale_name, $color_type}
$crate::implement_linear_interpolation_color_map!{$color_scale_name, $color_type}
}
}

define_linear_interpolation_color_map! {
def_linear_colormap! {
ViridisRGBA,
RGBAColor,
"A colormap optimized for visually impaired people (RGBA format).
Expand All @@ -251,7 +310,7 @@ define_linear_interpolation_color_map! {
(254, 232, 37, 1.0)
}

define_linear_interpolation_color_map! {
def_linear_colormap! {
ViridisRGB,
RGBColor,
"A colormap optimized for visually impaired people (RGB Format).
Expand All @@ -267,23 +326,23 @@ define_linear_interpolation_color_map! {
(254, 232, 37)
}

define_linear_interpolation_color_map! {
def_linear_colormap! {
BlackWhite,
RGBColor,
"Simple chromatic colormap from black to white.",
( 0, 0, 0),
(255, 255, 255)
}

define_linear_interpolation_color_map! {
def_linear_colormap! {
MandelbrotHSL,
HSLColor,
"Colormap created to replace the one used in the mandelbrot example.",
(0.0, 1.0, 0.5),
(1.0, 1.0, 0.5)
}

define_linear_interpolation_color_map! {
def_linear_colormap! {
VulcanoHSL,
HSLColor,
"A vulcanic colormap that display red/orange and black colors",
Expand All @@ -292,7 +351,7 @@ define_linear_interpolation_color_map! {
}

use super::full_palette::*;
define_linear_interpolation_color_map! {
def_linear_colormap! {
Bone,
RGBColor,
"Dark colormap going from black over blue to white.",
Expand All @@ -301,7 +360,7 @@ define_linear_interpolation_color_map! {
WHITE
}

define_linear_interpolation_color_map! {
def_linear_colormap! {
Copper,
RGBColor,
"Friendly black to brown colormap.",
Expand Down
Loading