From 5348428b91c8e199c945db24ca2f10c0517a4fc8 Mon Sep 17 00:00:00 2001 From: Fabien Maussion Date: Tue, 2 May 2017 08:53:44 +0200 Subject: [PATCH 01/15] Solve conflicts --- lib/matplotlib/colors.py | 28 +++++++++-- lib/matplotlib/tests/test_colors.py | 76 +++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index ffc259ac3901..c717caf39ad7 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1410,7 +1410,7 @@ class BoundaryNorm(Normalize): interpolation, but using integers seems simpler, and reduces the number of conversions back and forth between integer and floating point. """ - def __init__(self, boundaries, ncolors, clip=False): + def __init__(self, boundaries, ncolors, clip=False, extend='neither'): """ Parameters ---------- @@ -1427,6 +1427,9 @@ def __init__(self, boundaries, ncolors, clip=False): they are below ``boundaries[0]`` or mapped to *ncolors* if they are above ``boundaries[-1]``. These are then converted to valid indices by `Colormap.__call__`. + extend : str, optional + 'neither', 'both', 'min', or 'max': select the colors out of + cmap so that the extensions are considered in the interpolation Notes ----- @@ -1442,7 +1445,24 @@ def __init__(self, boundaries, ncolors, clip=False): self.boundaries = np.asarray(boundaries) self.N = len(self.boundaries) self.Ncmap = ncolors - if self.N - 1 == self.Ncmap: + + # Extension. We use the same trick as colorbar.py and add a fake + # boundary were needed. Since colorbar does it too, we have to use a + # private property for that. + _b = list(boundaries) + if extend == 'both': + _b = [_b[0] - 1] + _b + [_b[-1] + 1] + elif extend == 'min': + _b = [_b[0] - 1] + _b + elif extend == 'max': + _b = _b + [_b[-1] + 1] + self._b = np.array(_b) + + # I am not sure if the private _N is necessary (I dont think so, + # but it should be consistent with boundaries I guess. This _N is + # used for interpolation). + self._N = len(self._b) + if self._N - 1 == self.Ncmap: self._interp = False else: self._interp = True @@ -1460,10 +1480,10 @@ def __call__(self, value, clip=None): else: max_col = self.Ncmap iret = np.zeros(xx.shape, dtype=np.int16) - for i, b in enumerate(self.boundaries): + for i, b in enumerate(self._b): iret[xx >= b] = i if self._interp: - scalefac = (self.Ncmap - 1) / (self.N - 2) + scalefac = float(self.Ncmap - 1) / (self._N - 2) iret = (iret * scalefac).astype(np.int16) iret[xx < self.vmin] = -1 iret[xx >= self.vmax] = max_col diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index b18035150fbb..e87b396b895d 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -265,6 +265,82 @@ def test_BoundaryNorm(): vals = np.ma.masked_invalid([np.Inf]) assert np.all(bn(vals).mask) + # Testing extend keyword + bounds = [1, 2, 3] + cmap = cm.get_cmap('jet') + + refnorm = mcolors.BoundaryNorm([0] + bounds + [4], cmap.N) + mynorm = mcolors.BoundaryNorm(bounds, cmap.N, extend='both') + x = np.random.random(100) + 1.5 + np.testing.assert_array_equal(refnorm(x), mynorm(x)) + + # Min and max + cmref = mcolors.ListedColormap(['blue', 'red']) + cmref.set_over('black') + cmref.set_under('white') + + cmshould = mcolors.ListedColormap(['white', 'blue', 'red', 'black']) + cmshould.set_over(cmshould(cmshould.N)) + cmshould.set_under(cmshould(0)) + + refnorm = mcolors.BoundaryNorm(bounds, cmref.N) + mynorm = mcolors.BoundaryNorm(bounds, cmshould.N, extend='both') + np.testing.assert_array_equal(refnorm.vmin, mynorm.vmin) + np.testing.assert_array_equal(refnorm.vmax, mynorm.vmax) + x = [-1, 1.2, 2.3, 9.6] + np.testing.assert_array_equal(cmshould([0, 1, 2, 3]), cmshould(mynorm(x))) + x = np.random.randn(100) * 10 + 2 + np.testing.assert_array_equal(cmref(refnorm(x)), cmshould(mynorm(x))) + + np.testing.assert_array_equal(-1, mynorm(-1)) + np.testing.assert_array_equal(1, mynorm(1.1)) + np.testing.assert_array_equal(4, mynorm(12)) + + # Just min + cmref = mcolors.ListedColormap(['blue', 'red']) + cmref.set_under('white') + cmshould = mcolors.ListedColormap(['white', 'blue', 'red']) + cmshould.set_under(cmshould(0)) + + np.testing.assert_array_equal(2, cmref.N) + np.testing.assert_array_equal(3, cmshould.N) + refnorm = mcolors.BoundaryNorm(bounds, cmref.N) + mynorm = mcolors.BoundaryNorm(bounds, cmshould.N, extend='min') + np.testing.assert_array_equal(refnorm.vmin, mynorm.vmin) + np.testing.assert_array_equal(refnorm.vmax, mynorm.vmax) + x = [-1, 1.2, 2.3] + np.testing.assert_array_equal(cmshould([0, 1, 2]), cmshould(mynorm(x))) + x = np.random.randn(100) * 10 + 2 + np.testing.assert_array_equal(cmref(refnorm(x)), cmshould(mynorm(x))) + + # Just max + cmref = mcolors.ListedColormap(['blue', 'red']) + cmref.set_over('black') + cmshould = mcolors.ListedColormap(['blue', 'red', 'black']) + cmshould.set_over(cmshould(2)) + + np.testing.assert_array_equal(2, cmref.N) + np.testing.assert_array_equal(3, cmshould.N) + refnorm = mcolors.BoundaryNorm(bounds, cmref.N) + mynorm = mcolors.BoundaryNorm(bounds, cmshould.N, extend='max') + np.testing.assert_array_equal(refnorm.vmin, mynorm.vmin) + np.testing.assert_array_equal(refnorm.vmax, mynorm.vmax) + x = [1.2, 2.3, 4] + np.testing.assert_array_equal(cmshould([0, 1, 2]), cmshould(mynorm(x))) + x = np.random.randn(100) * 10 + 2 + np.testing.assert_array_equal(cmref(refnorm(x)), cmshould(mynorm(x))) + + # General case + bounds = [1, 2, 3, 4] + cmap = cm.get_cmap('jet') + mynorm = mcolors.BoundaryNorm(bounds, cmap.N, extend='both') + refnorm = mcolors.BoundaryNorm([-100] + bounds + [100], cmap.N) + x = np.random.randn(100) * 10 - 5 + ref = refnorm(x) + ref = np.where(ref == 0, -1, ref) + ref = np.where(ref == cmap.N-1, cmap.N, ref) + np.testing.assert_array_equal(ref, mynorm(x)) + @pytest.mark.parametrize("vmin,vmax", [[-1, 2], [3, 1]]) def test_lognorm_invalid(vmin, vmax): From 4be7829094fea5cbb352a44c3d6079cbf88ba3a0 Mon Sep 17 00:00:00 2001 From: Fabien Maussion Date: Wed, 9 Sep 2015 12:21:05 +0200 Subject: [PATCH 02/15] added support for extend in colorbarbase --- lib/matplotlib/colorbar.py | 2 ++ lib/matplotlib/colors.py | 13 +++++++------ lib/matplotlib/tests/test_colors.py | 4 ++++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 7860631e0ce0..e8571ae9ede9 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -430,6 +430,8 @@ def __init__(self, ax, cmap=None, cmap = cm.get_cmap() if norm is None: norm = colors.Normalize() + elif hasattr(norm, 'extend') and norm.extend != 'neither': + extend = norm.extend self.alpha = alpha self.cmap = cmap self.norm = norm diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index c717caf39ad7..2a77bb7d9592 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1439,6 +1439,9 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): If the number of bins doesn't equal *ncolors*, the color is chosen by linear interpolation of the bin number onto color numbers. """ + + if clip and extend != 'neither': + raise ValueError("'clip=True' is not compatible with 'extend'") self.clip = clip self.vmin = boundaries[0] self.vmax = boundaries[-1] @@ -1447,8 +1450,7 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): self.Ncmap = ncolors # Extension. We use the same trick as colorbar.py and add a fake - # boundary were needed. Since colorbar does it too, we have to use a - # private property for that. + # boundary were needed. _b = list(boundaries) if extend == 'both': _b = [_b[0] - 1] + _b + [_b[-1] + 1] @@ -1456,11 +1458,10 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): _b = [_b[0] - 1] + _b elif extend == 'max': _b = _b + [_b[-1] + 1] - self._b = np.array(_b) + self.extend = extend - # I am not sure if the private _N is necessary (I dont think so, - # but it should be consistent with boundaries I guess. This _N is - # used for interpolation). + # needed for the interpolation but should not be seen from outside + self._b = np.array(_b) self._N = len(self._b) if self._N - 1 == self.Ncmap: self._interp = False diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index e87b396b895d..a2d597f70517 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -296,6 +296,10 @@ def test_BoundaryNorm(): np.testing.assert_array_equal(1, mynorm(1.1)) np.testing.assert_array_equal(4, mynorm(12)) + # Test raises + assert_raises(ValueError, mcolors.BoundaryNorm, bounds, cmref.N, + extend='both', clip=True) + # Just min cmref = mcolors.ListedColormap(['blue', 'red']) cmref.set_under('white') From 8cb7b45e09ba0cad5b8b65a7c3b14b84870ea2dd Mon Sep 17 00:00:00 2001 From: Fabien Maussion Date: Tue, 10 Nov 2015 16:35:50 +0100 Subject: [PATCH 03/15] won't override user extend keyword anymore --- lib/matplotlib/colorbar.py | 9 +++-- .../test_colors/boundarynorm_and_colorbar.png | Bin 0 -> 16613 bytes lib/matplotlib/tests/test_colors.py | 31 +++++++++++++++++- 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 lib/matplotlib/tests/baseline_images/test_colors/boundarynorm_and_colorbar.png diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index e8571ae9ede9..718a3974cf4d 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -402,7 +402,7 @@ def __init__(self, ax, cmap=None, boundaries=None, orientation='vertical', ticklocation='auto', - extend='neither', + extend=None, spacing='uniform', # uniform or proportional ticks=None, format=None, @@ -430,8 +430,11 @@ def __init__(self, ax, cmap=None, cmap = cm.get_cmap() if norm is None: norm = colors.Normalize() - elif hasattr(norm, 'extend') and norm.extend != 'neither': - extend = norm.extend + if extend is None: + if hasattr(norm, 'extend'): + extend = norm.extend + else: + extend = 'neither' self.alpha = alpha self.cmap = cmap self.norm = norm diff --git a/lib/matplotlib/tests/baseline_images/test_colors/boundarynorm_and_colorbar.png b/lib/matplotlib/tests/baseline_images/test_colors/boundarynorm_and_colorbar.png new file mode 100644 index 0000000000000000000000000000000000000000..999ac38b92e8df5c3a7bcc4f8c8058b8a1bb6133 GIT binary patch literal 16613 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 Y44$a#(}{F}M Date: Tue, 2 May 2017 09:31:42 +0200 Subject: [PATCH 04/15] Updates for pytest and mpl2 --- lib/matplotlib/colors.py | 1 - .../test_colors/boundarynorm_and_colorbar.png | Bin 16613 -> 16772 bytes lib/matplotlib/tests/test_colors.py | 6 +++--- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 2a77bb7d9592..7c2d86913445 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1439,7 +1439,6 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): If the number of bins doesn't equal *ncolors*, the color is chosen by linear interpolation of the bin number onto color numbers. """ - if clip and extend != 'neither': raise ValueError("'clip=True' is not compatible with 'extend'") self.clip = clip diff --git a/lib/matplotlib/tests/baseline_images/test_colors/boundarynorm_and_colorbar.png b/lib/matplotlib/tests/baseline_images/test_colors/boundarynorm_and_colorbar.png index 999ac38b92e8df5c3a7bcc4f8c8058b8a1bb6133..59062a1a1900f7b7c0b266b9e2cfc7bd8b444ada 100644 GIT binary patch literal 16772 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

