Source code for mascdb.utils_figs

# -----------------------------------------------------------------------------.
# Copyright (c) 2021-2025 MASCDB developers
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the MIT License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# You should have received a copy of the MIT License
# along with this program.  If not, see <https://opensource.org/license/mit/>.
# -----------------------------------------------------------------------------.
"""MASCDB Visualization Utilities."""
# -----------------------------------------------------------------------------.
import matplotlib as mpl
import numpy as np
import pandas as pd


[docs] def cm2inch(*tupl): """Convert centimeters to inches. Parameters ---------- *tupl : float or tuple of float Dimensions in centimeters. Can be individual numbers or a single tuple. Returns ------- tuple of float Dimensions converted to inches. Examples -------- >>> cm2inch(10, 20) (3.937, 7.874) >>> cm2inch((10, 20)) (3.937, 7.874) """ inch = 2.54 if isinstance(tupl[0], tuple): return tuple(i / inch for i in tupl[0]) return tuple(i / inch for i in tupl)
[docs] def minmax(x): """Return the minimum and maximum values of an array. Parameters ---------- x : array-like Input array. Returns ------- list of float List containing [minimum, maximum] values. """ return [np.min(x), np.max(x)]
[docs] def get_c_cmap_from_color_dict(color_dict, labels): """Create color mapping and colormap for scatter plots from a color dictionary. This function converts a dictionary of label-to-color mappings into integer color indices and a matplotlib colormap suitable for use with plt.scatter. Parameters ---------- color_dict : dict Dictionary mapping labels to color names or hex values. labels : array-like Array of labels corresponding to data points. Returns ------- list A list containing [c, cmap] where: - c : numpy.ndarray Integer array of color indices for each label. - cmap : matplotlib.colors.ListedColormap Colormap object with unique colors from the dictionary. Examples -------- >>> color_dict = {"A": "red", "B": "blue", "C": "green"} >>> labels = ["A", "B", "A", "C"] >>> c, cmap = get_c_cmap_from_color_dict(color_dict, labels) """ c_names = [color_dict[x] for x in labels] # Retrieve c integer values c, c_unique_name = pd.factorize(c_names, sort=False) # Create cmap cmap = mpl.colors.ListedColormap(c_unique_name) # Return object return [c, cmap]
[docs] def get_legend_handles_from_colors_dict(colors_dict, marker="o"): """Create legend handles from a color dictionary for matplotlib legends. This function generates legend handles that can be used with matplotlib.pyplot.legend to create custom legend entries based on a color dictionary. Parameters ---------- colors_dict : dict Dictionary mapping labels to colors (color names or hex values). marker : str, optional Marker style for legend entries. Default is "o" (filled circle). Options include: - "o" : filled circle - "s" : filled square - "PATCH" : filled large rectangle - Any valid matplotlib marker Returns ------- list List of matplotlib handle objects (Line2D or Patch) for use in legend. Examples -------- >>> colors = {"Category A": "red", "Category B": "blue"} >>> handles = get_legend_handles_from_colors_dict(colors, marker="s") >>> plt.legend(handles=handles) """ import matplotlib as mpl if marker == "PATCH": # PATCH ('filled large rectangle') handles = [] for key in colors_dict: data_key = mpl.patches.Patch(facecolor=colors_dict[key], edgecolor=colors_dict[key], label=key) handles.append(data_key) else: # Classical Markers handles = [] for key in colors_dict: data_key = mpl.lines.Line2D( [0], [0], linewidth=0, marker=marker, label=key, markerfacecolor=colors_dict[key], markeredgecolor=colors_dict[key], markersize=3, ) handles.append(data_key) return handles
[docs] def get_colors_from_cmap(x, cmap_name="Spectral", vmin=None, vmax=None, nan_color=None): """Map numeric values to colors using a matplotlib colormap. This function converts numeric values to hexadecimal color codes based on a specified colormap. It handles both arrays and dictionaries, and provides special handling for NaN values. Parameters ---------- x : array-like, dict, or float Numeric values to map to colors. Can be a single value, array, or dictionary where values are numeric. cmap_name : str, optional Name of the matplotlib colormap to use. Default is "Spectral". Examples: "Spectral", "viridis", "plasma", "Wistia", "Green". vmin : float, optional Minimum value for colormap normalization. Default is None. vmax : float, optional Maximum value for colormap normalization. Default is None. nan_color : str, optional Hexadecimal color code to use for NaN values. If None, NaN values are marked as "NaN" string. Default is None. Returns ------- numpy.ndarray or dict Hexadecimal color codes corresponding to input values. Returns a dictionary if input was a dictionary, otherwise returns an array. Examples -------- >>> get_colors_from_cmap(2, cmap_name="Spectral", vmin=0, vmax=8) '#...' # hex color >>> get_colors_from_cmap([0, 2, 3, 5], cmap_name="Wistia", vmin=0, vmax=5) array(['#...', '#...', '#...', '#...'], dtype='<U7') >>> get_colors_from_cmap({"a": 1, "b": 5}, cmap_name="viridis", vmin=0, vmax=10) {'a': '#...', 'b': '#...'} Notes ----- NaN values in the input are preserved and can be assigned a custom color using the nan_color parameter. """ # # - mpl.cm.<Spectral> : colormaps are defined by default between 0 and 255 mpl.cm.Spectral(257) flag_dict = False # Preprocess x if dictionary if isinstance(x, dict): flag_dict = True keys = list(x.keys()) x = np.asarray(list(x.values())) # Get index with NaN idx_nan = np.isnan(x) # Retrieve colormap cmap = mpl.cm.get_cmap(cmap_name) # Rescale x and assign colormap values rgb_val = cmap((x - vmin) / vmax) # norm_fun = mpl.colors.Normalize(vmin=vmin, vmax=vmax) # rgb_val = cmap(norm_fun(x)) # ----------------------------------------------------------------. ###################### ## Convert to hex #### ###################### # If only a single value if isinstance(rgb_val, tuple): rgb_hex = mpl.colors.rgb2hex(rgb_val) # If multiple values (matrix with rows of rgb values) else: rgb_hex = [] for i in range(rgb_val.shape[0]): rgb_hex.append(mpl.colors.rgb2hex(rgb_val[i,])) # ----------------------------------------------------------------. # Set back NaN rgb_hex = np.array(rgb_hex) if np.sum(idx_nan) > 0: rgb_hex[idx_nan] = "NaN" # ----------------------------------------------------------------. if nan_color is not None: rgb_hex[idx_nan] = nan_color ################################################# # If x is dictionary --> Recreate dictionary #### ################################################# if flag_dict: rgb_hex = dict(zip(keys, rgb_hex)) # ---------------------------------------------------------------------. return rgb_hex
#### Test get_colors_from_cmap # get_colors_from_cmap(2, cmap_name="Spectral", vmin=0, vmax = 8) # get_colors_from_cmap(np.array([2,np.nan,5]), cmap_name="Green", vmin=0, vmax = 8) # get_colors_from_cmap(np.array([0,2,3,5]), cmap_name="Wistia", vmin=0, vmax = 5)