def create_cmap_overview(cmaps=None, *, savefig=None, use_types=True, sort='alphabetical', show_grayscale=True, plot_profile=False, dark_mode=False, title="Colormap Overview", wscale=1, hscale=1): """ Creates an overview plot containing all colormaps defined in the provided `cmaps`. Optional -------- cmaps : list of {str; :obj:`~matplotlib.colors.Colormap` objects}, dict \ of lists or None. Default: None A list of all colormaps that must be included in the overview plot. If dict of lists, the keys define categories for the colormaps. If *None*, all colormaps defined in *CMasher* are used instead. savefig : str or None. Default: None If not *None*, the path where the overview plot must be saved to. Else, the plot will simply be shown. use_types : bool. Default: True Whether all colormaps in `cmaps` should be categorized into their colormap types (sequential; diverging; cyclic; qualitative; misc). If `cmaps` is a dict, this value is ignored. sort : {'alphabetical'/'name'; 'lightness'}, function or None. Default: \ 'alphabetical' String or function indicating how the colormaps should be sorted in the overview. If 'alphabetical', the colormaps are sorted alphabetically on their name. If 'lightness', the colormaps are sorted based on their lightness profile. If function, a function definition that takes a :obj:`~matplotlib.colors.Colormap` object and returns the sorted position of that colormap. If *None*, the colormaps retain the order they were given in. show_grayscale : bool. Default: True Whether to show the grayscale versions of the given `cmaps` in the overview. plot_profile : bool or float. Default: False Whether the lightness profiles of all colormaps should be plotted. If not *False*, the lightness profile of a colormap is plotted on top of its gray-scale version and `plot_profile` is used for setting the alpha (opacity) value. If `plot_profile` is *True*, it will be set to `0.25`. If `show_grayscale` is *False*, this value is ignored. dark_mode : bool. Default: False Whether the colormap overview should be created using mostly dark colors. title : str or None. Default: "Colormap Overview" String to be used as the title of the colormap overview. If empty or *None*, no title will be used. wscale, hscale : float. Default: (1, 1) Floats that determine with what factor the colormap subplot dimensions in the overview should be scaled with. The default values uses the default dimensions for the subplots (which are determined by other input arguments). Notes ----- The colormaps in `cmaps` can either be provided as their registered name in :mod:`matplotlib.cm`, or their corresponding :obj:`~matplotlib.colors.Colormap` object. Any provided reversed colormaps (colormaps that end their name with '_r') are ignored if their normal versions were provided as well. If `plot_profile` is not set to *False*, the lightness profiles are plotted on top of the gray-scale colormap versions, where the y-axis ranges from 0% lightness to 100% lightness. The lightness profile transitions between black and white at 50% lightness. """ # Check value of show_grayscale if show_grayscale: # If True, the overview will have two columns ncols = 2 else: # If False, the overview will have one column and no profile plotted ncols = 1 wscale *= 0.5 plot_profile = False # Determine positions wscale = 0.2 + 0.8 * wscale left_pos = 0.2 / wscale spacing = 0.01 / wscale # If plot_profile is True, set it to its default value if plot_profile is True: plot_profile = 0.25 # Check if dark mode is requested if dark_mode: # If so, use dark grey for the background and light grey for the text edge_color = '#24292E' face_color = '#24292E' text_color = '#9DA5B4' else: # If not, use white for the background and black for the text edge_color = '#FFFFFF' face_color = '#FFFFFF' text_color = '#000000' # If cmaps is None, use cmap_d.values if cmaps is None: cmaps = cmrcm.cmap_d.values() # If sort is a string, obtain proper function if isinstance(sort, str): # Convert sort to lowercase sort = sort.lower() # Check what string was provided and obtain sorting function if sort in ('alphabetical', 'name'): def sort(x): return (x.name) elif (sort == 'lightness'): sort = _get_cmap_lightness_rank # Create empty list of cmaps cmaps_list = [] # If cmaps is a dict, it has cm_types defined if isinstance(cmaps, dict): # Set use_types to True use_types = True # Define empty dict of colormaps cmaps_dict = odict() # Save provided cmaps as something else input_cmaps = cmaps # Loop over all cm_types for cm_type, cmaps in input_cmaps.items(): # Add empty list of colormaps to cmaps_dict with this cm_type cmaps_dict[cm_type] = [] # Loop over all cmaps and remove reversed versions for cmap in cmaps: if isinstance(cmap, str): cmaps_dict[cm_type].append(mplcm.get_cmap(cmap)) else: cmaps_dict[cm_type].append(cmap) # Else, it is a list with no cm_types else: # If cm_types are requested if use_types: # Define empty dict with the base cm_types cm_types = [ 'sequential', 'diverging', 'cyclic', 'qualitative', 'misc' ] cmaps_dict = odict([[cm_type, []] for cm_type in cm_types]) # Loop over all cmaps and remove reversed versions for cmap in cmaps: cm_type = get_cmap_type(cmap) if isinstance(cmap, str): cmaps_dict[cm_type].append(mplcm.get_cmap(cmap)) else: cmaps_dict[cm_type].append(cmap) else: # Loop over all cmaps and remove reversed versions for cmap in cmaps: if isinstance(cmap, str): cmaps_list.append(mplcm.get_cmap(cmap)) else: cmaps_list.append(cmap) # If use_types is True, a dict is currently used if use_types: # Convert entire cmaps_dict into a list again for key, value in cmaps_dict.items(): # If this cm_type has at least 1 colormap, sort and add them if value: # Obtain the names of all colormaps names = [x.name for x in value] # Remove all reversed colormaps that also have their original off_dex = len(names) - 1 for i, name in enumerate(reversed(names)): if name.endswith('_r') and name[:-2] in names: value.pop(off_dex - i) # Sort the colormaps if requested if sort is not None: value.sort(key=sort) # Add to list cmaps_list.append((key, False)) cmaps_list.extend(value) # Else, a list is used else: # Obtain the names of all colormaps names = [x.name for x in cmaps_list] # Remove all reversed colormaps that also have their original off_dex = len(names) - 1 for i, name in enumerate(reversed(names)): if name.endswith('_r') and name[:-2] in names: cmaps_list.pop(off_dex - i) # Sort the colormaps if requested if sort is not None: cmaps_list.sort(key=sort) # Add title to cmaps_list if requested if title: cmaps_list.insert(0, (title, True)) # Obtain the colorspace converter for showing cmaps in grey-scale cspace_convert = cspace_converter("sRGB1", "CAM02-UCS") # Create figure instance height = (0.4 * len(cmaps_list) + 0.1) * hscale fig, axs = plt.subplots(figsize=(6.4 * wscale, height), nrows=len(cmaps_list), ncols=ncols, edgecolor=edge_color, facecolor=face_color) # Adjust subplot positioning fig.subplots_adjust(top=(1 - 0.05 / height), bottom=0.05 / height, left=left_pos, right=1.0 - spacing, wspace=0.05) # If cmaps_list only has a single element, make sure axs is a list if (len(cmaps_list) == 1): axs = [axs] # Loop over all cmaps defined in cmaps list for ax, cmap in zip(axs, cmaps_list): # Obtain axes objects and turn them off if show_grayscale: # Obtain Axes objects ax0, ax1 = ax # Turn axes off ax0.set_axis_off() ax1.set_axis_off() else: # Obtain Axes object ax0 = ax # Turn axis off ax0.set_axis_off() # Obtain position bbox of ax0 pos0 = ax0.get_position() # If cmap is a tuple, it defines a title or cm_type if isinstance(cmap, tuple): # Calculate title_pos title_pos = left_pos + (1 - spacing - left_pos) / 2 # If it is a title if cmap[1]: # Write the title as text in the correct position fig.text(title_pos, pos0.y0 + pos0.height / 2, cmap[0], va='center', ha='center', fontsize=18, c=text_color) # If it is a cm_type else: # Write the cm_type as text in the correct position fig.text(title_pos, pos0.y0, cmap[0], va='bottom', ha='center', fontsize=14, c=text_color) # Else, this is a colormap else: # Obtain the colormap type cm_type = get_cmap_type(cmap) # Get array of all values for which a colormap value is requested x = np.arange(cmap.N) # Get RGB values for colormap rgb = cmap(x)[:, :3] # Get lightness values of colormap lab = cspace_convert(rgb) L = lab[:, 0] # Normalize lightness values L /= 99.99871678 # Get corresponding RGB values for lightness values using neutral rgb_L = cmrcm.neutral(L)[:, :3] # Add colormap subplot ax0.imshow(rgb[np.newaxis, ...], aspect='auto') # Check if the lightness profile was requested if plot_profile and (cm_type != 'qualitative'): # Determine the points that need to be plotted plot_L = -(L - 0.5) points = np.stack([x, plot_L], axis=1) # Determine the colors that each point must have # Use black for L >= 0.5 and white for L <= 0.5. colors = np.zeros_like(plot_L, dtype=int) colors[plot_L >= 0] = 1 # Split points up into segments with the same color s_idx = np.nonzero(np.diff(colors))[0] + 1 segments = np.split(points, s_idx) # Loop over all pairs of adjacent segments for i, (seg1, seg2) in enumerate(zip(segments[:-1], segments[1:])): # Determine the point in the center of these segments central_point = (seg1[-1] + seg2[0]) / 2 # Add this point to the ends of these segments # This ensures that the color changes in between segments segments[i] = np.concatenate( [segments[i], [central_point]], axis=0) segments[i + 1] = np.concatenate( [[central_point], segments[i + 1]], axis=0) # Create an MPL LineCollection object with these segments lc = LineCollection(segments, cmap=cmrcm.neutral, alpha=plot_profile) lc.set_linewidth(1) # Determine the colors of each segment s_colors = [colors[0]] s_colors.extend(colors[s_idx]) s_colors = np.array(s_colors) # Set the values of the line-collection to be these colors lc.set_array(s_colors) # Add line-collection to this subplot ax1.add_collection(lc) # Add gray-scale colormap subplot if requested if show_grayscale: ax1.imshow(rgb_L[np.newaxis, ...], aspect='auto') # Plot the name of the colormap as text x_text = pos0.x0 - spacing y_text = pos0.y0 + pos0.height / 2 fig.text(x_text, y_text, cmap.name, va='center', ha='right', fontsize=10, c=text_color) # If savefig is not None, save the figure if savefig is not None: dpi = 100 if (path.splitext(savefig)[1] == '.svg') else 250 plt.savefig(savefig, dpi=dpi, facecolor=face_color, edgecolor=edge_color) plt.close(fig) # Else, simply show it else: plt.show()
def create_cmap_overview(cmaps=None, savefig=None, use_types=True): """ Creates an overview plot containing all colormaps defined in the provided `cmaps`. Optional -------- cmaps : list of {str; :obj:`~matplotlib.colors.Colormap` objects}, dict \ of lists or None. Default: None A list of all colormaps that must be included in the overview plot. If dict of lists, the keys define categories for the colormaps. If *None*, all colormaps defined in *CMasher* are used instead. savefig : str or None. Default: None If not *None*, the path where the overview plot must be saved to. Else, the plot will simply be shown. use_types : bool. Default: True Whether all colormaps in `cmaps` should be categorized into their colormap types (sequential; diverging; cyclic; qualitative; misc). If `cmaps` is a dict, this value is ignored. Note ---- The colormaps in `cmaps` can either be provided as their registered name in *MPL*, or their corresponding :obj:`~matplotlib.colors.Colormap` object. Any provided reversed colormaps (colormaps that end their name with '_r') are ignored. """ # If cmaps is None, use cmap_d.values if cmaps is None: cmaps = cmrcm.cmap_d.values() # Create empty list of cmaps cmaps_list = [] # If cmaps is a dict, it has cm_types defined if isinstance(cmaps, dict): # Define empty dict of colormaps cmaps_dict = odict() # Save provided cmaps as something else input_cmaps = cmaps # Loop over all cm_types for cm_type, cmaps in input_cmaps.items(): # Add empty list of colormaps to cmaps_dict with this cm_type cmaps_dict[cm_type] = [] type_lst = cmaps_dict[cm_type] # Loop over all cmaps and remove reversed versions for cmap in cmaps: if isinstance(cmap, string_types) and not cmap.endswith('_r'): type_lst.append(mplcm.get_cmap(cmap)) elif not cmap.name.endswith('_r'): type_lst.append(cmap) # Sort the colormaps in this cm_type type_lst.sort(key=lambda x: x.name) # Convert entire cmaps_dict into a list again for key, value in cmaps_dict.items(): # If this cm_type has at least 1 colormap, add them if value: cmaps_list.append(key) cmaps_list.extend(value) # Else, it is a list with no cm_types else: # If cm_types are requested if use_types: # Define empty dict with the base cm_types cm_types = [ 'sequential', 'diverging', 'cyclic', 'qualitative', 'misc' ] cmaps_dict = odict([[cm_type, []] for cm_type in cm_types]) # Loop over all cmaps and remove reversed versions for cmap in cmaps: cm_type = _get_cm_type(cmap) if isinstance(cmap, string_types) and not cmap.endswith('_r'): cmaps_dict[cm_type].append(mplcm.get_cmap(cmap)) elif not cmap.name.endswith('_r'): cmaps_dict[cm_type].append(cmap) # Loop over all cm_types and sort their colormaps for cm_type in cm_types: cmaps_dict[cm_type].sort(key=lambda x: x.name) # Convert entire cmaps_dict into a list again for key, value in cmaps_dict.items(): # If this cm_type has at least 1 colormap, add them if value: cmaps_list.append(key) cmaps_list.extend(value) else: # Loop over all cmaps and remove reversed versions for cmap in cmaps: if isinstance(cmap, string_types) and not cmap.endswith('_r'): cmaps_list.append(mplcm.get_cmap(cmap)) elif not cmap.name.endswith('_r'): cmaps_list.append(cmap) cmaps_list.sort(key=lambda x: x.name) # Obtain the colorspace converter for showing cmaps in grey-scale cspace_convert = cspace_converter("sRGB1", "CAM02-UCS") # Create figure instance height = 0.4 * (len(cmaps_list) + 1) fig, axes = plt.subplots(figsize=(6.4, height), nrows=len(cmaps_list), ncols=2) w_pad, h_pad, wspace, hspace = fig.get_constrained_layout_pads() fig.subplots_adjust(top=(1 - 0.24 / height), bottom=0.01, left=0.2, right=0.99, wspace=0.05) fig.suptitle("Colormap Overview", fontsize=16, y=1.0, x=0.595) # If cmaps_list only has a single element, make sure axes is a list if (len(cmaps_list) == 1): axes = [axes] # Loop over all cmaps defined in cmaps list for ax, cmap in zip(axes, cmaps_list): # Turn axes off ax[0].set_axis_off() ax[1].set_axis_off() # If cmap is a string, it defines a cm_type if isinstance(cmap, string_types): # Write the cm_type as text in the correct position fig.text(0.595, ax[0].get_position().bounds[1], cmap, va='bottom', ha='center', fontsize=14) # Else, this is a colormap else: # Get array of all values for which a colormap value is requested x = np.linspace(0, 1, cmap.N) # Get RGB values for colormap rgb = cmap(x)[:, :3] # Get lightness values of colormap lab = cspace_convert(rgb) L = lab[:, 0] # Get corresponding RGB values for lightness values using neutral rgb_L = cmrcm.neutral(L / 99.99871678)[:, :3] # Add subplots ax[0].imshow(rgb[np.newaxis, ...], aspect='auto') ax[1].imshow(rgb_L[np.newaxis, ...], aspect='auto') pos = list(ax[0].get_position().bounds) x_text = pos[0] - 0.01 y_text = pos[1] + pos[3] / 2 fig.text(x_text, y_text, cmap.name, va='center', ha='right', fontsize=10) # If savefig is not None, save the figure if savefig is not None: plt.savefig(savefig, dpi=250) plt.close(fig) # Else, simply show it else: plt.show()
def create_cmap_overview(cmaps=None, savefig=None, use_types=True, sort='alphabetical', plot_profile=False): """ Creates an overview plot containing all colormaps defined in the provided `cmaps`. Optional -------- cmaps : list of {str; :obj:`~matplotlib.colors.Colormap` objects}, dict \ of lists or None. Default: None A list of all colormaps that must be included in the overview plot. If dict of lists, the keys define categories for the colormaps. If *None*, all colormaps defined in *CMasher* are used instead. savefig : str or None. Default: None If not *None*, the path where the overview plot must be saved to. Else, the plot will simply be shown. use_types : bool. Default: True Whether all colormaps in `cmaps` should be categorized into their colormap types (sequential; diverging; cyclic; qualitative; misc). If `cmaps` is a dict, this value is ignored. sort : {'alphabetical'/'name'; 'lightness'} or None. Default: \ 'alphabetical' String indicating how the colormaps should be sorted in the overview. If 'alphabetical', the colormaps are sorted alphabetically on their name. If 'lightness', the colormaps are sorted on their starting lightness and their lightness range. If *None*, the colormaps retain the order they were given in. plot_profile : bool or float. Default: False Whether the lightness profiles of all colormaps should be plotted. If not *False*, the lightness profile of a colormap is plotted on top of its gray-scale version and `plot_profile` is used for setting the alpha (opacity) value. If `plot_profile` is *True*, it will be set to `0.25`. Notes ----- The colormaps in `cmaps` can either be provided as their registered name in :mod:`matplotlib.cm`, or their corresponding :obj:`~matplotlib.colors.Colormap` object. Any provided reversed colormaps (colormaps that end their name with '_r') are ignored. If `plot_profile` is not set to *False*, the lightness profiles are plotted on top of the gray-scale colormap versions, where the y-axis ranges from 0% lightness to 100% lightness. The lightness profile transitions between black and white at 50% lightness. """ # If plot_profile is True, set it to its default value if plot_profile is True: plot_profile = 0.25 # If cmaps is None, use cmap_d.values if cmaps is None: cmaps = cmrcm.cmap_d.values() # If sort is a string, convert to lowercase if isinstance(sort, string_types): sort = sort.lower() # Create empty list of cmaps cmaps_list = [] # If cmaps is a dict, it has cm_types defined if isinstance(cmaps, dict): # Set use_types to True use_types = True # Define empty dict of colormaps cmaps_dict = odict() # Save provided cmaps as something else input_cmaps = cmaps # Loop over all cm_types for cm_type, cmaps in input_cmaps.items(): # Add empty list of colormaps to cmaps_dict with this cm_type cmaps_dict[cm_type] = [] # Loop over all cmaps and remove reversed versions for cmap in cmaps: if isinstance(cmap, string_types): if not cmap.endswith('_r'): cmaps_dict[cm_type].append(mplcm.get_cmap(cmap)) elif not cmap.name.endswith('_r'): cmaps_dict[cm_type].append(cmap) # Else, it is a list with no cm_types else: # If cm_types are requested if use_types: # Define empty dict with the base cm_types cm_types = [ 'sequential', 'diverging', 'cyclic', 'qualitative', 'misc' ] cmaps_dict = odict([[cm_type, []] for cm_type in cm_types]) # Loop over all cmaps and remove reversed versions for cmap in cmaps: cm_type = get_cmap_type(cmap) if isinstance(cmap, string_types): if not cmap.endswith('_r'): cmaps_dict[cm_type].append(mplcm.get_cmap(cmap)) elif not cmap.name.endswith('_r'): cmaps_dict[cm_type].append(cmap) else: # Loop over all cmaps and remove reversed versions for cmap in cmaps: if isinstance(cmap, string_types): if not cmap.endswith('_r'): cmaps_list.append(mplcm.get_cmap(cmap)) elif not cmap.name.endswith('_r'): cmaps_list.append(cmap) # If use_types is True, a dict is currently used if use_types: # Convert entire cmaps_dict into a list again for key, value in cmaps_dict.items(): # If this cm_type has at least 1 colormap, sort and add them if value: # Sort on lightness if requested and this cm_type is compatible if ((sort == 'lightness') and (key not in ('qualitative', 'misc'))): value.sort(key=_get_cmap_lightness_rank) # Else, sort on name elif sort in ('alphabetical', 'name'): value.sort(key=lambda x: x.name) # Add to list cmaps_list.append(key) cmaps_list.extend(value) # Else, a list is used else: # Sort the colormaps if (sort == 'lightness'): cmaps_list.sort(key=_get_cmap_lightness_rank) elif sort in ('alphabetical', 'name'): cmaps_list.sort(key=lambda x: x.name) # Obtain the colorspace converter for showing cmaps in grey-scale cspace_convert = cspace_converter("sRGB1", "CAM02-UCS") # Create figure instance height = 0.4 * (len(cmaps_list) + 1) fig, axes = plt.subplots(figsize=(6.4, height), nrows=len(cmaps_list), ncols=2) fig.subplots_adjust(top=(1 - 0.24 / height), bottom=0.048 / height, left=0.2, right=0.99, wspace=0.05) fig.suptitle("Colormap Overview", fontsize=16, y=1.0, x=0.595) # If cmaps_list only has a single element, make sure axes is a list if (len(cmaps_list) == 1): axes = [axes] # Set the current cm_type to None cm_type = None # Loop over all cmaps defined in cmaps list for ax, cmap in zip(axes, cmaps_list): # Turn axes off ax[0].set_axis_off() ax[1].set_axis_off() # If cmap is a string, it defines a cm_type if isinstance(cmap, string_types): # Write the cm_type as text in the correct position fig.text(0.595, ax[0].get_position().bounds[1], cmap, va='bottom', ha='center', fontsize=14) # Save what the current cm_type is cm_type = cmap # Else, this is a colormap else: # Get array of all values for which a colormap value is requested x = np.arange(cmap.N) # Get RGB values for colormap rgb = cmap(x)[:, :3] # Get lightness values of colormap lab = cspace_convert(rgb) L = lab[:, 0] # Normalize lightness values L /= 99.99871678 # Get corresponding RGB values for lightness values using neutral rgb_L = cmrcm.neutral(L)[:, :3] # Add colormap subplot ax[0].imshow(rgb[np.newaxis, ...], aspect='auto') # Check if the lightness profile was requested if plot_profile and (cm_type != 'qualitative'): # Determine the points that need to be plotted plot_L = -(L - 0.5) points = np.stack([x, plot_L], axis=1) # Determine the colors that each point must have # Use black for L >= 0.5 and white for L <= 0.5. colors = np.zeros_like(plot_L, dtype=int) colors[plot_L >= 0] = 1 # Split points up into segments with the same color s_idx = np.nonzero(np.diff(colors))[0] + 1 segments = np.split(points, s_idx) # Loop over all pairs of adjacent segments for i, (seg1, seg2) in enumerate(zip(segments[:-1], segments[1:])): # Determine the point in the center of these segments central_point = (seg1[-1] + seg2[0]) / 2 # Add this point to the ends of these segments # This ensures that the color changes in between segments segments[i] = np.concatenate( [segments[i], [central_point]], axis=0) segments[i + 1] = np.concatenate( [[central_point], segments[i + 1]], axis=0) # Create an MPL LineCollection object with these segments lc = LineCollection(segments, cmap=cmrcm.neutral, alpha=plot_profile) lc.set_linewidth(1) # Determine the colors of each segment s_colors = [colors[0]] s_colors.extend(colors[s_idx]) s_colors = np.array(s_colors) # Set the values of the line-collection to be these colors lc.set_array(s_colors) # Add line-collection to this subplot ax[1].add_collection(lc) # Add gray-scale colormap subplot ax[1].imshow(rgb_L[np.newaxis, ...], aspect='auto') # Plot the name of the colormap as text pos = list(ax[0].get_position().bounds) x_text = pos[0] - 0.01 y_text = pos[1] + pos[3] / 2 fig.text(x_text, y_text, cmap.name, va='center', ha='right', fontsize=10) # If savefig is not None, save the figure if savefig is not None: dpi = 100 if (path.splitext(savefig)[1] == '.svg') else 250 plt.savefig(savefig, dpi=dpi) plt.close(fig) # Else, simply show it else: plt.show()