%2AMdVf`LR90f0+E7vM1@G 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 Y44$a#(}{F}M Date: Tue, 2 May 2017 09:53:59 +0200 Subject: [PATCH 05/15] Add colorbar example to docs --- examples/api/colorbar_only.py | 90 +++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 examples/api/colorbar_only.py diff --git a/examples/api/colorbar_only.py b/examples/api/colorbar_only.py new file mode 100644 index 000000000000..cc852d942284 --- /dev/null +++ b/examples/api/colorbar_only.py @@ -0,0 +1,90 @@ +''' +==================== +Customized colorbars +==================== + +This example shows how to build colorbars without an attached mappable. +''' + +import matplotlib.pyplot as plt +import matplotlib as mpl + +# Make a figure and axes with dimensions as desired. +fig = plt.figure(figsize=(8, 5)) +ax1 = fig.add_axes([0.05, 0.85, 0.9, 0.1]) +ax2 = fig.add_axes([0.05, 0.6, 0.9, 0.1]) +ax3 = fig.add_axes([0.05, 0.35, 0.9, 0.1]) +ax4 = fig.add_axes([0.05, 0.1, 0.9, 0.1]) + +# Set the colormap and norm to correspond to the data for which +# the colorbar will be used. +cmap = mpl.cm.cool +norm = mpl.colors.Normalize(vmin=5, vmax=10) + +# ColorbarBase derives from ScalarMappable and puts a colorbar +# in a specified axes, so it has everything needed for a +# standalone colorbar. There are many more kwargs, but the +# following gives a basic continuous colorbar with ticks +# and labels. +cb1 = mpl.colorbar.ColorbarBase(ax1, cmap=cmap, + norm=norm, + orientation='horizontal') +cb1.set_label('Some Units') + +# The second example shows how to make a discrete colorbar based on a +# continuous cmapnorm = mpl.colors.BoundaryNorm(bounds, cmap.N, extend='both') +cmap = mpl.cm.viridis +bounds = [-1, 2, 5, 7, 12, 15] +norm = mpl.colors.BoundaryNorm(bounds, cmap.N, extend='both') +cb2 = mpl.colorbar.ColorbarBase(ax2, cmap=cmap, + norm=norm, + orientation='horizontal') +cb2.set_label("Discrete intervals with extend='both' keyword") + + +# The third example illustrates the use of a ListedColormap, a +# BoundaryNorm, and extended ends to show the "over" and "under" +# value colors. +cmap = mpl.colors.ListedColormap(['r', 'g', 'b', 'c']) +cmap.set_over('0.25') +cmap.set_under('0.75') + +# If a ListedColormap is used, the length of the bounds array must be +# one greater than the length of the color list. The bounds must be +# monotonically increasing. +bounds = [1, 2, 4, 7, 8] +norm = mpl.colors.BoundaryNorm(bounds, cmap.N) +cb3 = mpl.colorbar.ColorbarBase(ax3, cmap=cmap, + norm=norm, + # to use 'extend', you must + # specify two extra boundaries: + boundaries=[0] + bounds + [13], + extend='both', + ticks=bounds, # optional + spacing='proportional', + orientation='horizontal') +cb3.set_label('Discrete intervals, some other units') + +# The fourth example illustrates the use of custom length colorbar +# extensions, used on a colorbar with discrete intervals. +cmap = mpl.colors.ListedColormap([[0., .4, 1.], [0., .8, 1.], + [1., .8, 0.], [1., .4, 0.]]) +cmap.set_over((1., 0., 0.)) +cmap.set_under((0., 0., 1.)) + +bounds = [-1., -.5, 0., .5, 1.] +norm = mpl.colors.BoundaryNorm(bounds, cmap.N) +cb4 = mpl.colorbar.ColorbarBase(ax4, cmap=cmap, + norm=norm, + boundaries=[-10] + bounds + [10], + extend='both', + # Make the length of each extension + # the same as the length of the + # interior colors: + extendfrac='auto', + ticks=bounds, + spacing='uniform', + orientation='horizontal') +cb4.set_label('Custom extension lengths, some other units') + +plt.show() From 3638d42cfb96e63e303920c6ec3700384398c76d Mon Sep 17 00:00:00 2001 From: Fabien Maussion Date: Tue, 2 May 2017 12:25:46 +0200 Subject: [PATCH 06/15] what's new --- .../extend_kwarg_to_BoundaryNorm.rst | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst diff --git a/doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst b/doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst new file mode 100644 index 000000000000..b225b02b86f1 --- /dev/null +++ b/doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst @@ -0,0 +1,41 @@ +New "extend" keyword to colors.BoundaryNorm +------------------------------------------- + +:func:`~matplotlib.colors.BoundaryNorm` now has an ``extend`` kwarg. This is +useful when creating a discrete colorbar from a continuous colormap: when +setting ``extend`` to ``'both'``, ``'min'`` or ``'max'``, the colors are +interpolated so that the extensions have a different color than the inner +cells. + +Example +``````` +:: + + from matplotlib import pyplot as plt + import matplotlib as mpl + + # Make a figure and axes with dimensions as desired. + fig = plt.figure(figsize=(8, 3)) + ax1 = fig.add_axes([0.05, 0.7, 0.9, 0.2]) + ax2 = fig.add_axes([0.05, 0.2, 0.9, 0.2]) + + # Set the colormap and bounds + bounds = [-1, 2, 5, 7, 12, 15] + cmap = mpl.cm.get_cmap('viridis') + + # Default behavior + norm = mpl.colors.BoundaryNorm(bounds, cmap.N) + cb1 = mpl.colorbar.ColorbarBase(ax1, cmap=cmap, + norm=norm, + extend='both', + orientation='horizontal') + cb1.set_label('Default BoundaryNorm ouput') + + # New behavior + norm = mpl.colors.BoundaryNorm(bounds, cmap.N, extend='both') + cb2 = mpl.colorbar.ColorbarBase(ax2, cmap=cmap, + norm=norm, + orientation='horizontal') + cb2.set_label("With new extend='both' keyword") + + plt.show() \ No newline at end of file From 754fd26a328a14ffaec6e3d0dd24c646a1cfa7dd Mon Sep 17 00:00:00 2001 From: Fabien Maussion Date: Wed, 22 Aug 2018 14:06:47 +0200 Subject: [PATCH 07/15] Update example --- examples/api/colorbar_only.py | 90 ------------------------------- tutorials/colors/colorbar_only.py | 20 +++++++ 2 files changed, 20 insertions(+), 90 deletions(-) delete mode 100644 examples/api/colorbar_only.py diff --git a/examples/api/colorbar_only.py b/examples/api/colorbar_only.py deleted file mode 100644 index cc852d942284..000000000000 --- a/examples/api/colorbar_only.py +++ /dev/null @@ -1,90 +0,0 @@ -''' -==================== -Customized colorbars -==================== - -This example shows how to build colorbars without an attached mappable. -''' - -import matplotlib.pyplot as plt -import matplotlib as mpl - -# Make a figure and axes with dimensions as desired. -fig = plt.figure(figsize=(8, 5)) -ax1 = fig.add_axes([0.05, 0.85, 0.9, 0.1]) -ax2 = fig.add_axes([0.05, 0.6, 0.9, 0.1]) -ax3 = fig.add_axes([0.05, 0.35, 0.9, 0.1]) -ax4 = fig.add_axes([0.05, 0.1, 0.9, 0.1]) - -# Set the colormap and norm to correspond to the data for which -# the colorbar will be used. -cmap = mpl.cm.cool -norm = mpl.colors.Normalize(vmin=5, vmax=10) - -# ColorbarBase derives from ScalarMappable and puts a colorbar -# in a specified axes, so it has everything needed for a -# standalone colorbar. There are many more kwargs, but the -# following gives a basic continuous colorbar with ticks -# and labels. -cb1 = mpl.colorbar.ColorbarBase(ax1, cmap=cmap, - norm=norm, - orientation='horizontal') -cb1.set_label('Some Units') - -# The second example shows how to make a discrete colorbar based on a -# continuous cmapnorm = mpl.colors.BoundaryNorm(bounds, cmap.N, extend='both') -cmap = mpl.cm.viridis -bounds = [-1, 2, 5, 7, 12, 15] -norm = mpl.colors.BoundaryNorm(bounds, cmap.N, extend='both') -cb2 = mpl.colorbar.ColorbarBase(ax2, cmap=cmap, - norm=norm, - orientation='horizontal') -cb2.set_label("Discrete intervals with extend='both' keyword") - - -# The third example illustrates the use of a ListedColormap, a -# BoundaryNorm, and extended ends to show the "over" and "under" -# value colors. -cmap = mpl.colors.ListedColormap(['r', 'g', 'b', 'c']) -cmap.set_over('0.25') -cmap.set_under('0.75') - -# If a ListedColormap is used, the length of the bounds array must be -# one greater than the length of the color list. The bounds must be -# monotonically increasing. -bounds = [1, 2, 4, 7, 8] -norm = mpl.colors.BoundaryNorm(bounds, cmap.N) -cb3 = mpl.colorbar.ColorbarBase(ax3, cmap=cmap, - norm=norm, - # to use 'extend', you must - # specify two extra boundaries: - boundaries=[0] + bounds + [13], - extend='both', - ticks=bounds, # optional - spacing='proportional', - orientation='horizontal') -cb3.set_label('Discrete intervals, some other units') - -# The fourth example illustrates the use of custom length colorbar -# extensions, used on a colorbar with discrete intervals. -cmap = mpl.colors.ListedColormap([[0., .4, 1.], [0., .8, 1.], - [1., .8, 0.], [1., .4, 0.]]) -cmap.set_over((1., 0., 0.)) -cmap.set_under((0., 0., 1.)) - -bounds = [-1., -.5, 0., .5, 1.] -norm = mpl.colors.BoundaryNorm(bounds, cmap.N) -cb4 = mpl.colorbar.ColorbarBase(ax4, cmap=cmap, - norm=norm, - boundaries=[-10] + bounds + [10], - extend='both', - # Make the length of each extension - # the same as the length of the - # interior colors: - extendfrac='auto', - ticks=bounds, - spacing='uniform', - orientation='horizontal') -cb4.set_label('Custom extension lengths, some other units') - -plt.show() diff --git a/tutorials/colors/colorbar_only.py b/tutorials/colors/colorbar_only.py index a047c3d840ee..712779ad482b 100644 --- a/tutorials/colors/colorbar_only.py +++ b/tutorials/colors/colorbar_only.py @@ -38,6 +38,26 @@ fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap), cax=ax, orientation='horizontal', label='Some Units') + +############################################################################### +# Extended colorbar with continuous colorscale +# -------------------------------------------- + +# The second example shows how to make a discrete colorbar based on a +# continuous cmap. With the "extend" kwarg the appropriate colors are chosen to +# fill the colorspace, including the extensions: +fig, ax = plt.subplots(figsize=(6, 1)) +fig.subplots_adjust(bottom=0.5) + +cmap = mpl.cm.viridis +bounds = [-1, 2, 5, 7, 12, 15] +norm = mpl.colors.BoundaryNorm(bounds, cmap.N, extend='both') +cb2 = mpl.colorbar.ColorbarBase(ax, cmap=cmap, + norm=norm, + orientation='horizontal') +cb2.set_label("Discrete intervals with extend='both' keyword") +fig.show() + ############################################################################### # Discrete intervals colorbar # --------------------------- From b6b9648b1be1a0178dffb942212f5083ef429255 Mon Sep 17 00:00:00 2001 From: Fabien Maussion Date: Fri, 24 Aug 2018 10:41:33 +0200 Subject: [PATCH 08/15] Review --- .../extend_kwarg_to_BoundaryNorm.rst | 61 ++++++++++--------- lib/matplotlib/colors.py | 5 +- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst b/doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst index b225b02b86f1..62886f2385f0 100644 --- a/doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst +++ b/doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst @@ -11,31 +11,36 @@ Example ``````` :: - from matplotlib import pyplot as plt - import matplotlib as mpl - - # Make a figure and axes with dimensions as desired. - fig = plt.figure(figsize=(8, 3)) - ax1 = fig.add_axes([0.05, 0.7, 0.9, 0.2]) - ax2 = fig.add_axes([0.05, 0.2, 0.9, 0.2]) - - # Set the colormap and bounds - bounds = [-1, 2, 5, 7, 12, 15] - cmap = mpl.cm.get_cmap('viridis') - - # Default behavior - norm = mpl.colors.BoundaryNorm(bounds, cmap.N) - cb1 = mpl.colorbar.ColorbarBase(ax1, cmap=cmap, - norm=norm, - extend='both', - orientation='horizontal') - cb1.set_label('Default BoundaryNorm ouput') - - # New behavior - norm = mpl.colors.BoundaryNorm(bounds, cmap.N, extend='both') - cb2 = mpl.colorbar.ColorbarBase(ax2, cmap=cmap, - norm=norm, - orientation='horizontal') - cb2.set_label("With new extend='both' keyword") - - plt.show() \ No newline at end of file + import matplotlib.pyplot as plt + from matplotlib.colors import BoundaryNorm + import numpy as np + + # Make the data + dx, dy = 0.05, 0.05 + y, x = np.mgrid[slice(1, 5 + dy, dy), + slice(1, 5 + dx, dx)] + z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) + z = z[:-1, :-1] + + # Z roughly varies between -1 and +1 + # my levels are chosen so that the color bar should be extended + levels = [-0.8, -0.5, -0.2, 0.2, 0.5, 0.8] + cmap = plt.get_cmap('PiYG') + + # Before this change + plt.subplot(2, 1, 1) + norm = BoundaryNorm(levels, ncolors=cmap.N) + im = plt.pcolormesh(x, y, z, cmap=cmap, norm=norm) + plt.colorbar(extend='both') + plt.axis([x.min(), x.max(), y.min(), y.max()]) + plt.title('pcolormesh with extended colorbar') + + # With the new keyword + norm = BoundaryNorm(levels, ncolors=cmap.N, extend='both') + plt.subplot(2, 1, 2) + im = plt.pcolormesh(x, y, z, cmap=cmap, norm=norm) + plt.colorbar() # note that the colorbar is updated accordingly + plt.axis([x.min(), x.max(), y.min(), y.max()]) + plt.title('pcolormesh with extended BoundaryNorm') + + plt.show() diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 7c2d86913445..b3fc47a3b14a 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1428,8 +1428,9 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): above ``boundaries[-1]``. These are then converted to valid indices by `Colormap.__call__`. extend : str, optional - 'neither', 'both', 'min', or 'max': select the colors out of - cmap so that the extensions are considered in the interpolation + 'neither', 'both', 'min', or 'max': reserve the first (last) colors + of the colormap for data values below (above) the first (last) + boundary value. Notes ----- From 97558ce955ce1bbf483d9f80b34cfb6a189a4d54 Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Thu, 23 Aug 2018 16:40:13 -1000 Subject: [PATCH 09/15] Simplify BoundaryNorm calculation. --- lib/matplotlib/colors.py | 49 +++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index b3fc47a3b14a..9ef0f9ce916b 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1427,18 +1427,23 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): they are below ``boundaries[0]`` or mapped to *ncolors* if they are above ``boundaries[-1]``. These are then converted to valid indices by `Colormap.__call__`. - extend : str, optional - 'neither', 'both', 'min', or 'max': reserve the first (last) colors - of the colormap for data values below (above) the first (last) - boundary value. + extend : {'neither', 'both', 'min', 'max'}, optional + Extend the number of bins to include one or both of the + regions beyond the boundaries. For example, if ``extend`` + is 'min', then the color to which the region between the first + pair of boundaries is mapped will be distinct from the first + color in the colormap, and by default a + `~matplotlib.colorbar.Colorbar` will be drawn with + the triangle extension on the left side. Notes ----- *boundaries* defines the edges of bins, and data falling within a bin is mapped to the color with the same index. - If the number of bins doesn't equal *ncolors*, the color is chosen - by linear interpolation of the bin number onto color numbers. + If the number of bins, including any extensions, doesn't equal + *ncolors*, the color is chosen by linear interpolation of the + bin number onto color numbers. """ if clip and extend != 'neither': raise ValueError("'clip=True' is not compatible with 'extend'") @@ -1448,25 +1453,15 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): self.boundaries = np.asarray(boundaries) self.N = len(self.boundaries) self.Ncmap = ncolors - - # Extension. We use the same trick as colorbar.py and add a fake - # boundary were needed. - _b = list(boundaries) - if extend == 'both': - _b = [_b[0] - 1] + _b + [_b[-1] + 1] - elif extend == 'min': - _b = [_b[0] - 1] + _b - elif extend == 'max': - _b = _b + [_b[-1] + 1] self.extend = extend - # needed for the interpolation but should not be seen from outside - self._b = np.array(_b) - self._N = len(self._b) - if self._N - 1 == self.Ncmap: - self._interp = False - else: - self._interp = True + self._N = self.N - 1 # number of colors needed + self._offset = 0 + if extend in ('min', 'both'): + self._N += 1 + self._offset = 1 + if extend in ('max', 'both'): + self._N += 1 def __call__(self, value, clip=None): if clip is None: @@ -1480,11 +1475,9 @@ def __call__(self, value, clip=None): max_col = self.Ncmap - 1 else: max_col = self.Ncmap - iret = np.zeros(xx.shape, dtype=np.int16) - for i, b in enumerate(self._b): - iret[xx >= b] = i - if self._interp: - scalefac = float(self.Ncmap - 1) / (self._N - 2) + iret = np.digitize(xx, self.boundaries) - 1 + self._offset + if self.Ncmap > self._N: + scalefac = (self.Ncmap - 1) / (self._N - 1) iret = (iret * scalefac).astype(np.int16) iret[xx < self.vmin] = -1 iret[xx >= self.vmax] = max_col From 5a985413b3252c18ea306ec3b57b485472b15cfc Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Thu, 28 May 2020 15:03:45 -1000 Subject: [PATCH 10/15] Add BoundaryNorm extend kwarg to colormapnorms. --- tutorials/colors/colormapnorms.py | 42 ++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/tutorials/colors/colormapnorms.py b/tutorials/colors/colormapnorms.py index 6090fcc25f77..5c176b925ddf 100644 --- a/tutorials/colors/colormapnorms.py +++ b/tutorials/colors/colormapnorms.py @@ -145,7 +145,9 @@ # Another normalization that comes with Matplotlib is `.colors.BoundaryNorm`. # In addition to *vmin* and *vmax*, this takes as arguments boundaries between # which data is to be mapped. The colors are then linearly distributed between -# these "bounds". For instance: +# these "bounds". It can also take an *extend* argument to add upper and/or +# lower out-of-bounds values to the range over which the colors are +# distributed For instance: # # .. ipython:: # @@ -161,30 +163,42 @@ # Note: Unlike the other norms, this norm returns values from 0 to *ncolors*-1. N = 100 -X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)] +X, Y = np.meshgrid(np.linspace(-3, 3, N), np.linspace(-2, 2, N)) Z1 = np.exp(-X**2 - Y**2) Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2) -Z = (Z1 - Z2) * 2 +Z = ((Z1 - Z2) * 2)[:-1, :-1] -fig, ax = plt.subplots(3, 1, figsize=(8, 8)) +fig, ax = plt.subplots(2, 2, figsize=(8, 6), constrained_layout=True) ax = ax.flatten() -# even bounds gives a contour-like effect -bounds = np.linspace(-1, 1, 10) -norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256) -pcm = ax[0].pcolormesh(X, Y, Z, norm=norm, cmap='RdBu_r', shading='auto') -fig.colorbar(pcm, ax=ax[0], extend='both', orientation='vertical') -# uneven bounds changes the colormapping: -bounds = np.array([-0.25, -0.125, 0, 0.5, 1]) +# Default norm: +pcm = ax[0].pcolormesh(X, Y, Z, cmap='RdBu_r') +fig.colorbar(pcm, ax=ax[0], orientation='vertical') +ax[0].set_title('Default norm') + +# Even bounds give a contour-like effect: +bounds = np.linspace(-1.5, 1.5, 7) norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256) -pcm = ax[1].pcolormesh(X, Y, Z, norm=norm, cmap='RdBu_r', shading='auto') +pcm = ax[1].pcolormesh(X, Y, Z, norm=norm, cmap='RdBu_r') fig.colorbar(pcm, ax=ax[1], extend='both', orientation='vertical') +ax[1].set_title('BoundaryNorm: 7 boundaries') -pcm = ax[2].pcolormesh(X, Y, Z, cmap='RdBu_r', vmin=-np.max(Z), shading='auto') +# Bounds may be unevenly spaced: +bounds = np.array([-0.2, -0.1, 0, 0.5, 1]) +norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256) +pcm = ax[2].pcolormesh(X, Y, Z, norm=norm, cmap='RdBu_r') fig.colorbar(pcm, ax=ax[2], extend='both', orientation='vertical') +ax[2].set_title('BoundaryNorm: nonuniform') + +# With out-of-bounds colors: +bounds = np.linspace(-1.5, 1.5, 7) +norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256, extend='both') +pcm = ax[3].pcolormesh(X, Y, Z, norm=norm, cmap='RdBu_r') +# The colorbar inherits the "extend" argument from BoundaryNorm. +fig.colorbar(pcm, ax=ax[3], orientation='vertical') +ax[3].set_title('BoundaryNorm: extend="both"') plt.show() - ############################################################################### # TwoSlopeNorm: Different mapping on either side of a center # ---------------------------------------------------------- From ffce3b91a70ea9ad49620194ae8d90e08210e6db Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Thu, 28 May 2020 15:15:49 -1000 Subject: [PATCH 11/15] Move whats_new entry to next_whats_new --- ...020-05-28-extend_kwarg_to_BoundaryNorm.rst | 48 +++++++++++++++++++ .../extend_kwarg_to_BoundaryNorm.rst | 46 ------------------ 2 files changed, 48 insertions(+), 46 deletions(-) create mode 100644 doc/users/next_whats_new/2020-05-28-extend_kwarg_to_BoundaryNorm.rst delete mode 100644 doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst diff --git a/doc/users/next_whats_new/2020-05-28-extend_kwarg_to_BoundaryNorm.rst b/doc/users/next_whats_new/2020-05-28-extend_kwarg_to_BoundaryNorm.rst new file mode 100644 index 000000000000..a1e01da016b2 --- /dev/null +++ b/doc/users/next_whats_new/2020-05-28-extend_kwarg_to_BoundaryNorm.rst @@ -0,0 +1,48 @@ +New "extend" keyword to colors.BoundaryNorm +------------------------------------------- + +`~.colors.BoundaryNorm` now has an ``extend`` kwarg, +analogous to ``extend`` in ~.axes._axes.Axes.contourf`. When set to +'both', 'min', or 'max', it interpolates such that the corresponding +out-of-range values are mapped to colors distinct from their in-range +neighbors. The colorbar inherits the ``extend`` argument from the +norm, so with ``extend='both'``, for example, the colorbar will have +triangular extensions for out-of-range values with colors that differ +from adjacent colors. + + .. plot:: + + import matplotlib.pyplot as plt + from matplotlib.colors import BoundaryNorm + import numpy as np + + # Make the data + dx, dy = 0.05, 0.05 + y, x = np.mgrid[slice(1, 5 + dy, dy), + slice(1, 5 + dx, dx)] + z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) + z = z[:-1, :-1] + + # Z roughly varies between -1 and +1. + # Color boundary levels range from -0.8 to 0.8, so there are out-of-bounds + # areas. + levels = [-0.8, -0.5, -0.2, 0.2, 0.5, 0.8] + cmap = plt.get_cmap('PiYG') + + fig, axs = plt.subplots(nrows=2, constrained_layout=True, sharex=True) + + # Before this change: + norm = BoundaryNorm(levels, ncolors=cmap.N) + im = axs[0].pcolormesh(x, y, z, cmap=cmap, norm=norm) + fig.colorbar(im, ax=axs[0], extend='both') + axs[0].axis([x.min(), x.max(), y.min(), y.max()]) + axs[0].set_title("Colorbar with extend='both'") + + # With the new keyword: + norm = BoundaryNorm(levels, ncolors=cmap.N, extend='both') + im = axs[1].pcolormesh(x, y, z, cmap=cmap, norm=norm) + fig.colorbar(im, ax=axs[1]) # note that the colorbar is updated accordingly + axs[1].axis([x.min(), x.max(), y.min(), y.max()]) + axs[1].set_title("BoundaryNorm with extend='both'") + + plt.show() diff --git a/doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst b/doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst deleted file mode 100644 index 62886f2385f0..000000000000 --- a/doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst +++ /dev/null @@ -1,46 +0,0 @@ -New "extend" keyword to colors.BoundaryNorm -------------------------------------------- - -:func:`~matplotlib.colors.BoundaryNorm` now has an ``extend`` kwarg. This is -useful when creating a discrete colorbar from a continuous colormap: when -setting ``extend`` to ``'both'``, ``'min'`` or ``'max'``, the colors are -interpolated so that the extensions have a different color than the inner -cells. - -Example -``````` -:: - - import matplotlib.pyplot as plt - from matplotlib.colors import BoundaryNorm - import numpy as np - - # Make the data - dx, dy = 0.05, 0.05 - y, x = np.mgrid[slice(1, 5 + dy, dy), - slice(1, 5 + dx, dx)] - z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) - z = z[:-1, :-1] - - # Z roughly varies between -1 and +1 - # my levels are chosen so that the color bar should be extended - levels = [-0.8, -0.5, -0.2, 0.2, 0.5, 0.8] - cmap = plt.get_cmap('PiYG') - - # Before this change - plt.subplot(2, 1, 1) - norm = BoundaryNorm(levels, ncolors=cmap.N) - im = plt.pcolormesh(x, y, z, cmap=cmap, norm=norm) - plt.colorbar(extend='both') - plt.axis([x.min(), x.max(), y.min(), y.max()]) - plt.title('pcolormesh with extended colorbar') - - # With the new keyword - norm = BoundaryNorm(levels, ncolors=cmap.N, extend='both') - plt.subplot(2, 1, 2) - im = plt.pcolormesh(x, y, z, cmap=cmap, norm=norm) - plt.colorbar() # note that the colorbar is updated accordingly - plt.axis([x.min(), x.max(), y.min(), y.max()]) - plt.title('pcolormesh with extended BoundaryNorm') - - plt.show() From a4aafd1895f01c091baa126c0e3d95a32a184b71 Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Sat, 30 May 2020 13:33:09 -1000 Subject: [PATCH 12/15] Documentation tweaks from review. --- ...020-05-28-extend_kwarg_to_BoundaryNorm.rst | 17 ++++++------ lib/matplotlib/colors.py | 2 +- tutorials/colors/colorbar_only.py | 26 +++++++++++-------- tutorials/colors/colormapnorms.py | 2 +- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/doc/users/next_whats_new/2020-05-28-extend_kwarg_to_BoundaryNorm.rst b/doc/users/next_whats_new/2020-05-28-extend_kwarg_to_BoundaryNorm.rst index a1e01da016b2..4f68950bf3dc 100644 --- a/doc/users/next_whats_new/2020-05-28-extend_kwarg_to_BoundaryNorm.rst +++ b/doc/users/next_whats_new/2020-05-28-extend_kwarg_to_BoundaryNorm.rst @@ -1,14 +1,15 @@ New "extend" keyword to colors.BoundaryNorm ------------------------------------------- -`~.colors.BoundaryNorm` now has an ``extend`` kwarg, -analogous to ``extend`` in ~.axes._axes.Axes.contourf`. When set to -'both', 'min', or 'max', it interpolates such that the corresponding -out-of-range values are mapped to colors distinct from their in-range -neighbors. The colorbar inherits the ``extend`` argument from the -norm, so with ``extend='both'``, for example, the colorbar will have -triangular extensions for out-of-range values with colors that differ -from adjacent colors. +`~.colors.BoundaryNorm` now has an ``extend`` keyword argument, analogous to +``extend`` in ~.axes._axes.Axes.contourf`. When set to 'both', 'min', or 'max', +it maps the corresponding out-of-range values to `~.colors.Colormap` +lookup-table indices near the appropriate ends of their range so that the +colors for out-of range values are adjacent to, but distinct from, their +in-range neighbors. The colorbar inherits the ``extend`` argument from the +norm, so with ``extend='both'``, for example, the colorbar will have triangular +extensions for out-of-range values with colors that differ from adjacent +colors. .. plot:: diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 9ef0f9ce916b..8a713c7cd8fa 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1434,7 +1434,7 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): pair of boundaries is mapped will be distinct from the first color in the colormap, and by default a `~matplotlib.colorbar.Colorbar` will be drawn with - the triangle extension on the left side. + the triangle extension on the left or lower end. Notes ----- diff --git a/tutorials/colors/colorbar_only.py b/tutorials/colors/colorbar_only.py index 712779ad482b..5d46dde2e755 100644 --- a/tutorials/colors/colorbar_only.py +++ b/tutorials/colors/colorbar_only.py @@ -42,10 +42,10 @@ ############################################################################### # Extended colorbar with continuous colorscale # -------------------------------------------- - +# # The second example shows how to make a discrete colorbar based on a -# continuous cmap. With the "extend" kwarg the appropriate colors are chosen to -# fill the colorspace, including the extensions: +# continuous cmap. With the "extend" keyword argument the appropriate colors +# are chosen to fill the colorspace, including the extensions: fig, ax = plt.subplots(figsize=(6, 1)) fig.subplots_adjust(bottom=0.5) @@ -62,7 +62,7 @@ # Discrete intervals colorbar # --------------------------- # -# The second example illustrates the use of a +# The third example illustrates the use of a # :class:`~matplotlib.colors.ListedColormap` which generates a colormap from a # set of listed colors, `.colors.BoundaryNorm` which generates a colormap # index based on discrete intervals and extended ends to show the "over" and @@ -74,11 +74,15 @@ # bounds array must be one greater than the length of the color list. The # bounds must be monotonically increasing. # -# This time we pass some more arguments in addition to previous arguments to -# `~.Figure.colorbar`. For the out-of-range values to -# display on the colorbar, we have to use the *extend* keyword argument. To use -# *extend*, you must specify two extra boundaries. Finally spacing argument -# ensures that intervals are shown on colorbar proportionally. +# This time we pass additional arguments to +# `~.Figure.colorbar`. For the out-of-range values to display on the colorbar +# without using the *extend* keyword with +# `.colors.BoundaryNorm`, we have to use the *extend* keyword argument directly +# in the colorbar call, and supply an additional boundary on each end of the +# range. Here we also +# use the spacing argument to make +# the length of each colorbar segment proportional to its corresponding +# interval. fig, ax = plt.subplots(figsize=(6, 1)) fig.subplots_adjust(bottom=0.5) @@ -92,7 +96,7 @@ fig.colorbar( mpl.cm.ScalarMappable(cmap=cmap, norm=norm), cax=ax, - boundaries=[0] + bounds + [13], + boundaries=[0] + bounds + [13], # Adding values for extensions. extend='both', ticks=bounds, spacing='proportional', @@ -104,7 +108,7 @@ # Colorbar with custom extension lengths # -------------------------------------- # -# Here we illustrate the use of custom length colorbar extensions, used on a +# Here we illustrate the use of custom length colorbar extensions, on a # colorbar with discrete intervals. To make the length of each extension the # same as the length of the interior colors, use ``extendfrac='auto'``. diff --git a/tutorials/colors/colormapnorms.py b/tutorials/colors/colormapnorms.py index 5c176b925ddf..eca01bebe6c4 100644 --- a/tutorials/colors/colormapnorms.py +++ b/tutorials/colors/colormapnorms.py @@ -147,7 +147,7 @@ # which data is to be mapped. The colors are then linearly distributed between # these "bounds". It can also take an *extend* argument to add upper and/or # lower out-of-bounds values to the range over which the colors are -# distributed For instance: +# distributed. For instance: # # .. ipython:: # From 3b6ac629c461c62cf6d3a92f03bae0c46641a3b9 Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Sun, 31 May 2020 06:22:01 -1000 Subject: [PATCH 13/15] Doc: specify default keyword argument for 'extend' Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/colors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 8a713c7cd8fa..43a478c65228 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1427,7 +1427,7 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): they are below ``boundaries[0]`` or mapped to *ncolors* if they are above ``boundaries[-1]``. These are then converted to valid indices by `Colormap.__call__`. - extend : {'neither', 'both', 'min', 'max'}, optional + extend : {'neither', 'both', 'min', 'max'}, default: 'neither' Extend the number of bins to include one or both of the regions beyond the boundaries. For example, if ``extend`` is 'min', then the color to which the region between the first From 49902e963ddf3dd3a88817a6ff0f5d499ca28b80 Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Sun, 31 May 2020 22:24:13 -1000 Subject: [PATCH 14/15] Improve the tests, docstrings, argument checking The substantive difference is that a ValueError is now raised if BoundaryNorm is called with ncolors < the total number of bins, including any extensions. --- lib/matplotlib/colors.py | 19 ++++-- lib/matplotlib/tests/test_colors.py | 92 ++++++++++++++--------------- 2 files changed, 59 insertions(+), 52 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 43a478c65228..c6fe81029b1f 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -530,7 +530,7 @@ def __call__(self, X, alpha=None, bytes=False): """ Parameters ---------- - X : float, ndarray + X : float or int, ndarray or scalar The data value(s) to convert to RGBA. For floats, X should be in the interval ``[0.0, 1.0]`` to return the RGBA values ``X*100`` percent along the Colormap line. @@ -1410,7 +1410,7 @@ class BoundaryNorm(Normalize): interpolation, but using integers seems simpler, and reduces the number of conversions back and forth between integer and floating point. """ - def __init__(self, boundaries, ncolors, clip=False, extend='neither'): + def __init__(self, boundaries, ncolors, clip=False, *, extend='neither'): """ Parameters ---------- @@ -1436,14 +1436,18 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): `~matplotlib.colorbar.Colorbar` will be drawn with the triangle extension on the left or lower end. + Returns + ------- + int16 scalar or array + Notes ----- *boundaries* defines the edges of bins, and data falling within a bin is mapped to the color with the same index. - If the number of bins, including any extensions, doesn't equal - *ncolors*, the color is chosen by linear interpolation of the - bin number onto color numbers. + If the number of bins, including any extensions, is less than + *ncolors*, the color index is chosen by linear interpolation, mapping + the ``[0, nbins - 1]`` range onto the ``[0, ncolors - 1]`` range. """ if clip and extend != 'neither': raise ValueError("'clip=True' is not compatible with 'extend'") @@ -1462,6 +1466,11 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): self._offset = 1 if extend in ('max', 'both'): self._N += 1 + if self._N > self.Ncmap: + raise ValueError(f"There are {self._N} color bins including " + f"extensions, but ncolors = {ncolors}; " + "ncolors must equal or exceed the number of " + "bins") def __call__(self, value, clip=None): if clip is None: diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index ae1fdeb70edc..948aa6d8420d 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -265,85 +265,83 @@ def test_BoundaryNorm(): vals = np.ma.masked_invalid([np.Inf]) assert np.all(bn(vals).mask) - # Testing extend keyword - bounds = [1, 2, 3] - cmap = cm.get_cmap('jet') + # Incompatible extend and clip + with pytest.raises(ValueError, match="not compatible"): + mcolors.BoundaryNorm(np.arange(4), 5, extend='both', clip=True) - refnorm = mcolors.BoundaryNorm([0] + bounds + [4], cmap.N) + # Too small ncolors argument + with pytest.raises(ValueError, match="ncolors must equal or exceed"): + mcolors.BoundaryNorm(np.arange(4), 2) + + with pytest.raises(ValueError, match="ncolors must equal or exceed"): + mcolors.BoundaryNorm(np.arange(4), 3, extend='min') + + with pytest.raises(ValueError, match="ncolors must equal or exceed"): + mcolors.BoundaryNorm(np.arange(4), 4, extend='both') + + # Testing extend keyword, with interpolation (large cmap) + bounds = [1, 2, 3] + cmap = cm.get_cmap('viridis') mynorm = mcolors.BoundaryNorm(bounds, cmap.N, extend='both') - x = np.random.random(100) + 1.5 - np.testing.assert_array_equal(refnorm(x), mynorm(x)) + refnorm = mcolors.BoundaryNorm([0] + bounds + [4], cmap.N) + x = np.random.randn(100) * 10 + 2 + ref = refnorm(x) + ref[ref == 0] = -1 + ref[ref == cmap.N - 1] = cmap.N + assert_array_equal(mynorm(x), ref) - # Min and max + # Without interpolation cmref = mcolors.ListedColormap(['blue', 'red']) cmref.set_over('black') cmref.set_under('white') - cmshould = mcolors.ListedColormap(['white', 'blue', 'red', 'black']) - cmshould.set_over(cmshould(cmshould.N)) - cmshould.set_under(cmshould(0)) refnorm = mcolors.BoundaryNorm(bounds, cmref.N) mynorm = mcolors.BoundaryNorm(bounds, cmshould.N, extend='both') - np.testing.assert_array_equal(refnorm.vmin, mynorm.vmin) - np.testing.assert_array_equal(refnorm.vmax, mynorm.vmax) - x = [-1, 1.2, 2.3, 9.6] - np.testing.assert_array_equal(cmshould([0, 1, 2, 3]), cmshould(mynorm(x))) - x = np.random.randn(100) * 10 + 2 - np.testing.assert_array_equal(cmref(refnorm(x)), cmshould(mynorm(x))) + assert mynorm.vmin == refnorm.vmin + assert mynorm.vmax == refnorm.vmax - np.testing.assert_array_equal(-1, mynorm(-1)) - np.testing.assert_array_equal(1, mynorm(1.1)) - np.testing.assert_array_equal(4, mynorm(12)) + assert mynorm(bounds[0] - 0.1) == -1 # under + assert mynorm(bounds[0] + 0.1) == 1 # first bin -> second color + assert mynorm(bounds[-1] - 0.1) == cmshould.N - 2 # next-to-last color + assert mynorm(bounds[-1] + 0.1) == cmshould.N # over - # Test raises - with pytest.raises(ValueError): - mcolors.BoundaryNorm(bounds, cmref.N, extend='both', clip=True) + x = [-1, 1.2, 2.3, 9.6] + assert_array_equal(cmshould(mynorm(x)), cmshould([0, 1, 2, 3])) + x = np.random.randn(100) * 10 + 2 + assert_array_equal(cmshould(mynorm(x)), cmref(refnorm(x))) # Just min cmref = mcolors.ListedColormap(['blue', 'red']) cmref.set_under('white') cmshould = mcolors.ListedColormap(['white', 'blue', 'red']) - cmshould.set_under(cmshould(0)) - np.testing.assert_array_equal(2, cmref.N) - np.testing.assert_array_equal(3, cmshould.N) + assert cmref.N == 2 + assert cmshould.N == 3 refnorm = mcolors.BoundaryNorm(bounds, cmref.N) mynorm = mcolors.BoundaryNorm(bounds, cmshould.N, extend='min') - np.testing.assert_array_equal(refnorm.vmin, mynorm.vmin) - np.testing.assert_array_equal(refnorm.vmax, mynorm.vmax) + assert mynorm.vmin == refnorm.vmin + assert mynorm.vmax == refnorm.vmax x = [-1, 1.2, 2.3] - np.testing.assert_array_equal(cmshould([0, 1, 2]), cmshould(mynorm(x))) + assert_array_equal(cmshould(mynorm(x)), cmshould([0, 1, 2])) x = np.random.randn(100) * 10 + 2 - np.testing.assert_array_equal(cmref(refnorm(x)), cmshould(mynorm(x))) + assert_array_equal(cmshould(mynorm(x)), cmref(refnorm(x))) # Just max cmref = mcolors.ListedColormap(['blue', 'red']) cmref.set_over('black') cmshould = mcolors.ListedColormap(['blue', 'red', 'black']) - cmshould.set_over(cmshould(2)) - np.testing.assert_array_equal(2, cmref.N) - np.testing.assert_array_equal(3, cmshould.N) + assert cmref.N == 2 + assert cmshould.N == 3 refnorm = mcolors.BoundaryNorm(bounds, cmref.N) mynorm = mcolors.BoundaryNorm(bounds, cmshould.N, extend='max') - np.testing.assert_array_equal(refnorm.vmin, mynorm.vmin) - np.testing.assert_array_equal(refnorm.vmax, mynorm.vmax) + assert mynorm.vmin == refnorm.vmin + assert mynorm.vmax == refnorm.vmax x = [1.2, 2.3, 4] - np.testing.assert_array_equal(cmshould([0, 1, 2]), cmshould(mynorm(x))) + assert_array_equal(cmshould(mynorm(x)), cmshould([0, 1, 2])) x = np.random.randn(100) * 10 + 2 - np.testing.assert_array_equal(cmref(refnorm(x)), cmshould(mynorm(x))) - - # General case - bounds = [1, 2, 3, 4] - cmap = cm.get_cmap('jet') - mynorm = mcolors.BoundaryNorm(bounds, cmap.N, extend='both') - refnorm = mcolors.BoundaryNorm([-100] + bounds + [100], cmap.N) - x = np.random.randn(100) * 10 - 5 - ref = refnorm(x) - ref = np.where(ref == 0, -1, ref) - ref = np.where(ref == cmap.N-1, cmap.N, ref) - np.testing.assert_array_equal(ref, mynorm(x)) + assert_array_equal(cmshould(mynorm(x)), cmref(refnorm(x))) @pytest.mark.parametrize("vmin,vmax", [[-1, 2], [3, 1]]) From f7cc4020cd35cea58106d2b629c35a731836c01e Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 1 Jun 2020 23:16:20 -0400 Subject: [PATCH 15/15] MNT/DOC: suggestions from review Whitespace and wording tweaks. Co-authored-by: Elliott Sales de Andrade --- .../2020-05-28-extend_kwarg_to_BoundaryNorm.rst | 4 ++-- tutorials/colors/colorbar_only.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/users/next_whats_new/2020-05-28-extend_kwarg_to_BoundaryNorm.rst b/doc/users/next_whats_new/2020-05-28-extend_kwarg_to_BoundaryNorm.rst index 4f68950bf3dc..1786bc0dfbf2 100644 --- a/doc/users/next_whats_new/2020-05-28-extend_kwarg_to_BoundaryNorm.rst +++ b/doc/users/next_whats_new/2020-05-28-extend_kwarg_to_BoundaryNorm.rst @@ -2,13 +2,13 @@ New "extend" keyword to colors.BoundaryNorm ------------------------------------------- `~.colors.BoundaryNorm` now has an ``extend`` keyword argument, analogous to -``extend`` in ~.axes._axes.Axes.contourf`. When set to 'both', 'min', or 'max', +``extend`` in `~.axes.Axes.contourf`. When set to 'both', 'min', or 'max', it maps the corresponding out-of-range values to `~.colors.Colormap` lookup-table indices near the appropriate ends of their range so that the colors for out-of range values are adjacent to, but distinct from, their in-range neighbors. The colorbar inherits the ``extend`` argument from the norm, so with ``extend='both'``, for example, the colorbar will have triangular -extensions for out-of-range values with colors that differ from adjacent +extensions for out-of-range values with colors that differ from adjacent in-range colors. .. plot:: diff --git a/tutorials/colors/colorbar_only.py b/tutorials/colors/colorbar_only.py index 5d46dde2e755..5d0e380be076 100644 --- a/tutorials/colors/colorbar_only.py +++ b/tutorials/colors/colorbar_only.py @@ -53,8 +53,8 @@ bounds = [-1, 2, 5, 7, 12, 15] norm = mpl.colors.BoundaryNorm(bounds, cmap.N, extend='both') cb2 = mpl.colorbar.ColorbarBase(ax, cmap=cmap, - norm=norm, - orientation='horizontal') + norm=norm, + orientation='horizontal') cb2.set_label("Discrete intervals with extend='both' keyword") fig.show()