diff --git a/doc/api/api_changes.rst b/doc/api/api_changes.rst index a077f851101f..bc65dcfb2a1f 100644 --- a/doc/api/api_changes.rst +++ b/doc/api/api_changes.rst @@ -51,6 +51,9 @@ Changes in 1.2.x matplotlib axes by providing a ``_as_mpl_axes`` method. See :ref:`adding-new-scales` for more detail. +* A new keyword *extendfrac* in :meth:`~matplotlib.pyplot.colorbar` and + :class:`~matplotlib.colorbar.ColorbarBase` allows one to control the size of + the triangular minimum and maximum extensions on colorbars. Changes in 1.1.x ================ diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst index f18fc2f1a5c5..83b707a58e68 100644 --- a/doc/users/whats_new.rst +++ b/doc/users/whats_new.rst @@ -26,6 +26,37 @@ Damon McDougall added a new plotting method for the .. plot:: mpl_examples/mplot3d/trisurf3d_demo.py +Control the lengths of colorbar extensions +------------------------------------------ + +Andrew Dawson added a new keyword argument *extendfrac* to +:meth:`~matplotlib.pyplot.colorbar` to control the length of +minimum and maximum colorbar extensions. + +.. plot:: + + import matplotlib.pyplot as plt + import numpy as np + + x = y = np.linspace(0., 2*np.pi, 100) + X, Y = np.meshgrid(x, y) + Z = np.cos(X) * np.sin(0.5*Y) + + clevs = [-.75, -.5, -.25, 0., .25, .5, .75] + cmap = plt.cm.get_cmap(name='jet', lut=8) + + ax1 = plt.subplot(211) + cs1 = plt.contourf(x, y, Z, clevs, cmap=cmap, extend='both') + cb1 = plt.colorbar(orientation='horizontal', extendfrac=None) + cb1.set_label('Default length colorbar extensions') + + ax2 = plt.subplot(212) + cs2 = plt.contourf(x, y, Z, clevs, cmap=cmap, extend='both') + cb2 = plt.colorbar(orientation='horizontal', extendfrac='auto') + cb2.set_label('Custom length colorbar extensions') + + plt.show() + .. _whats-new-1-1: new in matplotlib-1.1 diff --git a/examples/api/colorbar_only.py b/examples/api/colorbar_only.py index e071e1b4a156..0e1837e95f19 100644 --- a/examples/api/colorbar_only.py +++ b/examples/api/colorbar_only.py @@ -6,8 +6,9 @@ # Make a figure and axes with dimensions as desired. fig = pyplot.figure(figsize=(8,3)) -ax1 = fig.add_axes([0.05, 0.65, 0.9, 0.15]) -ax2 = fig.add_axes([0.05, 0.25, 0.9, 0.15]) +ax1 = fig.add_axes([0.05, 0.80, 0.9, 0.15]) +ax2 = fig.add_axes([0.05, 0.475, 0.9, 0.15]) +ax3 = fig.add_axes([0.05, 0.15, 0.9, 0.15]) # Set the colormap and norm to correspond to the data for which # the colorbar will be used. @@ -47,5 +48,27 @@ orientation='horizontal') cb2.set_label('Discrete intervals, some other units') +# The third 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) +cb3 = mpl.colorbar.ColorbarBase(ax3, 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') +cb3.set_label('Custom extension lengths, some other units') + pyplot.show() diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 0c4583f465f0..5eb9e779ced2 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -992,7 +992,8 @@ def tk_window_focus(): 'matplotlib.tests.test_text', 'matplotlib.tests.test_tightlayout', 'matplotlib.tests.test_delaunay', - 'matplotlib.tests.test_legend' + 'matplotlib.tests.test_legend', + 'matplotlib.tests.test_colorbar', ] def test(verbosity=1): diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 5f03f568feb5..2f8a72092300 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -59,13 +59,29 @@ colormap_kw_doc = ''' - =========== ==================================================== + ============ ==================================================== Property Description - =========== ==================================================== + ============ ==================================================== *extend* [ 'neither' | 'both' | 'min' | 'max' ] If not 'neither', make pointed end(s) for out-of- range values. These are set for a given colormap using the colormap set_under and set_over methods. + *extendfrac* [ *None* | 'auto' | length | lengths ] + If set to *None*, both the minimum and maximum + triangular colorbar extensions with have a length of + 5% of the interior colorbar length (this is the + default setting). If set to 'auto', makes the + triangular colorbar extensions the same lengths as + the interior boxes (when *spacing* is set to + 'uniform') or the same lengths as the respective + adjacent interior boxes (when *spacing* is set to + 'proportional'). If a scalar, indicates the length + of both the minimum and maximum triangular colorbar + extensions as a fraction of the interior colorbar + length. A two-element sequence of fractions may also + be given, indicating the lengths of the minimum and + maximum colorbar extensions respectively as a + fraction of the interior colorbar length. *spacing* [ 'uniform' | 'proportional' ] Uniform spacing gives each discrete color the same space; proportional makes the space proportional to @@ -82,7 +98,7 @@ given instead. *drawedges* [ False | True ] If true, draw lines at color boundaries. - =========== ==================================================== + ============ ==================================================== The following will probably be useful only in the context of indexed colors (that is, when the mappable has norm=NoNorm()), @@ -221,6 +237,7 @@ def __init__(self, ax, cmap=None, format=None, drawedges=False, filled=True, + extendfrac=None, ): self.ax = ax self._patch_ax() @@ -236,6 +253,7 @@ def __init__(self, ax, cmap=None, self.orientation = orientation self.drawedges = drawedges self.filled = filled + self.extendfrac = extendfrac self.solids = None self.lines = None self.outline = None @@ -616,6 +634,35 @@ def _extended_N(self): N += 1 return N + def _get_extension_lengths(self, frac, automin, automax, default=0.05): + ''' + Get the lengths of colorbar extensions. + + A helper method for _uniform_y and _proportional_y. + ''' + # Set the default value. + extendlength = np.array([default, default]) + if isinstance(frac, str): + if frac.lower() == 'auto': + # Use the provided values when 'auto' is required. + extendlength[0] = automin + extendlength[1] = automax + else: + # Any other string is invalid. + raise ValueError('invalid value for extendfrac') + elif frac is not None: + try: + # Try to set min and max extension fractions directly. + extendlength[:] = frac + # If frac is a sequence contaning None then NaN may + # be encountered. This is an error. + if np.isnan(extendlength).any(): + raise ValueError() + except (TypeError, ValueError): + # Raise an error on encountering an invalid value for frac. + raise ValueError('invalid value for extendfrac') + return extendlength + def _uniform_y(self, N): ''' Return colorbar data coordinates for *N* uniformly @@ -624,16 +671,19 @@ def _uniform_y(self, N): if self.extend == 'neither': y = np.linspace(0, 1, N) else: + automin = automax = 1. / (N - 1.) + extendlength = self._get_extension_lengths(self.extendfrac, + automin, automax, default=0.05) if self.extend == 'both': y = np.zeros(N + 2, 'd') - y[0] = -0.05 - y[-1] = 1.05 + y[0] = 0. - extendlength[0] + y[-1] = 1. + extendlength[1] elif self.extend == 'min': y = np.zeros(N + 1, 'd') - y[0] = -0.05 + y[0] = 0. - extendlength[0] else: y = np.zeros(N + 1, 'd') - y[-1] = 1.05 + y[-1] = 1. + extendlength[1] y[self._inside] = np.linspace(0, 1, N) return y @@ -648,10 +698,27 @@ def _proportional_y(self): y = y / (self._boundaries[-1] - self._boundaries[0]) else: y = self.norm(self._boundaries.copy()) - if self._extend_lower(): - y[0] = -0.05 - if self._extend_upper(): - y[-1] = 1.05 + if self.extend == 'min': + # Exclude leftmost interval of y. + clen = y[-1] - y[1] + automin = (y[2] - y[1]) / clen + automax = (y[-1] - y[-2]) / clen + elif self.extend == 'max': + # Exclude rightmost interval in y. + clen = y[-2] - y[0] + automin = (y[1] - y[0]) / clen + automax = (y[-2] - y[-3]) / clen + else: + # Exclude leftmost and rightmost intervals in y. + clen = y[-2] - y[1] + automin = (y[2] - y[1]) / clen + automax = (y[-2] - y[-3]) / clen + extendlength = self._get_extension_lengths(self.extendfrac, + automin, automax, default=0.05) + if self.extend in ('both', 'min'): + y[0] = 0. - extendlength[0] + if self.extend in ('both', 'max'): + y[-1] = 1. + extendlength[1] yi = y[self._inside] norm = colors.Normalize(yi[0], yi[-1]) y[self._inside] = norm(yi) diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_proportional.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_proportional.png new file mode 100644 index 000000000000..e531db31bb1b Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_proportional.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_uniform.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_uniform.png new file mode 100644 index 000000000000..d8c7b87aec06 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_uniform.png differ diff --git a/lib/matplotlib/tests/test_colorbar.py b/lib/matplotlib/tests/test_colorbar.py new file mode 100644 index 000000000000..541b2b7fc803 --- /dev/null +++ b/lib/matplotlib/tests/test_colorbar.py @@ -0,0 +1,61 @@ +from matplotlib import rcParams, rcParamsDefault +from matplotlib.testing.decorators import image_comparison +import matplotlib.pyplot as plt +from matplotlib.colors import BoundaryNorm +from matplotlib.cm import get_cmap +from matplotlib.colorbar import ColorbarBase + + +def _colorbar_extensions(spacing): + + # Create a color map and specify the levels it represents. + cmap = get_cmap("RdBu", lut=5) + clevs = [-5., -2.5, -.5, .5, 1.5, 3.5] + + # Define norms for the color maps. + norms = dict() + norms['neither'] = BoundaryNorm(clevs, len(clevs)-1) + norms['min'] = BoundaryNorm([-10]+clevs[1:], len(clevs)-1) + norms['max'] = BoundaryNorm(clevs[:-1]+[10], len(clevs)-1) + norms['both'] = BoundaryNorm([-10]+clevs[1:-1]+[10], len(clevs)-1) + + # Create a figure and adjust whitespace for subplots. + fig = plt.figure() + fig.subplots_adjust(hspace=.6) + + for i, extension_type in enumerate(('neither', 'min', 'max', 'both')): + # Get the appropriate norm and use it to get colorbar boundaries. + norm = norms[extension_type] + boundaries = values = norm.boundaries + for j, extendfrac in enumerate((None, 'auto', 0.1)): + # Create a subplot. + cax = fig.add_subplot(12, 1, i*3+j+1) + # Turn off text and ticks. + for item in cax.get_xticklabels() + cax.get_yticklabels() +\ + cax.get_xticklines() + cax.get_yticklines(): + item.set_visible(False) + # Generate the colorbar. + cb = ColorbarBase(cax, cmap=cmap, norm=norm, + boundaries=boundaries, values=values, + extend=extension_type, extendfrac=extendfrac, + orientation='horizontal', spacing=spacing) + + # Return the figure to the caller. + return fig + + +@image_comparison( + baseline_images=['colorbar_extensions_uniform', 'colorbar_extensions_proportional'], + extensions=['png']) +def test_colorbar_extensions(): + # Use default params so .matplotlibrc doesn't cause the test to fail. + rcParams.update(rcParamsDefault) + # Create figures for uniform and proportionally spaced colorbars. + fig1 = _colorbar_extensions('uniform') + fig2 = _colorbar_extensions('proportional') + + +if __name__ == '__main__': + import nose + nose.runmodule(argv=['-s', '--with-doctest'], exit=False) + pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy