diff --git a/doc/users/next_whats_new/colormap_get_under_over_bad.rst b/doc/users/next_whats_new/colormap_get_under_over_bad.rst new file mode 100644 index 000000000000..d21e7b4349cc --- /dev/null +++ b/doc/users/next_whats_new/colormap_get_under_over_bad.rst @@ -0,0 +1,7 @@ +Get under/over/bad colors of Colormap objects +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`matplotlib.colors.Colormap` now has methods +`~.colors.Colormap.get_under`, `~.colors.Colormap.get_over`, +`~.colors.Colormap.get_bad` for the colors used for out-of-range and masked +values. diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 95151c7b9779..57293aaa664a 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -110,6 +110,8 @@ def __delitem__(self, key): _colors_full_map.update(BASE_COLORS) _colors_full_map = _ColorMapping(_colors_full_map) +_REPR_PNG_SIZE = (512, 64) + def get_named_colors_mapping(): """Return the global mapping of names to named colors.""" @@ -614,6 +616,12 @@ def __copy__(self): cmapobject._global = False return cmapobject + def get_bad(self): + """Get the color for masked values.""" + if not self._isinit: + self._init() + return self._lut[self._i_bad] + def set_bad(self, color='k', alpha=None): """Set the color for masked values.""" _warn_if_global_cmap_modified(self) @@ -621,19 +629,27 @@ def set_bad(self, color='k', alpha=None): if self._isinit: self._set_extremes() + def get_under(self): + """Get the color for low out-of-range values.""" + if not self._isinit: + self._init() + return self._lut[self._i_under] + def set_under(self, color='k', alpha=None): - """ - Set the color for low out-of-range values when ``norm.clip = False``. - """ + """Set the color for low out-of-range values.""" _warn_if_global_cmap_modified(self) self._rgba_under = to_rgba(color, alpha) if self._isinit: self._set_extremes() + def get_over(self): + """Get the color for high out-of-range values.""" + if not self._isinit: + self._init() + return self._lut[self._i_over] + def set_over(self, color='k', alpha=None): - """ - Set the color for high out-of-range values when ``norm.clip = False``. - """ + """Set the color for high out-of-range values.""" _warn_if_global_cmap_modified(self) self._rgba_over = to_rgba(color, alpha) if self._isinit: @@ -655,6 +671,7 @@ def _init(self): raise NotImplementedError("Abstract class only") def is_gray(self): + """Return whether the color map is grayscale.""" if not self._isinit: self._init() return (np.all(self._lut[:, 0] == self._lut[:, 1]) and @@ -685,8 +702,8 @@ def reversed(self, name=None): def _repr_png_(self): """Generate a PNG representation of the Colormap.""" - IMAGE_SIZE = (400, 50) - X = np.tile(np.linspace(0, 1, IMAGE_SIZE[0]), (IMAGE_SIZE[1], 1)) + X = np.tile(np.linspace(0, 1, _REPR_PNG_SIZE[0]), + (_REPR_PNG_SIZE[1], 1)) pixels = self(X, bytes=True) png_bytes = io.BytesIO() title = self.name + ' color map' @@ -703,12 +720,36 @@ def _repr_html_(self): """Generate an HTML representation of the Colormap.""" png_bytes = self._repr_png_() png_base64 = base64.b64encode(png_bytes).decode('ascii') - return ('' + self.name + '' + - '') + def color_block(color): + hex_color = to_hex(color, keep_alpha=True) + return (f'
') + + return ('
' + f'{self.name} ' + '
' + '
' + '
' + '
' + f'{color_block(self.get_under())} under' + '
' + '
' + f'bad {color_block(self.get_bad())}' + '
' + '
' + f'over {color_block(self.get_over())}' + '
') class LinearSegmentedColormap(Colormap): diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index 5e0b2044bcf8..6180e789ac95 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -5,6 +5,7 @@ import numpy as np from PIL import Image import pytest +import base64 from numpy.testing import assert_array_equal, assert_array_almost_equal @@ -303,6 +304,9 @@ def test_BoundaryNorm(): cmref.set_under('white') cmshould = mcolors.ListedColormap(['white', 'blue', 'red', 'black']) + assert mcolors.same_color(cmref.get_over(), 'black') + assert mcolors.same_color(cmref.get_under(), 'white') + refnorm = mcolors.BoundaryNorm(bounds, cmref.N) mynorm = mcolors.BoundaryNorm(bounds, cmshould.N, extend='both') assert mynorm.vmin == refnorm.vmin @@ -323,6 +327,8 @@ def test_BoundaryNorm(): cmref.set_under('white') cmshould = mcolors.ListedColormap(['white', 'blue', 'red']) + assert mcolors.same_color(cmref.get_under(), 'white') + assert cmref.N == 2 assert cmshould.N == 3 refnorm = mcolors.BoundaryNorm(bounds, cmref.N) @@ -339,6 +345,8 @@ def test_BoundaryNorm(): cmref.set_over('black') cmshould = mcolors.ListedColormap(['blue', 'red', 'black']) + assert mcolors.same_color(cmref.get_over(), 'black') + assert cmref.N == 2 assert cmshould.N == 3 refnorm = mcolors.BoundaryNorm(bounds, cmref.N) @@ -1160,4 +1168,15 @@ def test_repr_html(): cmap = plt.get_cmap('viridis') html = cmap._repr_html_() assert len(html) > 0 + png = cmap._repr_png_() + assert base64.b64encode(png).decode('ascii') in html assert cmap.name in html + assert html.startswith('') + + +def test_get_under_over_bad(): + cmap = plt.get_cmap('viridis') + assert_array_equal(cmap.get_under(), cmap(-np.inf)) + assert_array_equal(cmap.get_over(), cmap(np.inf)) + assert_array_equal(cmap.get_bad(), cmap(np.nan))