From 5e9ff2137e1022a7d570c208f2e46717a7992273 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sun, 30 Jul 2023 02:40:06 +0200 Subject: [PATCH 1/5] Undo black string formatting --- proplot/axes/base.py | 2 +- proplot/config.py | 6 ++++++ proplot/figure.py | 2 +- proplot/internals/rcsetup.py | 7 ++++++- setup.cfg | 2 +- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/proplot/axes/base.py b/proplot/axes/base.py index 95bd06bdb..86a9b54ce 100644 --- a/proplot/axes/base.py +++ b/proplot/axes/base.py @@ -1144,7 +1144,7 @@ def _add_colorbar( mappable, cax=cax, ticks=locator, format=formatter, drawedges=grid, extendfrac=extendfrac, **kwargs ) - obj.minorlocator = minorlocator # backwards compatibility + # obj.minorlocator = minorlocator # backwards compatibility NOTE: NOT sure what this does obj.update_ticks = guides._update_ticks.__get__(obj) # backwards compatible if minorlocator is not None: obj.update_ticks() diff --git a/proplot/config.py b/proplot/config.py index 79f9e6f7b..7f55bb9fc 100644 --- a/proplot/config.py +++ b/proplot/config.py @@ -452,6 +452,8 @@ def register_cmaps(*args, user=None, local=None, default=False): """ # Register input colormaps from . import colors as pcolors + + import matplotlib as mpl user = _not_none(user, not bool(args)) # skip user folder if input args passed local = _not_none(local, not bool(args)) paths = [] @@ -471,6 +473,10 @@ def register_cmaps(*args, user=None, local=None, default=False): if i == 0 and cmap.name.lower() in pcolors.CMAPS_CYCLIC: cmap.set_cyclic(True) pcolors._cmap_database[cmap.name] = cmap + # for mpl >= 3.7.2 + if hasattr(mpl, "colormaps"): + mpl.colormaps.register(cmap) + @docstring._snippet_manager diff --git a/proplot/figure.py b/proplot/figure.py index bc0ff6f0d..a464f8c06 100644 --- a/proplot/figure.py +++ b/proplot/figure.py @@ -888,7 +888,7 @@ def _get_renderer(self): """ Get a renderer at all costs. See matplotlib's tight_layout.py. """ - if self._cachedRenderer: + if hasattr(self, "_cached_renderer"): renderer = self._cachedRenderer else: canvas = self.canvas diff --git a/proplot/internals/rcsetup.py b/proplot/internals/rcsetup.py index 105f219f3..8b5d593db 100644 --- a/proplot/internals/rcsetup.py +++ b/proplot/internals/rcsetup.py @@ -7,6 +7,7 @@ from collections.abc import MutableMapping from numbers import Integral, Real +import matplotlib as mpl import matplotlib.rcsetup as msetup import numpy as np from cycler import Cycler @@ -14,7 +15,11 @@ from matplotlib import rcParamsDefault as _rc_matplotlib_native from matplotlib.colors import Colormap from matplotlib.font_manager import font_scalings -from matplotlib.fontconfig_pattern import parse_fontconfig_pattern + +if hasattr(mpl, "_fontconfig_pattern"): + from matplotlib._fontconfig_pattern import parse_fontconfig_pattern +else: + from matplotlib.fontconfig_pattern import parse_fontconfig_pattern from . import ic # noqa: F401 from . import warnings diff --git a/setup.cfg b/setup.cfg index a30e3693b..abc5e76ed 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,6 +27,6 @@ project_urls = [options] packages = proplot -install_requires = matplotlib>=3.0.0,<3.6.0 +install_requires = matplotlib>=3.0.0,<=3.8.0 include_package_data = True python_requires = >=3.6.0 From 2d3f7749bb509f47969fc233e0981e682186f66e Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sun, 30 Jul 2023 02:54:33 +0200 Subject: [PATCH 2/5] Forgot proper reset of colors.py --- proplot/colors.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/proplot/colors.py b/proplot/colors.py index 531d70f02..d16cd5202 100644 --- a/proplot/colors.py +++ b/proplot/colors.py @@ -2742,19 +2742,22 @@ def _init_cmap_database(): """ # WARNING: Skip over the matplotlib native duplicate entries # with suffixes '_r' and '_shifted'. - attr = '_cmap_registry' if hasattr(mcm, '_cmap_registry') else 'cmap_d' - database = getattr(mcm, attr) - if mcm.get_cmap is not _get_cmap: - mcm.get_cmap = _get_cmap - if mcm.register_cmap is not _register_cmap: - mcm.register_cmap = _register_cmap - if not isinstance(database, ColormapDatabase): - database = { - key: value for key, value in database.items() - if key[-2:] != '_r' and key[-8:] != '_shifted' - } - database = ColormapDatabase(database) - setattr(mcm, attr, database) + if hasattr(mcm, "_cmap_d"): + attr = '_cmap_registry' if hasattr(mcm, '_cmap_registry') else 'cmap_d' + database = getattr(mcm, attr) + if mcm.get_cmap is not _get_cmap: + mcm.get_cmap = _get_cmap + if mcm.register_cmap is not _register_cmap: + mcm.register_cmap = _register_cmap + if not isinstance(database, ColormapDatabase): + database = { + key: value for key, value in database.items() + if key[-2:] != '_r' and key[-8:] != '_shifted' + } + database = ColormapDatabase(database) + setattr(mcm, attr, database) + else: + database = ColormapDatabase(mcm._gen_cmap_registry()) return database From a74c1ac92f378481ec9c0de3251ca4b50e778ce9 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Wed, 9 Aug 2023 09:15:21 +0200 Subject: [PATCH 3/5] proper cm check having the required functions --- proplot/colors.py | 1032 +++++++++++++++++++++++++-------------------- 1 file changed, 578 insertions(+), 454 deletions(-) diff --git a/proplot/colors.py b/proplot/colors.py index 053118eda..a836bacf2 100644 --- a/proplot/colors.py +++ b/proplot/colors.py @@ -41,135 +41,136 @@ from .utils import set_alpha, to_hex, to_rgb, to_rgba, to_xyz, to_xyza __all__ = [ - 'DiscreteColormap', - 'ContinuousColormap', - 'PerceptualColormap', - 'DiscreteNorm', - 'DivergingNorm', - 'SegmentedNorm', - 'ColorDatabase', - 'ColormapDatabase', - 'ListedColormap', # deprecated - 'LinearSegmentedColormap', # deprecated - 'PerceptuallyUniformColormap', # deprecated - 'LinearSegmentedNorm', # deprecated + "DiscreteColormap", + "ContinuousColormap", + "PerceptualColormap", + "DiscreteNorm", + "DivergingNorm", + "SegmentedNorm", + "ColorDatabase", + "ColormapDatabase", + "ListedColormap", # deprecated + "LinearSegmentedColormap", # deprecated + "PerceptuallyUniformColormap", # deprecated + "LinearSegmentedNorm", # deprecated ] # Default colormap properties -DEFAULT_NAME = '_no_name' -DEFAULT_SPACE = 'hsl' +DEFAULT_NAME = "_no_name" +DEFAULT_SPACE = "hsl" # Color regexes # NOTE: We do not compile hex regex because config.py needs this surrounded by \A\Z -_regex_hex = r'#(?:[0-9a-fA-F]{3,4}){2}' # 6-8 digit hex +_regex_hex = r"#(?:[0-9a-fA-F]{3,4}){2}" # 6-8 digit hex REGEX_HEX_MULTI = re.compile(_regex_hex) -REGEX_HEX_SINGLE = re.compile(rf'\A{_regex_hex}\Z') -REGEX_ADJUST = re.compile(r'\A(light|dark|medium|pale|charcoal)?\s*(gr[ea]y[0-9]?)?\Z') +REGEX_HEX_SINGLE = re.compile(rf"\A{_regex_hex}\Z") +REGEX_ADJUST = re.compile(r"\A(light|dark|medium|pale|charcoal)?\s*(gr[ea]y[0-9]?)?\Z") # Colormap constants CMAPS_CYCLIC = tuple( # cyclic colormaps loaded from rgb files - key.lower() for key in ( - 'MonoCycle', - 'twilight', - 'Phase', - 'romaO', - 'brocO', - 'corkO', - 'vikO', - 'bamO', + key.lower() + for key in ( + "MonoCycle", + "twilight", + "Phase", + "romaO", + "brocO", + "corkO", + "vikO", + "bamO", ) ) CMAPS_DIVERGING = { # mirrored dictionary mapping for reversed names key.lower(): value.lower() for key1, key2 in ( - ('BR', 'RB'), - ('NegPos', 'PosNeg'), - ('CoolWarm', 'WarmCool'), - ('ColdHot', 'HotCold'), - ('DryWet', 'WetDry'), - ('PiYG', 'GYPi'), - ('PRGn', 'GnRP'), - ('BrBG', 'GBBr'), - ('PuOr', 'OrPu'), - ('RdGy', 'GyRd'), - ('RdBu', 'BuRd'), - ('RdYlBu', 'BuYlRd'), - ('RdYlGn', 'GnYlRd'), + ("BR", "RB"), + ("NegPos", "PosNeg"), + ("CoolWarm", "WarmCool"), + ("ColdHot", "HotCold"), + ("DryWet", "WetDry"), + ("PiYG", "GYPi"), + ("PRGn", "GnRP"), + ("BrBG", "GBBr"), + ("PuOr", "OrPu"), + ("RdGy", "GyRd"), + ("RdBu", "BuRd"), + ("RdYlBu", "BuYlRd"), + ("RdYlGn", "GnYlRd"), ) for key, value in ((key1, key2), (key2, key1)) } for _cmap_diverging in ( # remaining diverging cmaps (see PlotAxes._parse_cmap) - 'Div', - 'Vlag', - 'Spectral', - 'Balance', - 'Delta', - 'Curl', - 'roma', - 'broc', - 'cork', - 'vik', - 'bam', - 'lisbon', - 'tofino', - 'berlin', - 'vanimo', + "Div", + "Vlag", + "Spectral", + "Balance", + "Delta", + "Curl", + "roma", + "broc", + "cork", + "vik", + "bam", + "lisbon", + "tofino", + "berlin", + "vanimo", ): CMAPS_DIVERGING[_cmap_diverging.lower()] = _cmap_diverging.lower() CMAPS_REMOVED = { - 'Blue0': '0.6.0', - 'Cool': '0.6.0', - 'Warm': '0.6.0', - 'Hot': '0.6.0', - 'Floral': '0.6.0', - 'Contrast': '0.6.0', - 'Sharp': '0.6.0', - 'Viz': '0.6.0', + "Blue0": "0.6.0", + "Cool": "0.6.0", + "Warm": "0.6.0", + "Hot": "0.6.0", + "Floral": "0.6.0", + "Contrast": "0.6.0", + "Sharp": "0.6.0", + "Viz": "0.6.0", } CMAPS_RENAMED = { - 'GrayCycle': ('MonoCycle', '0.6.0'), - 'Blue1': ('Blues1', '0.7.0'), - 'Blue2': ('Blues2', '0.7.0'), - 'Blue3': ('Blues3', '0.7.0'), - 'Blue4': ('Blues4', '0.7.0'), - 'Blue5': ('Blues5', '0.7.0'), - 'Blue6': ('Blues6', '0.7.0'), - 'Blue7': ('Blues7', '0.7.0'), - 'Blue8': ('Blues8', '0.7.0'), - 'Blue9': ('Blues9', '0.7.0'), - 'Green1': ('Greens1', '0.7.0'), - 'Green2': ('Greens2', '0.7.0'), - 'Green3': ('Greens3', '0.7.0'), - 'Green4': ('Greens4', '0.7.0'), - 'Green5': ('Greens5', '0.7.0'), - 'Green6': ('Greens6', '0.7.0'), - 'Green7': ('Greens7', '0.7.0'), - 'Green8': ('Greens8', '0.7.0'), - 'Orange1': ('Yellows1', '0.7.0'), - 'Orange2': ('Yellows2', '0.7.0'), - 'Orange3': ('Yellows3', '0.7.0'), - 'Orange4': ('Oranges2', '0.7.0'), - 'Orange5': ('Oranges1', '0.7.0'), - 'Orange6': ('Oranges3', '0.7.0'), - 'Orange7': ('Oranges4', '0.7.0'), - 'Orange8': ('Yellows4', '0.7.0'), - 'Brown1': ('Browns1', '0.7.0'), - 'Brown2': ('Browns2', '0.7.0'), - 'Brown3': ('Browns3', '0.7.0'), - 'Brown4': ('Browns4', '0.7.0'), - 'Brown5': ('Browns5', '0.7.0'), - 'Brown6': ('Browns6', '0.7.0'), - 'Brown7': ('Browns7', '0.7.0'), - 'Brown8': ('Browns8', '0.7.0'), - 'Brown9': ('Browns9', '0.7.0'), - 'RedPurple1': ('Reds1', '0.7.0'), - 'RedPurple2': ('Reds2', '0.7.0'), - 'RedPurple3': ('Reds3', '0.7.0'), - 'RedPurple4': ('Reds4', '0.7.0'), - 'RedPurple5': ('Reds5', '0.7.0'), - 'RedPurple6': ('Purples1', '0.7.0'), - 'RedPurple7': ('Purples2', '0.7.0'), - 'RedPurple8': ('Purples3', '0.7.0'), + "GrayCycle": ("MonoCycle", "0.6.0"), + "Blue1": ("Blues1", "0.7.0"), + "Blue2": ("Blues2", "0.7.0"), + "Blue3": ("Blues3", "0.7.0"), + "Blue4": ("Blues4", "0.7.0"), + "Blue5": ("Blues5", "0.7.0"), + "Blue6": ("Blues6", "0.7.0"), + "Blue7": ("Blues7", "0.7.0"), + "Blue8": ("Blues8", "0.7.0"), + "Blue9": ("Blues9", "0.7.0"), + "Green1": ("Greens1", "0.7.0"), + "Green2": ("Greens2", "0.7.0"), + "Green3": ("Greens3", "0.7.0"), + "Green4": ("Greens4", "0.7.0"), + "Green5": ("Greens5", "0.7.0"), + "Green6": ("Greens6", "0.7.0"), + "Green7": ("Greens7", "0.7.0"), + "Green8": ("Greens8", "0.7.0"), + "Orange1": ("Yellows1", "0.7.0"), + "Orange2": ("Yellows2", "0.7.0"), + "Orange3": ("Yellows3", "0.7.0"), + "Orange4": ("Oranges2", "0.7.0"), + "Orange5": ("Oranges1", "0.7.0"), + "Orange6": ("Oranges3", "0.7.0"), + "Orange7": ("Oranges4", "0.7.0"), + "Orange8": ("Yellows4", "0.7.0"), + "Brown1": ("Browns1", "0.7.0"), + "Brown2": ("Browns2", "0.7.0"), + "Brown3": ("Browns3", "0.7.0"), + "Brown4": ("Browns4", "0.7.0"), + "Brown5": ("Browns5", "0.7.0"), + "Brown6": ("Browns6", "0.7.0"), + "Brown7": ("Browns7", "0.7.0"), + "Brown8": ("Browns8", "0.7.0"), + "Brown9": ("Browns9", "0.7.0"), + "RedPurple1": ("Reds1", "0.7.0"), + "RedPurple2": ("Reds2", "0.7.0"), + "RedPurple3": ("Reds3", "0.7.0"), + "RedPurple4": ("Reds4", "0.7.0"), + "RedPurple5": ("Reds5", "0.7.0"), + "RedPurple6": ("Purples1", "0.7.0"), + "RedPurple7": ("Purples2", "0.7.0"), + "RedPurple8": ("Purples3", "0.7.0"), } # Color constants @@ -177,94 +178,169 @@ COLORS_XKCD = {} # populated during register_colors COLORS_KEEP = ( *( # always load these XKCD colors regardless of settings - 'charcoal', 'tomato', 'burgundy', 'maroon', 'burgundy', 'lavendar', - 'taupe', 'sand', 'stone', 'earth', 'sand brown', 'sienna', - 'terracotta', 'moss', 'crimson', 'mauve', 'rose', 'teal', 'forest', - 'grass', 'sage', 'pine', 'vermillion', 'russet', 'cerise', 'avocado', - 'wine', 'brick', 'umber', 'mahogany', 'puce', 'grape', 'blurple', - 'cranberry', 'sand', 'aqua', 'jade', 'coral', 'olive', 'magenta', - 'turquoise', 'sea blue', 'royal blue', 'slate blue', 'slate grey', - 'baby blue', 'salmon', 'beige', 'peach', 'mustard', 'lime', 'indigo', - 'cornflower', 'marine', 'cloudy blue', 'tangerine', 'scarlet', 'navy', - 'cool grey', 'warm grey', 'chocolate', 'raspberry', 'denim', - 'gunmetal', 'midnight', 'chartreuse', 'ivory', 'khaki', 'plum', - 'silver', 'tan', 'wheat', 'buff', 'bisque', 'cerulean', + "charcoal", + "tomato", + "burgundy", + "maroon", + "burgundy", + "lavendar", + "taupe", + "sand", + "stone", + "earth", + "sand brown", + "sienna", + "terracotta", + "moss", + "crimson", + "mauve", + "rose", + "teal", + "forest", + "grass", + "sage", + "pine", + "vermillion", + "russet", + "cerise", + "avocado", + "wine", + "brick", + "umber", + "mahogany", + "puce", + "grape", + "blurple", + "cranberry", + "sand", + "aqua", + "jade", + "coral", + "olive", + "magenta", + "turquoise", + "sea blue", + "royal blue", + "slate blue", + "slate grey", + "baby blue", + "salmon", + "beige", + "peach", + "mustard", + "lime", + "indigo", + "cornflower", + "marine", + "cloudy blue", + "tangerine", + "scarlet", + "navy", + "cool grey", + "warm grey", + "chocolate", + "raspberry", + "denim", + "gunmetal", + "midnight", + "chartreuse", + "ivory", + "khaki", + "plum", + "silver", + "tan", + "wheat", + "buff", + "bisque", + "cerulean", ), *( # common combinations - 'red orange', 'yellow orange', 'yellow green', - 'blue green', 'blue violet', 'red violet', - 'bright red', # backwards compatibility + "red orange", + "yellow orange", + "yellow green", + "blue green", + "blue violet", + "red violet", + "bright red", # backwards compatibility ), *( # common names prefix + color for color in ( - 'red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet', - 'brown', 'grey', 'gray', + "red", + "orange", + "yellow", + "green", + "blue", + "indigo", + "violet", + "brown", + "grey", + "gray", ) - for prefix in ('', 'light ', 'dark ', 'medium ', 'pale ') - ) + for prefix in ("", "light ", "dark ", "medium ", "pale ") + ), ) COLORS_REMOVE = ( # filter these out, let's try to be professional here... - 'shit', - 'poop', - 'poo', - 'pee', - 'piss', - 'puke', - 'vomit', - 'snot', - 'booger', - 'bile', - 'diarrhea', - 'icky', - 'sickly', + "shit", + "poop", + "poo", + "pee", + "piss", + "puke", + "vomit", + "snot", + "booger", + "bile", + "diarrhea", + "icky", + "sickly", ) COLORS_REPLACE = ( # prevent registering similar-sounding names # these can all be combined - ('/', ' '), # convert [color1]/[color2] to compound (e.g. grey/blue to grey blue) - ("'s", 's'), # robin's egg - ('egg blue', 'egg'), # robin's egg blue - ('grey', 'gray'), # 'Murica - ('ochre', 'ocher'), # ... - ('forrest', 'forest'), # ... - ('ocre', 'ocher'), # correct spelling - ('kelley', 'kelly'), # ... - ('reddish', 'red'), # remove [color]ish where it modifies the spelling of color - ('purplish', 'purple'), # ... - ('pinkish', 'pink'), - ('yellowish', 'yellow'), - ('bluish', 'blue'), - ('greyish', 'grey'), - ('ish', ''), # these are all [color]ish ('ish' substring appears nowhere else) - ('bluey', 'blue'), # remove [color]y trailing y - ('greeny', 'green'), # ... - ('reddy', 'red'), - ('pinky', 'pink'), - ('purply', 'purple'), - ('purpley', 'purple'), - ('yellowy', 'yellow'), - ('orangey', 'orange'), - ('browny', 'brown'), - ('minty', 'mint'), # now remove [object]y trailing y - ('grassy', 'grass'), # ... - ('mossy', 'moss'), - ('dusky', 'dusk'), - ('rusty', 'rust'), - ('muddy', 'mud'), - ('sandy', 'sand'), - ('leafy', 'leaf'), - ('dusty', 'dust'), - ('dirty', 'dirt'), - ('peachy', 'peach'), - ('stormy', 'storm'), - ('cloudy', 'cloud'), - ('grayblue', 'gray blue'), # separate merge compounds - ('bluegray', 'gray blue'), # ... - ('lightblue', 'light blue'), - ('yellowgreen', 'yellow green'), - ('yelloworange', 'yellow orange'), + ("/", " "), # convert [color1]/[color2] to compound (e.g. grey/blue to grey blue) + ("'s", "s"), # robin's egg + ("egg blue", "egg"), # robin's egg blue + ("grey", "gray"), # 'Murica + ("ochre", "ocher"), # ... + ("forrest", "forest"), # ... + ("ocre", "ocher"), # correct spelling + ("kelley", "kelly"), # ... + ("reddish", "red"), # remove [color]ish where it modifies the spelling of color + ("purplish", "purple"), # ... + ("pinkish", "pink"), + ("yellowish", "yellow"), + ("bluish", "blue"), + ("greyish", "grey"), + ("ish", ""), # these are all [color]ish ('ish' substring appears nowhere else) + ("bluey", "blue"), # remove [color]y trailing y + ("greeny", "green"), # ... + ("reddy", "red"), + ("pinky", "pink"), + ("purply", "purple"), + ("purpley", "purple"), + ("yellowy", "yellow"), + ("orangey", "orange"), + ("browny", "brown"), + ("minty", "mint"), # now remove [object]y trailing y + ("grassy", "grass"), # ... + ("mossy", "moss"), + ("dusky", "dusk"), + ("rusty", "rust"), + ("muddy", "mud"), + ("sandy", "sand"), + ("leafy", "leaf"), + ("dusty", "dust"), + ("dirty", "dirt"), + ("peachy", "peach"), + ("stormy", "storm"), + ("cloudy", "cloud"), + ("grayblue", "gray blue"), # separate merge compounds + ("bluegray", "gray blue"), # ... + ("lightblue", "light blue"), + ("yellowgreen", "yellow green"), + ("yelloworange", "yellow orange"), ) # Simple snippets @@ -312,13 +388,13 @@ ``len(colors) - 1``. Larger numbers indicate a slower transition, smaller numbers indicate a faster transition. """ -docstring._snippet_manager['colors.N'] = _N_docstring -docstring._snippet_manager['colors.alpha'] = _alpha_docstring -docstring._snippet_manager['colors.cyclic'] = _cyclic_docstring -docstring._snippet_manager['colors.gamma'] = _gamma_docstring -docstring._snippet_manager['colors.space'] = _space_docstring -docstring._snippet_manager['colors.ratios'] = _ratios_docstring -docstring._snippet_manager['colors.name'] = _name_docstring +docstring._snippet_manager["colors.N"] = _N_docstring +docstring._snippet_manager["colors.alpha"] = _alpha_docstring +docstring._snippet_manager["colors.cyclic"] = _cyclic_docstring +docstring._snippet_manager["colors.gamma"] = _gamma_docstring +docstring._snippet_manager["colors.space"] = _space_docstring +docstring._snippet_manager["colors.ratios"] = _ratios_docstring +docstring._snippet_manager["colors.name"] = _name_docstring # List classmethod snippets _from_list_docstring = """ @@ -336,7 +412,7 @@ creates a colormap with the transition from red to blue taking *twice as long* as the transition from blue to green. """ -docstring._snippet_manager['colors.from_list'] = _from_list_docstring +docstring._snippet_manager["colors.from_list"] = _from_list_docstring def _clip_colors(colors, clip=True, gray=0.2, warn_invalid=False): @@ -363,14 +439,14 @@ def _clip_colors(colors, clip=True, gray=0.2, warn_invalid=False): else: colors[under | over] = gray if warn_invalid: - msg = 'Clipped' if clip else 'Invalid' - for i, name in enumerate('rgb'): + msg = "Clipped" if clip else "Invalid" + for i, name in enumerate("rgb"): if np.any(under[:, i]) or np.any(over[:, i]): - warnings._warn_proplot(f'{msg} {name!r} channel.') + warnings._warn_proplot(f"{msg} {name!r} channel.") return colors -def _color_channel(color, channel, space='hcl'): +def _color_channel(color, channel, space="hcl"): """ Get the hue, saturation, or luminance channel value from the input color. The color name `color` can optionally be a string with the format ``'color+x'`` @@ -393,21 +469,21 @@ def _color_channel(color, channel, space='hcl'): # Interpret channel if callable(color) or isinstance(color, Number): return color - if channel == 'hue': + if channel == "hue": channel = 0 - elif channel in ('chroma', 'saturation'): + elif channel in ("chroma", "saturation"): channel = 1 - elif channel == 'luminance': + elif channel == "luminance": channel = 2 else: - raise ValueError(f'Unknown channel {channel!r}.') + raise ValueError(f"Unknown channel {channel!r}.") # Interpret string or RGB tuple offset = 0 if isinstance(color, str): - m = re.search('([-+][0-9.]+)$', color) + m = re.search("([-+][0-9.]+)$", color) if m: offset = float(m.group(0)) - color = color[:m.start()] + color = color[: m.start()] return offset + to_xyz(color, space)[channel] @@ -435,24 +511,21 @@ def _make_segment_data(values, coords=None, ratios=None): # Get coordinates if not np.iterable(values): - raise TypeError('Colors must be iterable, got {values!r}.') + raise TypeError("Colors must be iterable, got {values!r}.") if coords is not None: coords = np.atleast_1d(coords) if ratios is not None: warnings._warn_proplot( - f'Segment coordinates were provided, ignoring ' - f'ratios={ratios!r}.' + f"Segment coordinates were provided, ignoring " f"ratios={ratios!r}." ) if len(coords) != len(values) or coords[0] != 0 or coords[-1] != 1: - raise ValueError( - f'Coordinates must range from 0 to 1, got {coords!r}.' - ) + raise ValueError(f"Coordinates must range from 0 to 1, got {coords!r}.") elif ratios is not None: coords = np.atleast_1d(ratios) if len(coords) != len(values) - 1: raise ValueError( - f'Need {len(values) - 1} ratios for {len(values)} colors, ' - f'but got {len(coords)} ratios.' + f"Need {len(values) - 1} ratios for {len(values)} colors, " + f"but got {len(coords)} ratios." ) coords = np.concatenate(([0], np.cumsum(coords))) coords = coords / np.max(coords) # normalize to 0-1 @@ -510,11 +583,11 @@ def _make_lookup_table(N, data, gamma=1.0, inverse=False): # Allow for *callable* instead of linearly interpolating between segments gammas = np.atleast_1d(gamma) if np.any(gammas < 0.01) or np.any(gammas > 10): - raise ValueError('Gamma can only be in range [0.01,10].') + raise ValueError("Gamma can only be in range [0.01,10].") if callable(data): if len(gammas) > 1: - raise ValueError('Only one gamma allowed for functional segmentdata.') - x = np.linspace(0, 1, N)**gamma + raise ValueError("Only one gamma allowed for functional segmentdata.") + x = np.linspace(0, 1, N) ** gamma lut = np.array(data(x), dtype=float) return lut @@ -522,9 +595,11 @@ def _make_lookup_table(N, data, gamma=1.0, inverse=False): data = np.array(data) shape = data.shape if len(shape) != 2 or shape[1] != 3: - raise ValueError('Mapping data must have shape N x 3.') + raise ValueError("Mapping data must have shape N x 3.") if len(gammas) != 1 and len(gammas) != shape[0] - 1: - raise ValueError(f'Expected {shape[0] - 1} gammas for {shape[0]} coords. Got {len(gamma)}.') # noqa: E501 + raise ValueError( + f"Expected {shape[0] - 1} gammas for {shape[0]} coords. Got {len(gamma)}." + ) # noqa: E501 if len(gammas) == 1: gammas = np.repeat(gammas, shape[:1]) @@ -533,9 +608,9 @@ def _make_lookup_table(N, data, gamma=1.0, inverse=False): y0 = data[:, 1] y1 = data[:, 2] if x[0] != 0.0 or x[-1] != 1.0: - raise ValueError('Data mapping points must start with x=0 and end with x=1.') + raise ValueError("Data mapping points must start with x=0 and end with x=1.") if np.any(np.diff(x) < 0): - raise ValueError('Data mapping points must have x in increasing order.') + raise ValueError("Data mapping points must have x in increasing order.") x = x * (N - 1) # Get distances from the segmentdata entry to the *left* for each requested @@ -562,9 +637,9 @@ def _make_lookup_table(N, data, gamma=1.0, inverse=False): if inverse: # reverse if we are transitioning to *higher* channel value reverse = not reverse if reverse: - offsets[ui:ui + ci] = 1 - (1 - offsets[ui:ui + ci]) ** gamma + offsets[ui : ui + ci] = 1 - (1 - offsets[ui : ui + ci]) ** gamma else: - offsets[ui:ui + ci] **= gamma + offsets[ui : ui + ci] **= gamma # Perform successive linear interpolations rolled up into one equation lut = np.zeros((N,), float) @@ -585,7 +660,7 @@ def _load_colors(path, warn_on_failure=True): """ # Warn or raise error (matches Colormap._from_file behavior) if not os.path.isfile(path): - message = f'Failed to load color data file {path!r}. File not found.' + message = f"Failed to load color data file {path!r}. File not found." if warn_on_failure: warnings._warn_proplot(message) else: @@ -593,16 +668,16 @@ def _load_colors(path, warn_on_failure=True): # Iterate through lines loaded = {} - with open(path, 'r') as fh: + with open(path, "r") as fh: for count, line in enumerate(fh): stripped = line.strip() - if not stripped or stripped[0] == '#': + if not stripped or stripped[0] == "#": continue - pair = tuple(item.strip().lower() for item in line.split(':')) + pair = tuple(item.strip().lower() for item in line.split(":")) if len(pair) != 2 or not REGEX_HEX_SINGLE.match(pair[1]): warnings._warn_proplot( - f'Illegal line #{count + 1} in color file {path!r}:\n' - f'{line!r}\n' + f"Illegal line #{count + 1} in color file {path!r}:\n" + f"{line!r}\n" f'Lines must be formatted as "name: hexcolor".' ) continue @@ -635,8 +710,8 @@ def _standardize_colors(input, space, margin): color = input.pop(name, None) if color is None: continue - if 'grey' in name: - name = name.replace('grey', 'gray') + if "grey" in name: + name = name.replace("grey", "gray") colors.append((name, color)) channels.append(to_xyz(color, space=space)) output[name] = color # required in case "kept" colors are close to each other @@ -673,6 +748,7 @@ class _Colormap(object): """ Mixin class used to add some helper methods. """ + def _get_data(self, ext, alpha=True): """ Return a string containing the colormap colors for saving. @@ -690,15 +766,15 @@ def _get_data(self, ext, alpha=True): colors = self._lut[:-3, :] # Get data string - if ext == 'hex': - data = ', '.join(mcolors.to_hex(color) for color in colors) - elif ext in ('txt', 'rgb'): + if ext == "hex": + data = ", ".join(mcolors.to_hex(color) for color in colors) + elif ext in ("txt", "rgb"): rgb = mcolors.to_rgba if alpha else mcolors.to_rgb data = [rgb(color) for color in colors] - data = '\n'.join(' '.join(f'{num:0.6f}' for num in line) for line in data) + data = "\n".join(" ".join(f"{num:0.6f}" for num in line) for line in data) else: raise ValueError( - f'Invalid extension {ext!r}. Options are: ' + f"Invalid extension {ext!r}. Options are: " "'hex', 'txt', 'rgb', 'rgba'." ) return data @@ -709,12 +785,12 @@ def _make_name(self, suffix=None): leading underscore or more than one identical suffix. """ name = self.name - name = name or '' - if name[:1] != '_': - name = '_' + name - suffix = suffix or 'copy' - suffix = '_' + suffix - if name[-len(suffix):] != suffix: + name = name or "" + if name[:1] != "_": + name = "_" + name + suffix = suffix or "copy" + suffix = "_" + suffix + if name[-len(suffix) :] != suffix: name = name + suffix return name @@ -734,14 +810,14 @@ def _parse_path(self, path, ext=None, subfolder=None): # Get the folder folder = rc.user_folder(subfolder=subfolder) if path is not None: - path = os.path.expanduser(path or '.') # interpret empty string as '.' + path = os.path.expanduser(path or ".") # interpret empty string as '.' if os.path.isdir(path): folder, path = path, None # Get the filename if path is None: path = os.path.join(folder, self.name) if not os.path.splitext(path)[1]: - path = path + '.' + ext # default file extension + path = path + "." + ext # default file extension return path @staticmethod @@ -754,7 +830,7 @@ def _pop_args(*args, names=None, **kwargs): names = names or () if isinstance(names, str): names = (names,) - names = ('name', *names) + names = ("name", *names) args, kwargs = _kwargs_to_args(names, *args, **kwargs) if args[0] is not None and args[1] is None: args[:2] = (None, args[0]) @@ -770,35 +846,36 @@ def _from_file(cls, path, warn_on_failure=False): path = os.path.expanduser(path) name, ext = os.path.splitext(os.path.basename(path)) listed = issubclass(cls, mcolors.ListedColormap) - reversed = name[-2:] == '_r' + reversed = name[-2:] == "_r" # Warn if loading failed during `register_cmaps` or `register_cycles` # but raise error if user tries to load a file. def _warn_or_raise(descrip, error=RuntimeError): - prefix = f'Failed to load colormap or color cycle file {path!r}.' + prefix = f"Failed to load colormap or color cycle file {path!r}." if warn_on_failure: - warnings._warn_proplot(prefix + ' ' + descrip) + warnings._warn_proplot(prefix + " " + descrip) else: - raise error(prefix + ' ' + descrip) + raise error(prefix + " " + descrip) + if not os.path.isfile(path): - return _warn_or_raise('File not found.', FileNotFoundError) + return _warn_or_raise("File not found.", FileNotFoundError) # Directly read segmentdata json file # NOTE: This is special case! Immediately return name and cmap ext = ext[1:] - if ext == 'json': + if ext == "json": if listed: - return _warn_or_raise('Cannot load cycles from JSON files.') + return _warn_or_raise("Cannot load cycles from JSON files.") try: - with open(path, 'r') as fh: + with open(path, "r") as fh: data = json.load(fh) except json.JSONDecodeError: - return _warn_or_raise('JSON decoding error.', json.JSONDecodeError) + return _warn_or_raise("JSON decoding error.", json.JSONDecodeError) kw = {} - for key in ('cyclic', 'gamma', 'gamma1', 'gamma2', 'space'): + for key in ("cyclic", "gamma", "gamma1", "gamma2", "space"): if key in data: kw[key] = data.pop(key, None) - if 'red' in data: + if "red" in data: cmap = ContinuousColormap(name, data) else: cmap = PerceptualColormap(name, data, **kw) @@ -807,29 +884,29 @@ def _warn_or_raise(descrip, error=RuntimeError): return cmap # Read .rgb and .rgba files - if ext in ('txt', 'rgb'): + if ext in ("txt", "rgb"): # Load file # NOTE: This appears to be biggest import time bottleneck! Increases # time from 0.05s to 0.2s, with numpy loadtxt or with this regex thing. - delim = re.compile(r'[,\s]+') + delim = re.compile(r"[,\s]+") data = [ delim.split(line.strip()) for line in open(path) - if line.strip() and line.strip()[0] != '#' + if line.strip() and line.strip()[0] != "#" ] try: data = [[float(num) for num in line] for line in data] except ValueError: return _warn_or_raise( - 'Expected a table of comma or space-separated floats.' + "Expected a table of comma or space-separated floats." ) # Build x-coordinates and standardize shape data = np.array(data) if data.shape[1] not in (3, 4): return _warn_or_raise( - f'Expected 3 or 4 columns of floats. Got {data.shape[1]} columns.' + f"Expected 3 or 4 columns of floats. Got {data.shape[1]} columns." ) - if ext[0] != 'x': # i.e. no x-coordinates specified explicitly + if ext[0] != "x": # i.e. no x-coordinates specified explicitly x = np.linspace(0, 1, data.shape[0]) else: x, data = data[:, 0], data[:, 1:] @@ -837,43 +914,41 @@ def _warn_or_raise(descrip, error=RuntimeError): # Load XML files created with scivizcolor # Adapted from script found here: # https://sciviscolor.org/matlab-matplotlib-pv44/ - elif ext == 'xml': + elif ext == "xml": try: doc = ElementTree.parse(path) except ElementTree.ParseError: - return _warn_or_raise('XML parsing error.', ElementTree.ParseError) + return _warn_or_raise("XML parsing error.", ElementTree.ParseError) x, data = [], [] - for s in doc.getroot().findall('.//Point'): + for s in doc.getroot().findall(".//Point"): # Verify keys - if any(key not in s.attrib for key in 'xrgb'): + if any(key not in s.attrib for key in "xrgb"): return _warn_or_raise( - 'Missing an x, r, g, or b key inside one or more tags.' + "Missing an x, r, g, or b key inside one or more tags." ) # Get data color = [] - for key in 'rgbao': # o for opacity + for key in "rgbao": # o for opacity if key not in s.attrib: continue color.append(float(s.attrib[key])) - x.append(float(s.attrib['x'])) + x.append(float(s.attrib["x"])) data.append(color) # Convert to array if not all( len(data[0]) == len(color) and len(color) in (3, 4) for color in data ): return _warn_or_raise( - 'Unexpected channel number or mixed channels across tags.' + "Unexpected channel number or mixed channels across tags." ) # Read hex strings - elif ext == 'hex': + elif ext == "hex": # Read arbitrary format string = open(path).read() # into single string data = REGEX_HEX_MULTI.findall(string) if len(data) < 2: - return _warn_or_raise( - 'Failed to find 6-digit or 8-digit HEX strings.' - ) + return _warn_or_raise("Failed to find 6-digit or 8-digit HEX strings.") # Convert to array x = np.linspace(0, 1, len(data)) data = [to_rgb(color) for color in data] @@ -881,9 +956,9 @@ def _warn_or_raise(descrip, error=RuntimeError): # Invalid extension else: return _warn_or_raise( - 'Unknown colormap file extension {ext!r}. Options are: ' - + ', '.join(map(repr, ('json', 'txt', 'rgb', 'hex'))) - + '.' + "Unknown colormap file extension {ext!r}. Options are: " + + ", ".join(map(repr, ("json", "txt", "rgb", "hex"))) + + "." ) # Standardize and reverse if necessary to cmap @@ -909,23 +984,24 @@ class ContinuousColormap(mcolors.LinearSegmentedColormap, _Colormap): r""" Replacement for `~matplotlib.colors.LinearSegmentedColormap`. """ + def __str__(self): - return type(self).__name__ + f'(name={self.name!r})' + return type(self).__name__ + f"(name={self.name!r})" def __repr__(self): string = f" 'name': {self.name!r},\n" - if hasattr(self, '_space'): + if hasattr(self, "_space"): string += f" 'space': {self._space!r},\n" - if hasattr(self, '_cyclic'): + if hasattr(self, "_cyclic"): string += f" 'cyclic': {self._cyclic!r},\n" for key, data in self._segmentdata.items(): if callable(data): - string += f' {key!r}: ,\n' + string += f" {key!r}: ,\n" else: stop = data[-1][1] start = data[0][2] - string += f' {key!r}: [{start:.2f}, ..., {stop:.2f}],\n' - return type(self).__name__ + '({\n' + string + '})' + string += f" {key!r}: [{start:.2f}, ..., {stop:.2f}],\n" + return type(self).__name__ + "({\n" + string + "})" @docstring._snippet_manager def __init__(self, *args, gamma=1, alpha=None, cyclic=False, **kwargs): @@ -959,14 +1035,14 @@ def __init__(self, *args, gamma=1, alpha=None, cyclic=False, **kwargs): """ # NOTE: Additional keyword args should raise matplotlib error name, segmentdata, N, kwargs = self._pop_args( - *args, names=('segmentdata', 'N'), **kwargs + *args, names=("segmentdata", "N"), **kwargs ) if not isinstance(segmentdata, dict): - raise ValueError(f'Invalid segmentdata {segmentdata}. Must be a dict.') - N = _not_none(N, rc['image.lut']) - data = _pop_props(segmentdata, 'rgba', 'hsla') + raise ValueError(f"Invalid segmentdata {segmentdata}. Must be a dict.") + N = _not_none(N, rc["image.lut"]) + data = _pop_props(segmentdata, "rgba", "hsla") if segmentdata: - raise ValueError(f'Invalid segmentdata keys {tuple(segmentdata)}.') + raise ValueError(f"Invalid segmentdata keys {tuple(segmentdata)}.") super().__init__(name, data, N=N, gamma=gamma, **kwargs) self._cyclic = cyclic if alpha is not None: @@ -1013,11 +1089,11 @@ def append(self, *args, ratios=None, name=None, N=None, **kwargs): if not args: return self if not all(isinstance(cmap, mcolors.LinearSegmentedColormap) for cmap in args): - raise TypeError(f'Arguments {args!r} must be LinearSegmentedColormaps.') + raise TypeError(f"Arguments {args!r} must be LinearSegmentedColormaps.") # PerceptualColormap --> ContinuousColormap conversions cmaps = [self, *args] - spaces = {getattr(cmap, '_space', None) for cmap in cmaps} + spaces = {getattr(cmap, "_space", None) for cmap in cmaps} to_continuous = len(spaces) > 1 # mixed colorspaces *or* mixed types if to_continuous: for i, cmap in enumerate(cmaps): @@ -1028,7 +1104,7 @@ def append(self, *args, ratios=None, name=None, N=None, **kwargs): # we never interpolate between end colors of different colormaps segmentdata = {} if name is None: - name = '_' + '_'.join(cmap.name for cmap in cmaps) + name = "_" + "_".join(cmap.name for cmap in cmaps) if not np.iterable(ratios): ratios = [1] * len(cmaps) ratios = np.asarray(ratios) / np.sum(ratios) @@ -1041,6 +1117,7 @@ def append(self, *args, ratios=None, name=None, N=None, **kwargs): # embed 'funcs' into the definition using a keyword argument. datas = [cmap._segmentdata[key] for cmap in cmaps] if all(map(callable, datas)): # expand range from x-to-w to 0-1 + def xyy(ix, funcs=datas): # noqa: E306 ix = np.atleast_1d(ix) kx = np.empty(ix.shape) @@ -1066,23 +1143,23 @@ def xyy(ix, funcs=datas): # noqa: E306 else: raise TypeError( - 'Cannot merge colormaps with mixed callable ' - 'and non-callable segment data.' + "Cannot merge colormaps with mixed callable " + "and non-callable segment data." ) segmentdata[key] = xyy # Handle gamma values ikey = None - if key == 'saturation': - ikey = 'gamma1' - elif key == 'luminance': - ikey = 'gamma2' + if key == "saturation": + ikey = "gamma1" + elif key == "luminance": + ikey = "gamma2" if not ikey or ikey in kwargs: continue gamma = [] callable_ = all(map(callable, datas)) for cmap in cmaps: - igamma = getattr(cmap, '_' + ikey) + igamma = getattr(cmap, "_" + ikey) if not np.iterable(igamma): if callable_: igamma = (igamma,) @@ -1092,8 +1169,8 @@ def xyy(ix, funcs=datas): # noqa: E306 if callable_: if any(igamma != gamma[0] for igamma in gamma[1:]): warnings._warn_proplot( - 'Cannot use multiple segment gammas when concatenating ' - f'callable segments. Using the first gamma of {gamma[0]}.' + "Cannot use multiple segment gammas when concatenating " + f"callable segments. Using the first gamma of {gamma[0]}." ) gamma = gamma[0] kwargs[ikey] = gamma @@ -1151,7 +1228,7 @@ def cut(self, cut=None, name=None, left=None, right=None, **kwargs): # Decompose cut into two truncations followed by concatenation if 0.5 - offset < left or 0.5 + offset > right: - raise ValueError(f'Invalid cut={cut} for left={left} and right={right}.') + raise ValueError(f"Invalid cut={cut} for left={left} and right={right}.") if name is None: name = self._make_name() cmap_left = self.truncate(left, 0.5 - offset) @@ -1162,13 +1239,13 @@ def cut(self, cut=None, name=None, left=None, right=None, **kwargs): args = [] if cut < 0: ratio = 0.5 - 0.5 * abs(cut) # ratio for flanks on either side - space = getattr(self, '_space', None) or 'rgb' + space = getattr(self, "_space", None) or "rgb" xyza = to_xyza(self(0.5), space=space) segmentdata = { - key: _make_segment_data(x) for key, x in zip(space + 'a', xyza) + key: _make_segment_data(x) for key, x in zip(space + "a", xyza) } args.append(type(self)(DEFAULT_NAME, segmentdata, self.N)) - kwargs.setdefault('ratios', (ratio, abs(cut), ratio)) + kwargs.setdefault("ratios", (ratio, abs(cut), ratio)) args.append(cmap_right) return cmap_left.append(*args, name=name, **kwargs) @@ -1196,19 +1273,19 @@ def reversed(self, name=None, **kwargs): segmentdata = { key: ( (lambda x, func=data: func(x)) - if callable(data) else - [(1.0 - x, y1, y0) for x, y0, y1 in reversed(data)] + if callable(data) + else [(1.0 - x, y1, y0) for x, y0, y1 in reversed(data)] ) for key, data in self._segmentdata.items() } # Reverse gammas if name is None: - name = self._make_name(suffix='r') - for key in ('gamma1', 'gamma2'): + name = self._make_name(suffix="r") + for key in ("gamma1", "gamma2"): if key in kwargs: continue - gamma = getattr(self, '_' + key, None) + gamma = getattr(self, "_" + key, None) if gamma is not None and np.iterable(gamma): kwargs[key] = gamma[::-1] @@ -1245,32 +1322,32 @@ def save(self, path=None, alpha=True): # cmap.append() embeds functions as keyword arguments, this seems to make it # *impossible* to load back up the function with FunctionType (error message: # arg 5 (closure) must be tuple). Instead use this brute force workaround. - filename = self._parse_path(path, ext='json', subfolder='cmaps') + filename = self._parse_path(path, ext="json", subfolder="cmaps") _, ext = os.path.splitext(filename) - if ext[1:] != 'json': + if ext[1:] != "json": # Save lookup table colors data = self._get_data(ext[1:], alpha=alpha) - with open(filename, 'w') as fh: + with open(filename, "w") as fh: fh.write(data) else: # Save segment data itself data = {} for key, value in self._segmentdata.items(): if callable(value): - x = np.linspace(0, 1, rc['image.lut']) # just save the transitions + x = np.linspace(0, 1, rc["image.lut"]) # just save the transitions y = np.array([value(_) for _ in x]).squeeze() value = np.vstack((x, y, y)).T data[key] = np.asarray(value).astype(float).tolist() keys = () if isinstance(self, PerceptualColormap): - keys = ('cyclic', 'gamma1', 'gamma2', 'space') + keys = ("cyclic", "gamma1", "gamma2", "space") elif isinstance(self, ContinuousColormap): - keys = ('cyclic', 'gamma') + keys = ("cyclic", "gamma") for key in keys: # add all attrs to dictionary - data[key] = getattr(self, '_' + key) - with open(filename, 'w') as fh: + data[key] = getattr(self, "_" + key) + with open(filename, "w") as fh: json.dump(data, fh, indent=4) - print(f'Saved colormap to {filename!r}.') + print(f"Saved colormap to {filename!r}.") def set_alpha(self, alpha, coords=None, ratios=None): """ @@ -1296,7 +1373,7 @@ def set_alpha(self, alpha, coords=None, ratios=None): DiscreteColormap.set_alpha """ alpha = _make_segment_data(alpha, coords=coords, ratios=ratios) - self._segmentdata['alpha'] = alpha + self._segmentdata["alpha"] = alpha self._isinit = False def set_cyclic(self, b): @@ -1333,11 +1410,11 @@ def shifted(self, shift=180, name=None, **kwargs): if shift == 0: return self if name is None: - name = self._make_name(suffix='s') + name = self._make_name(suffix="s") if not self._cyclic: warnings._warn_proplot( - f'Shifting non-cyclic colormap {self.name!r}. To suppress this ' - 'warning use cmap.set_cyclic(True) or Colormap(..., cyclic=True).' + f"Shifting non-cyclic colormap {self.name!r}. To suppress this " + "warning use cmap.set_cyclic(True) or Colormap(..., cyclic=True)." ) self._cyclic = True ratios = (1 - shift, shift) @@ -1386,6 +1463,7 @@ def truncate(self, left=None, right=None, name=None, **kwargs): # the lambda function it gets overwritten in the loop! Must embed # the old callable in the new one as a default keyword arg. if callable(data): + def xyy(x, func=data): return func(left + x * (right - left)) @@ -1397,7 +1475,7 @@ def xyy(x, func=data): x = xyy[:, 0] l = np.searchsorted(x, left) # first x value > left # noqa r = np.searchsorted(x, right) - 1 # last x value < right - xc = xyy[l:r + 1, :].copy() + xc = xyy[l : r + 1, :].copy() xl = xyy[l - 1, 1:] + (left - x[l - 1]) * ( (xyy[l, 1:] - xyy[l - 1, 1:]) / (x[l] - x[l - 1]) ) @@ -1409,26 +1487,26 @@ def xyy(x, func=data): # Retain the corresponding gamma *segments* segmentdata[key] = xyy - if key == 'saturation': - ikey = 'gamma1' - elif key == 'luminance': - ikey = 'gamma2' + if key == "saturation": + ikey = "gamma1" + elif key == "luminance": + ikey = "gamma2" else: continue if ikey in kwargs: continue - gamma = getattr(self, '_' + ikey) + gamma = getattr(self, "_" + ikey) if np.iterable(gamma): if callable(xyy): if any(igamma != gamma[0] for igamma in gamma[1:]): warnings._warn_proplot( - 'Cannot use multiple segment gammas when ' - 'truncating colormap. Using the first gamma ' - f'of {gamma[0]}.' + "Cannot use multiple segment gammas when " + "truncating colormap. Using the first gamma " + f"of {gamma[0]}." ) gamma = gamma[0] else: - igamma = gamma[l - 1:r + 1] + igamma = gamma[l - 1 : r + 1] if len(igamma) == 0: # TODO: issue warning? gamma = gamma[0] else: @@ -1438,8 +1516,14 @@ def xyy(x, func=data): return self.copy(name, segmentdata, **kwargs) def copy( - self, name=None, segmentdata=None, N=None, *, - alpha=None, gamma=None, cyclic=None + self, + name=None, + segmentdata=None, + N=None, + *, + alpha=None, + gamma=None, + cyclic=None, ): """ Return a new colormap with relevant properties copied from this one @@ -1469,8 +1553,7 @@ def copy( if N is None: N = self.N cmap = ContinuousColormap( - name, segmentdata, N, - alpha=alpha, gamma=gamma, cyclic=cyclic + name, segmentdata, N, alpha=alpha, gamma=gamma, cyclic=cyclic ) cmap._rgba_bad = self._rgba_bad cmap._rgba_under = self._rgba_under @@ -1503,7 +1586,7 @@ def to_discrete(self, samples=10, name=None, **kwargs): if isinstance(samples, Integral): samples = np.linspace(0, 1, samples) elif not np.iterable(samples): - raise TypeError('Samples must be integer or iterable.') + raise TypeError("Samples must be integer or iterable.") samples = np.asarray(samples) colors = self(samples) if name is None: @@ -1560,11 +1643,11 @@ def from_list(cls, *args, **kwargs): """ # Get coordinates name, colors, ratios, kwargs = cls._pop_args( - *args, names=('colors', 'ratios'), **kwargs + *args, names=("colors", "ratios"), **kwargs ) coords = None if not np.iterable(colors): - raise TypeError('Colors must be iterable.') + raise TypeError("Colors must be iterable.") if ( np.iterable(colors[0]) and len(colors[0]) == 2 @@ -1574,19 +1657,16 @@ def from_list(cls, *args, **kwargs): colors = [to_rgba(color) for color in colors] # Build segmentdata - keys = ('red', 'green', 'blue', 'alpha') + keys = ("red", "green", "blue", "alpha") cdict = {} for key, values in zip(keys, zip(*colors)): cdict[key] = _make_segment_data(values, coords, ratios) return cls(name, cdict, **kwargs) # Deprecated - to_listed = warnings._rename_objs( - '0.8.0', - to_listed=to_discrete - ) + to_listed = warnings._rename_objs("0.8.0", to_listed=to_discrete) concatenate, punched, truncated, updated = warnings._rename_objs( - '0.6.0', + "0.6.0", concatenate=append, punched=cut, truncated=truncate, @@ -1598,15 +1678,16 @@ class DiscreteColormap(mcolors.ListedColormap, _Colormap): r""" Replacement for `~matplotlib.colors.ListedColormap`. """ + def __str__(self): - return f'DiscreteColormap(name={self.name!r})' + return f"DiscreteColormap(name={self.name!r})" def __repr__(self): colors = [c if isinstance(c, str) else to_hex(c) for c in self.colors] - string = 'DiscreteColormap({\n' + string = "DiscreteColormap({\n" string += f" 'name': {self.name!r},\n" string += f" 'colors': {colors!r},\n" - string += '})' + string += "})" return string def __init__(self, colors, name=None, N=None, alpha=None, **kwargs): @@ -1678,10 +1759,10 @@ def append(self, *args, name=None, N=None, **kwargs): if not args: return self if not all(isinstance(cmap, mcolors.ListedColormap) for cmap in args): - raise TypeError(f'Arguments {args!r} must be DiscreteColormap.') + raise TypeError(f"Arguments {args!r} must be DiscreteColormap.") cmaps = (self, *args) if name is None: - name = '_' + '_'.join(cmap.name for cmap in cmaps) + name = "_" + "_".join(cmap.name for cmap in cmaps) colors = [color for cmap in cmaps for color in cmap.colors] N = _not_none(N, len(colors)) return self.copy(colors, name, N, **kwargs) @@ -1709,12 +1790,12 @@ def save(self, path=None, alpha=True): -------- ContinuousColormap.save """ - filename = self._parse_path(path, ext='hex', subfolder='cycles') + filename = self._parse_path(path, ext="hex", subfolder="cycles") _, ext = os.path.splitext(filename) data = self._get_data(ext[1:], alpha=alpha) - with open(filename, 'w') as fh: + with open(filename, "w") as fh: fh.write(data) - print(f'Saved colormap to {filename!r}.') + print(f"Saved colormap to {filename!r}.") def set_alpha(self, alpha): """ @@ -1751,7 +1832,7 @@ def reversed(self, name=None, **kwargs): matplotlib.colors.ListedColormap.reversed """ if name is None: - name = self._make_name(suffix='r') + name = self._make_name(suffix="r") colors = self.colors[::-1] cmap = self.copy(colors, name, **kwargs) cmap._rgba_under, cmap._rgba_over = cmap._rgba_over, cmap._rgba_under @@ -1775,7 +1856,7 @@ def shifted(self, shift=1, name=None): if not shift: return self if name is None: - name = self._make_name(suffix='s') + name = self._make_name(suffix="s") shift = shift % len(self.colors) colors = list(self.colors) colors = colors[shift:] + colors[:shift] @@ -1862,7 +1943,7 @@ def from_file(cls, path, *, warn_on_failure=False): # Rename methods concatenate, truncated, updated = warnings._rename_objs( - '0.6.0', + "0.6.0", concatenate=append, truncated=truncate, updated=copy, @@ -1874,10 +1955,17 @@ class PerceptualColormap(ContinuousColormap): A `ContinuousColormap` with linear transitions across hue, saturation, and luminance rather than red, blue, and green. """ + @docstring._snippet_manager def __init__( - self, *args, space=None, clip=True, gamma=None, gamma1=None, gamma2=None, - **kwargs + self, + *args, + space=None, + clip=True, + gamma=None, + gamma1=None, + gamma2=None, + **kwargs, ): """ Parameters @@ -1927,14 +2015,14 @@ def __init__( """ # Checks name, segmentdata, N, kwargs = self._pop_args( - *args, names=('segmentdata', 'N'), **kwargs + *args, names=("segmentdata", "N"), **kwargs ) - data = _pop_props(segmentdata, 'hsla') + data = _pop_props(segmentdata, "hsla") if segmentdata: - raise ValueError(f'Invalid segmentdata keys {tuple(segmentdata)}.') + raise ValueError(f"Invalid segmentdata keys {tuple(segmentdata)}.") space = _not_none(space, DEFAULT_SPACE).lower() - if space not in ('rgb', 'hsv', 'hpl', 'hsl', 'hcl'): - raise ValueError(f'Unknown colorspace {space!r}.') + if space not in ("rgb", "hsv", "hpl", "hsl", "hcl"): + raise ValueError(f"Unknown colorspace {space!r}.") # Convert color strings to channel values for key, array in data.items(): if callable(array): # permit callable @@ -1957,7 +2045,7 @@ def _init(self): each value in the lookup table from ``self._space`` to RGB. """ # First generate the lookup table - channels = ('hue', 'saturation', 'luminance') + channels = ("hue", "saturation", "luminance") inverses = (False, False, True) # weight low chroma, high luminance gammas = (1.0, self._gamma1, self._gamma2) self._lut_hsl = np.ones((self.N + 3, 4), float) # fill @@ -1965,9 +2053,9 @@ def _init(self): self._lut_hsl[:-3, i] = _make_lookup_table( self.N, self._segmentdata[channel], gamma, inverse ) - if 'alpha' in self._segmentdata: + if "alpha" in self._segmentdata: self._lut_hsl[:-3, 3] = _make_lookup_table( - self.N, self._segmentdata['alpha'] + self.N, self._segmentdata["alpha"] ) self._lut_hsl[:-3, 0] %= 360 @@ -1999,9 +2087,18 @@ def set_gamma(self, gamma=None, gamma1=None, gamma2=None): self._init() def copy( - self, name=None, segmentdata=None, N=None, *, - alpha=None, gamma=None, cyclic=None, - clip=None, gamma1=None, gamma2=None, space=None + self, + name=None, + segmentdata=None, + N=None, + *, + alpha=None, + gamma=None, + cyclic=None, + clip=None, + gamma1=None, + gamma2=None, + space=None, ): """ Return a new colormap with relevant properties copied from this one @@ -2039,9 +2136,15 @@ def copy( if N is None: N = self.N cmap = PerceptualColormap( - name, segmentdata, N, - alpha=alpha, clip=clip, cyclic=cyclic, - gamma1=gamma1, gamma2=gamma2, space=space + name, + segmentdata, + N, + alpha=alpha, + clip=clip, + cyclic=cyclic, + gamma1=gamma1, + gamma2=gamma2, + space=space, ) cmap._rgba_bad = self._rgba_bad cmap._rgba_under = self._rgba_under @@ -2075,7 +2178,7 @@ def to_continuous(self, name=None, **kwargs): @classmethod @docstring._snippet_manager - @warnings._rename_kwargs('0.7.0', fade='saturation', shade='luminance') + @warnings._rename_kwargs("0.7.0", fade="saturation", shade="luminance") def from_color(cls, *args, **kwargs): """ Return a simple monochromatic "sequential" colormap that blends from white @@ -2115,22 +2218,24 @@ def from_color(cls, *args, **kwargs): PerceptualColormap.from_list """ name, color, space, kwargs = cls._pop_args( - *args, names=('color', 'space'), **kwargs + *args, names=("color", "space"), **kwargs ) space = _not_none(space, DEFAULT_SPACE).lower() - props = _pop_props(kwargs, 'hsla') - if props.get('hue', None) is not None: + props = _pop_props(kwargs, "hsla") + if props.get("hue", None) is not None: raise TypeError("from_color() got an unexpected keyword argument 'hue'") hue, saturation, luminance, alpha = to_xyza(color, space) - alpha_fade = props.pop('alpha', 1) - luminance_fade = props.pop('luminance', 100) - saturation_fade = props.pop('saturation', saturation) + alpha_fade = props.pop("alpha", 1) + luminance_fade = props.pop("luminance", 100) + saturation_fade = props.pop("saturation", saturation) return cls.from_hsl( - name, hue=hue, space=space, + name, + hue=hue, + space=space, alpha=(alpha_fade, alpha), saturation=(saturation_fade, saturation), luminance=(luminance_fade, luminance), - **kwargs + **kwargs, ) @classmethod @@ -2183,15 +2288,15 @@ def from_hsl(cls, *args, **kwargs): PerceptualColormap.from_list """ name, space, ratios, kwargs = cls._pop_args( - *args, names=('space', 'ratios'), **kwargs + *args, names=("space", "ratios"), **kwargs ) cdict = {} - props = _pop_props(kwargs, 'hsla') + props = _pop_props(kwargs, "hsla") for key, default in ( - ('hue', 0), - ('saturation', 100), - ('luminance', (100, 20)), - ('alpha', 1), + ("hue", 0), + ("saturation", 100), + ("luminance", (100, 20)), + ("alpha", 1), ): value = props.pop(key, default) cdict[key] = _make_segment_data(value, ratios=ratios) @@ -2232,20 +2337,21 @@ def from_list(cls, *args, adjust_grays=True, **kwargs): """ # Get coordinates coords = None - space = kwargs.get('space', DEFAULT_SPACE).lower() + space = kwargs.get("space", DEFAULT_SPACE).lower() name, colors, ratios, kwargs = cls._pop_args( - *args, names=('colors', 'ratios'), **kwargs + *args, names=("colors", "ratios"), **kwargs ) if not np.iterable(colors): - raise ValueError(f'Colors must be iterable, got colors={colors!r}') + raise ValueError(f"Colors must be iterable, got colors={colors!r}") if ( - np.iterable(colors[0]) and len(colors[0]) == 2 + np.iterable(colors[0]) + and len(colors[0]) == 2 and not isinstance(colors[0], str) ): coords, colors = zip(*colors) # Build segmentdata - keys = ('hue', 'saturation', 'luminance', 'alpha') + keys = ("hue", "saturation", "luminance", "alpha") hslas = [to_xyza(color, space) for color in colors] cdict = {} for key, values in zip(keys, zip(*hslas)): @@ -2253,7 +2359,7 @@ def from_list(cls, *args, adjust_grays=True, **kwargs): # Adjust grays if adjust_grays: - hues = cdict['hue'] # segment data + hues = cdict["hue"] # segment data for i, color in enumerate(colors): rgb = to_rgb(color) if isinstance(color, str) and REGEX_ADJUST.match(color): @@ -2270,8 +2376,7 @@ def from_list(cls, *args, adjust_grays=True, **kwargs): # Deprecated to_linear_segmented = warnings._rename_objs( - '0.8.0', - to_linear_segmented=to_continuous + "0.8.0", to_linear_segmented=to_continuous ) @@ -2313,11 +2418,11 @@ def _sanitize_levels(levels, minsize=2): # NOTE: Matplotlib does not support datetime colormap levels as of 3.5 levels = inputs._to_numpy_array(levels) if levels.ndim != 1 or levels.size < minsize: - raise ValueError(f'Levels {levels} must be a 1D array with size >= {minsize}.') + raise ValueError(f"Levels {levels} must be a 1D array with size >= {minsize}.") if isinstance(levels, ma.core.MaskedArray): levels = levels.filled(np.nan) if not inputs._is_numeric(levels) or not np.all(np.isfinite(levels)): - raise ValueError(f'Levels {levels} does not support non-numeric cmap levels.') + raise ValueError(f"Levels {levels} does not support non-numeric cmap levels.") diffs = np.sign(np.diff(levels)) if np.all(diffs == 1): descending = False @@ -2325,7 +2430,7 @@ def _sanitize_levels(levels, minsize=2): descending = True levels = levels[::-1] else: - raise ValueError(f'Levels {levels} must be monotonic.') + raise ValueError(f"Levels {levels} must be monotonic.") return levels, descending @@ -2334,16 +2439,23 @@ class DiscreteNorm(mcolors.BoundaryNorm): Meta-normalizer that discretizes the possible color values returned by arbitrary continuous normalizers given a sequence of level boundaries. """ + # See this post: https://stackoverflow.com/a/48614231/4970632 # WARNING: Must be child of BoundaryNorm. Many methods in ColorBarBase # test for class membership, crucially including _process_values(), which # if it doesn't detect BoundaryNorm will try to use DiscreteNorm.inverse(). @warnings._rename_kwargs( - '0.7.0', extend='unique', descending='DiscreteNorm(descending_levels)' + "0.7.0", extend="unique", descending="DiscreteNorm(descending_levels)" ) def __init__( - self, levels, - norm=None, unique=None, step=None, clip=False, ticks=None, labels=None + self, + levels, + norm=None, + unique=None, + step=None, + clip=False, + ticks=None, + labels=None, ): """ Parameters @@ -2402,19 +2514,19 @@ def __init__( if step is None: step = 1.0 if unique is None: - unique = 'neither' + unique = "neither" if not norm: norm = mcolors.Normalize() elif isinstance(norm, mcolors.BoundaryNorm): - raise ValueError('Normalizer cannot be instance of BoundaryNorm.') + raise ValueError("Normalizer cannot be instance of BoundaryNorm.") elif not isinstance(norm, mcolors.Normalize): - raise ValueError('Normalizer must be instance of Normalize.') - uniques = ('min', 'max', 'both', 'neither') + raise ValueError("Normalizer must be instance of Normalize.") + uniques = ("min", "max", "both", "neither") if unique not in uniques: raise ValueError( - f'Unknown unique setting {unique!r}. Options are: ' - + ', '.join(map(repr, uniques)) - + '.' + f"Unknown unique setting {unique!r}. Options are: " + + ", ".join(map(repr, uniques)) + + "." ) # Process level boundaries and centers @@ -2423,7 +2535,7 @@ def __init__( # Instead user-reversed levels will always get passed here just as # they are passed to SegmentedNorm inside plot.py levels, descending = _sanitize_levels(levels) - vcenter = getattr(norm, 'vcenter', None) + vcenter = getattr(norm, "vcenter", None) vmin = norm.vmin = np.min(levels) vmax = norm.vmax = np.max(levels) bins, _ = _sanitize_levels(norm(levels)) @@ -2440,10 +2552,10 @@ def __init__( # minimum 0 maximum 1, would mess up color distribution. However this is still # not perfect... get asymmetric color intensity either side of central point. # So we add special handling for diverging norms below to improve symmetry. - if unique in ('min', 'both'): + if unique in ("min", "both"): scale = levels[0] - levels[1] if len(levels) == 2 else mids[1] - mids[2] mids[0] += step * scale - if unique in ('max', 'both'): + if unique in ("max", "both"): scale = levels[-1] - levels[-2] if len(levels) == 2 else mids[-2] - mids[-3] mids[-1] += step * scale mmin = np.min(mids) @@ -2527,7 +2639,7 @@ def inverse(self, value): # noqa: U100 ValueError Inversion after discretization is impossible. """ - raise ValueError('DiscreteNorm is not invertible.') + raise ValueError("DiscreteNorm is not invertible.") @property def descending(self): @@ -2542,7 +2654,10 @@ class SegmentedNorm(mcolors.Normalize): Normalizer that scales data linearly with respect to the interpolated index in an arbitrary monotonic level sequence. """ - def __init__(self, levels, vcenter=None, vmin=None, vmax=None, clip=None, fair=True): # noqa: E501 + + def __init__( + self, levels, vcenter=None, vmin=None, vmax=None, clip=None, fair=True + ): # noqa: E501 """ Parameters ---------- @@ -2593,7 +2708,7 @@ def __init__(self, levels, vcenter=None, vmin=None, vmax=None, clip=None, fair=T vmax = np.max(levels) if vcenter is not None: center = _interpolate_extrapolate_vector(vcenter, levels, dest) - idxs, = np.where(np.isclose(vcenter, levels)) + (idxs,) = np.where(np.isclose(vcenter, levels)) if fair: delta = center - 0.5 delta = max(-(dest[0] - delta), dest[-1] - delta - 1) @@ -2603,7 +2718,7 @@ def __init__(self, levels, vcenter=None, vmin=None, vmax=None, clip=None, fair=T dest2 = np.linspace(0.5, 1, len(levels) - idxs[0]) dest = np.append(dest1, dest2[1:]) else: - raise ValueError(f'Center {vcenter} not in level list {levels}.') + raise ValueError(f"Center {vcenter} not in level list {levels}.") super().__init__(vmin=vmin, vmax=vmax, clip=clip) self.vcenter = vcenter # used for DiscreteNorm self._x = self.boundaries = levels # 'boundaries' are used in PlotAxes @@ -2651,8 +2766,9 @@ class DivergingNorm(mcolors.Normalize): Normalizer that ensures some central data value lies at the central colormap color. The default central value is ``0``. """ + def __str__(self): - return type(self).__name__ + f'(center={self.vcenter!r})' + return type(self).__name__ + f"(center={self.vcenter!r})" def __init__(self, vcenter=0, vmin=None, vmax=None, clip=None, fair=True): """ @@ -2706,7 +2822,7 @@ def __call__(self, value, clip=None): if clip: # note that np.clip can handle masked arrays value = np.clip(value, self.vmin, self.vmax) if self.vmin > self.vmax: - raise ValueError('vmin must be less than or equal to vmax.') + raise ValueError("vmin must be less than or equal to vmax.") elif self.vmin == self.vmax: x = [self.vmin, self.vmax] y = [0.0, 0.0] @@ -2746,7 +2862,7 @@ def _init_color_database(): database = mcolors._colors_full_map if not isinstance(database, ColorDatabase): database = mcolors._colors_full_map = ColorDatabase(database) - if hasattr(mcolors, 'colorConverter'): # suspect deprecation is coming soon + if hasattr(mcolors, "colorConverter"): # suspect deprecation is coming soon mcolors.colorConverter.cache = database.cache mcolors.colorConverter.colors = database return database @@ -2758,8 +2874,8 @@ def _init_cmap_database(): """ # WARNING: Skip over the matplotlib native duplicate entries # with suffixes '_r' and '_shifted'. - if hasattr(mcm, "_cmap_d"): - attr = '_cmap_registry' if hasattr(mcm, '_cmap_registry') else 'cmap_d' + if hasattr(mcm, "_cmap_d") or hasattr(mcm, "_cmap_registry"): + attr = "_cmap_registry" if hasattr(mcm, "_cmap_registry") else "cmap_d" database = getattr(mcm, attr) if mcm.get_cmap is not _get_cmap: mcm.get_cmap = _get_cmap @@ -2767,17 +2883,22 @@ def _init_cmap_database(): mcm.register_cmap = _register_cmap if not isinstance(database, ColormapDatabase): database = { - key: value for key, value in database.items() - if key[-2:] != '_r' and key[-8:] != '_shifted' + key: value + for key, value in database.items() + if key[-2:] != "_r" and key[-8:] != "_shifted" } database = ColormapDatabase(database) setattr(mcm, attr, database) - else: + elif hasattr("mcm", "_gen_cmap_registry"): database = ColormapDatabase(mcm._gen_cmap_registry()) + else: + raise ValueError("ColormapDatabase not initialized") return database _mpl_register_cmap = mcm.register_cmap + + @functools.wraps(_mpl_register_cmap) # noqa: E302 def _register_cmap(*args, **kwargs): """ @@ -2786,7 +2907,7 @@ def _register_cmap(*args, **kwargs): and triggers 100 warnings when importing seaborn. """ with warnings.catch_warnings(): - warnings.simplefilter('ignore', UserWarning) + warnings.simplefilter("ignore", UserWarning) return _mpl_register_cmap(*args, **kwargs) @@ -2798,7 +2919,7 @@ def _get_cmap(name=None, lut=None): because matplotlib now uses _check_in_list with cmap dictionary keys. """ if name is None: - name = rc['image.cmap'] + name = rc["image.cmap"] if isinstance(name, mcolors.Colormap): return name cmap = _cmap_database[name] @@ -2813,21 +2934,21 @@ def _get_cmap_subtype(name, subtype): a useful error message that omits colormaps from other classes. """ # NOTE: Right now this is just used in rc validation but could be used elsewhere - if subtype == 'discrete': + if subtype == "discrete": cls = DiscreteColormap - elif subtype == 'continuous': + elif subtype == "continuous": cls = ContinuousColormap - elif subtype == 'perceptual': + elif subtype == "perceptual": cls = PerceptualColormap else: - raise RuntimeError(f'Invalid subtype {subtype!r}.') + raise RuntimeError(f"Invalid subtype {subtype!r}.") cmap = _cmap_database.get(name, None) if not isinstance(cmap, cls): names = sorted(k for k, v in _cmap_database.items() if isinstance(v, cls)) raise ValueError( - f'Invalid {subtype} colormap name {name!r}. Options are: ' - + ', '.join(map(repr, names)) - + '.' + f"Invalid {subtype} colormap name {name!r}. Options are: " + + ", ".join(map(repr, names)) + + "." ) return cmap @@ -2840,9 +2961,9 @@ def _translate_cmap(cmap, lut=None, cyclic=None, listedthresh=None): # Parse args # WARNING: Apply default 'cyclic' property to native matplotlib colormaps # based on known names. Maybe slightly dangerous but cleanest approach - lut = _not_none(lut, rc['image.lut']) + lut = _not_none(lut, rc["image.lut"]) cyclic = _not_none(cyclic, cmap.name and cmap.name.lower() in CMAPS_CYCLIC) - listedthresh = _not_none(listedthresh, rc['cmap.listedthresh']) + listedthresh = _not_none(listedthresh, rc["cmap.listedthresh"]) # Translate the colormap # WARNING: Here we ignore 'N' in order to respect proplotrc lut sizes @@ -2866,8 +2987,8 @@ def _translate_cmap(cmap, lut=None, cyclic=None, listedthresh=None): pass else: raise ValueError( - f'Invalid colormap type {type(cmap).__name__!r}. ' - 'Must be instance of matplotlib.colors.Colormap.' + f"Invalid colormap type {type(cmap).__name__!r}. " + "Must be instance of matplotlib.colors.Colormap." ) # Apply hidden settings @@ -2882,6 +3003,7 @@ class _ColorCache(dict): """ Replacement for the native color cache. """ + def __getitem__(self, key): """ Get the standard color, colormap color, or color cycle color. @@ -2909,15 +3031,15 @@ def _get_rgba(self, arg, alpha): if isinstance(cmap, DiscreteColormap): if not 0 <= arg[1] < len(cmap.colors): raise ValueError( - f'Color cycle sample for {arg[0]!r} cycle must be ' - f'between 0 and {len(cmap.colors) - 1}, got {arg[1]}.' + f"Color cycle sample for {arg[0]!r} cycle must be " + f"between 0 and {len(cmap.colors) - 1}, got {arg[1]}." ) rgba = cmap.colors[arg[1]] # draw from list of colors else: if not 0 <= arg[1] <= 1: raise ValueError( - f'Colormap sample for {arg[0]!r} colormap must be ' - f'between 0 and 1, got {arg[1]}.' + f"Colormap sample for {arg[0]!r} colormap must be " + f"between 0 and 1, got {arg[1]}." ) rgba = cmap(arg[1]) # get color selection # Return the colormap value @@ -2931,10 +3053,11 @@ class ColorDatabase(MutableMapping, dict): Dictionary subclass used to replace the builtin matplotlib color database. See `~ColorDatabase.__getitem__` for details. """ + _colors_replace = ( - ('grey', 'gray'), # British --> American synonyms - ('ochre', 'ocher'), # ... - ('kelley', 'kelly'), # backwards compatibility to correct spelling + ("grey", "gray"), # British --> American synonyms + ("ochre", "ocher"), # ... + ("kelley", "kelly"), # backwards compatibility to correct spelling ) def __iter__(self): @@ -2993,7 +3116,7 @@ def _parse_key(self, key): Parse the color key. Currently this just translates grays. """ if not isinstance(key, str): - raise ValueError(f'Invalid color name {key!r}. Must be string.') + raise ValueError(f"Invalid color name {key!r}. Must be string.") if isinstance(key, str) and len(key) > 1: # ignore base colors key = key.lower() for sub, rep in self._colors_replace: @@ -3013,8 +3136,9 @@ class ColormapDatabase(MutableMapping, dict): colormap registry. See `~ColormapDatabase.__getitem__` and `~ColormapDatabase.__setitem__` for details. """ - _regex_grays = re.compile(r'\A(grays)(_r|_s)*\Z', flags=re.IGNORECASE) - _regex_suffix = re.compile(r'(_r|_s)*\Z', flags=re.IGNORECASE) + + _regex_grays = re.compile(r"\A(grays)(_r|_s)*\Z", flags=re.IGNORECASE) + _regex_suffix = re.compile(r"(_r|_s)*\Z", flags=re.IGNORECASE) def __iter__(self): yield from dict.__iter__(self) @@ -3067,20 +3191,20 @@ def _translate_deprecated(self, key): # helpfully "redirect" user to SciVisColor cmap when they are trying to # generate open-color monochromatic cmaps and would disallow some color names if isinstance(key, str): - test = self._regex_suffix.sub('', key) + test = self._regex_suffix.sub("", key) else: test = None if not self._has_item(test) and test in CMAPS_REMOVED: version = CMAPS_REMOVED[test] raise ValueError( - f'The colormap name {key!r} was removed in version {version}.' + f"The colormap name {key!r} was removed in version {version}." ) if not self._has_item(test) and test in CMAPS_RENAMED: test_new, version = CMAPS_RENAMED[test] warnings._warn_proplot( - f'The colormap name {test!r} was deprecated in version {version} ' - f'and may be removed in {warnings._next_release()}. Please use ' - f'the colormap name {test_new!r} instead.' + f"The colormap name {test!r} was deprecated in version {version} " + f"and may be removed in {warnings._next_release()}. Please use " + f"the colormap name {test_new!r} instead." ) key = re.sub(test, test_new, key, flags=re.IGNORECASE) return key @@ -3091,11 +3215,11 @@ def _translate_key(self, key, mirror=True): """ # Sanitize key if not isinstance(key, str): - raise KeyError(f'Invalid key {key!r}. Key must be a string.') + raise KeyError(f"Invalid key {key!r}. Key must be a string.") key = key.lower() - key = self._regex_grays.sub(r'greys\2', key) + key = self._regex_grays.sub(r"greys\2", key) # Mirror diverging - reverse = key[-2:] == '_r' + reverse = key[-2:] == "_r" if reverse: key = key[:-2] if mirror and not self._has_item(key): # avoid recursion here @@ -3104,7 +3228,7 @@ def _translate_key(self, key, mirror=True): reverse = not reverse key = key_mirror if reverse: - key = key + '_r' + key = key + "_r" return key def _has_item(self, key): @@ -3120,10 +3244,10 @@ def _get_item(self, key): # Sanitize key key = self._translate_deprecated(key) key = self._translate_key(key, mirror=True) - shift = key[-2:] == '_s' and not self._has_item(key) + shift = key[-2:] == "_s" and not self._has_item(key) if shift: key = key[:-2] - reverse = key[-2:] == '_r' and not self._has_item(key) + reverse = key[-2:] == "_r" and not self._has_item(key) if reverse: key = key[:-2] # Retrieve colormap @@ -3131,9 +3255,9 @@ def _get_item(self, key): value = dict.__getitem__(self, key) # may raise keyerror except KeyError: raise KeyError( - f'Invalid colormap or color cycle name {key!r}. Options are: ' - + ', '.join(map(repr, self)) - + '.' + f"Invalid colormap or color cycle name {key!r}. Options are: " + + ", ".join(map(repr, self)) + + "." ) # Modify colormap if reverse: @@ -3147,9 +3271,9 @@ def _set_item(self, key, value): Add the colormap after validating and converting. """ if not isinstance(key, str): - raise KeyError(f'Invalid key {key!r}. Must be string.') + raise KeyError(f"Invalid key {key!r}. Must be string.") if not isinstance(value, mcolors.Colormap): - raise ValueError('Object is not a colormap.') + raise ValueError("Object is not a colormap.") key = self._translate_key(key, mirror=False) value = _translate_cmap(value) dict.__setitem__(self, key, value) @@ -3166,7 +3290,7 @@ def _set_item(self, key, value): PerceptuallyUniformColormap, LinearSegmentedNorm, ) = warnings._rename_objs( # noqa: E501 - '0.8.0', + "0.8.0", ListedColormap=DiscreteColormap, LinearSegmentedColormap=ContinuousColormap, PerceptuallyUniformColormap=PerceptualColormap, From fcdcd25067199f11f77953df2fd66f6178b6ca4f Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Thu, 10 Aug 2023 16:42:12 +0200 Subject: [PATCH 4/5] fixed erreoneous _ --- proplot/colors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proplot/colors.py b/proplot/colors.py index a836bacf2..ac4e08599 100644 --- a/proplot/colors.py +++ b/proplot/colors.py @@ -2874,7 +2874,7 @@ def _init_cmap_database(): """ # WARNING: Skip over the matplotlib native duplicate entries # with suffixes '_r' and '_shifted'. - if hasattr(mcm, "_cmap_d") or hasattr(mcm, "_cmap_registry"): + if hasattr(mcm, "cmap_d") or hasattr(mcm, "_cmap_registry"): attr = "_cmap_registry" if hasattr(mcm, "_cmap_registry") else "cmap_d" database = getattr(mcm, attr) if mcm.get_cmap is not _get_cmap: From c0fe4c60f54d35fd395b73f6eda7c57f15ebf88f Mon Sep 17 00:00:00 2001 From: Casper van Elteren Date: Fri, 11 Aug 2023 20:18:22 +0200 Subject: [PATCH 5/5] Update proplot/colors.py Co-authored-by: Scott Staniewicz --- proplot/colors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proplot/colors.py b/proplot/colors.py index ac4e08599..e70ce6c03 100644 --- a/proplot/colors.py +++ b/proplot/colors.py @@ -2889,7 +2889,7 @@ def _init_cmap_database(): } database = ColormapDatabase(database) setattr(mcm, attr, database) - elif hasattr("mcm", "_gen_cmap_registry"): + elif hasattr(mcm, "_gen_cmap_registry"): database = ColormapDatabase(mcm._gen_cmap_registry()) else: raise ValueError("ColormapDatabase not initialized")