Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a2d44d5

Browse files
committed
Merge pull request #766 from ajdawson/colorbar-extensions
NF - control the length of colorbar extensions
2 parents 18cf273 + c33a09f commit a2d44d5

File tree

8 files changed

+200
-14
lines changed

8 files changed

+200
-14
lines changed

doc/api/api_changes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ Changes in 1.2.x
5151
matplotlib axes by providing a ``_as_mpl_axes`` method. See
5252
:ref:`adding-new-scales` for more detail.
5353

54+
* A new keyword *extendfrac* in :meth:`~matplotlib.pyplot.colorbar` and
55+
:class:`~matplotlib.colorbar.ColorbarBase` allows one to control the size of
56+
the triangular minimum and maximum extensions on colorbars.
5457

5558
Changes in 1.1.x
5659
================

doc/users/whats_new.rst

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,37 @@ Damon McDougall added a new plotting method for the
2626

2727
.. plot:: mpl_examples/mplot3d/trisurf3d_demo.py
2828

29+
Control the lengths of colorbar extensions
30+
------------------------------------------
31+
32+
Andrew Dawson added a new keyword argument *extendfrac* to
33+
:meth:`~matplotlib.pyplot.colorbar` to control the length of
34+
minimum and maximum colorbar extensions.
35+
36+
.. plot::
37+
38+
import matplotlib.pyplot as plt
39+
import numpy as np
40+
41+
x = y = np.linspace(0., 2*np.pi, 100)
42+
X, Y = np.meshgrid(x, y)
43+
Z = np.cos(X) * np.sin(0.5*Y)
44+
45+
clevs = [-.75, -.5, -.25, 0., .25, .5, .75]
46+
cmap = plt.cm.get_cmap(name='jet', lut=8)
47+
48+
ax1 = plt.subplot(211)
49+
cs1 = plt.contourf(x, y, Z, clevs, cmap=cmap, extend='both')
50+
cb1 = plt.colorbar(orientation='horizontal', extendfrac=None)
51+
cb1.set_label('Default length colorbar extensions')
52+
53+
ax2 = plt.subplot(212)
54+
cs2 = plt.contourf(x, y, Z, clevs, cmap=cmap, extend='both')
55+
cb2 = plt.colorbar(orientation='horizontal', extendfrac='auto')
56+
cb2.set_label('Custom length colorbar extensions')
57+
58+
plt.show()
59+
2960
.. _whats-new-1-1:
3061

3162
new in matplotlib-1.1

examples/api/colorbar_only.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66

77
# Make a figure and axes with dimensions as desired.
88
fig = pyplot.figure(figsize=(8,3))
9-
ax1 = fig.add_axes([0.05, 0.65, 0.9, 0.15])
10-
ax2 = fig.add_axes([0.05, 0.25, 0.9, 0.15])
9+
ax1 = fig.add_axes([0.05, 0.80, 0.9, 0.15])
10+
ax2 = fig.add_axes([0.05, 0.475, 0.9, 0.15])
11+
ax3 = fig.add_axes([0.05, 0.15, 0.9, 0.15])
1112

1213
# Set the colormap and norm to correspond to the data for which
1314
# the colorbar will be used.
@@ -47,5 +48,27 @@
4748
orientation='horizontal')
4849
cb2.set_label('Discrete intervals, some other units')
4950

51+
# The third example illustrates the use of custom length colorbar
52+
# extensions, used on a colorbar with discrete intervals.
53+
cmap = mpl.colors.ListedColormap([[0., .4, 1.], [0., .8, 1.],
54+
[1., .8, 0.], [1., .4, 0.]])
55+
cmap.set_over((1., 0., 0.))
56+
cmap.set_under((0., 0., 1.))
57+
58+
bounds = [-1., -.5, 0., .5, 1.]
59+
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
60+
cb3 = mpl.colorbar.ColorbarBase(ax3, cmap=cmap,
61+
norm=norm,
62+
boundaries=[-10]+bounds+[10],
63+
extend='both',
64+
# Make the length of each extension
65+
# the same as the length of the
66+
# interior colors:
67+
extendfrac='auto',
68+
ticks=bounds,
69+
spacing='uniform',
70+
orientation='horizontal')
71+
cb3.set_label('Custom extension lengths, some other units')
72+
5073
pyplot.show()
5174

lib/matplotlib/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -992,7 +992,8 @@ def tk_window_focus():
992992
'matplotlib.tests.test_text',
993993
'matplotlib.tests.test_tightlayout',
994994
'matplotlib.tests.test_delaunay',
995-
'matplotlib.tests.test_legend'
995+
'matplotlib.tests.test_legend',
996+
'matplotlib.tests.test_colorbar',
996997
]
997998

998999
def test(verbosity=1):

lib/matplotlib/colorbar.py

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,29 @@
5959

6060
colormap_kw_doc = '''
6161
62-
=========== ====================================================
62+
============ ====================================================
6363
Property Description
64-
=========== ====================================================
64+
============ ====================================================
6565
*extend* [ 'neither' | 'both' | 'min' | 'max' ]
6666
If not 'neither', make pointed end(s) for out-of-
6767
range values. These are set for a given colormap
6868
using the colormap set_under and set_over methods.
69+
*extendfrac* [ *None* | 'auto' | length | lengths ]
70+
If set to *None*, both the minimum and maximum
71+
triangular colorbar extensions with have a length of
72+
5% of the interior colorbar length (this is the
73+
default setting). If set to 'auto', makes the
74+
triangular colorbar extensions the same lengths as
75+
the interior boxes (when *spacing* is set to
76+
'uniform') or the same lengths as the respective
77+
adjacent interior boxes (when *spacing* is set to
78+
'proportional'). If a scalar, indicates the length
79+
of both the minimum and maximum triangular colorbar
80+
extensions as a fraction of the interior colorbar
81+
length. A two-element sequence of fractions may also
82+
be given, indicating the lengths of the minimum and
83+
maximum colorbar extensions respectively as a
84+
fraction of the interior colorbar length.
6985
*spacing* [ 'uniform' | 'proportional' ]
7086
Uniform spacing gives each discrete color the same
7187
space; proportional makes the space proportional to
@@ -82,7 +98,7 @@
8298
given instead.
8399
*drawedges* [ False | True ] If true, draw lines at color
84100
boundaries.
85-
=========== ====================================================
101+
============ ====================================================
86102
87103
The following will probably be useful only in the context of
88104
indexed colors (that is, when the mappable has norm=NoNorm()),
@@ -221,6 +237,7 @@ def __init__(self, ax, cmap=None,
221237
format=None,
222238
drawedges=False,
223239
filled=True,
240+
extendfrac=None,
224241
):
225242
self.ax = ax
226243
self._patch_ax()
@@ -236,6 +253,7 @@ def __init__(self, ax, cmap=None,
236253
self.orientation = orientation
237254
self.drawedges = drawedges
238255
self.filled = filled
256+
self.extendfrac = extendfrac
239257
self.solids = None
240258
self.lines = None
241259
self.outline = None
@@ -616,6 +634,35 @@ def _extended_N(self):
616634
N += 1
617635
return N
618636

637+
def _get_extension_lengths(self, frac, automin, automax, default=0.05):
638+
'''
639+
Get the lengths of colorbar extensions.
640+
641+
A helper method for _uniform_y and _proportional_y.
642+
'''
643+
# Set the default value.
644+
extendlength = np.array([default, default])
645+
if isinstance(frac, str):
646+
if frac.lower() == 'auto':
647+
# Use the provided values when 'auto' is required.
648+
extendlength[0] = automin
649+
extendlength[1] = automax
650+
else:
651+
# Any other string is invalid.
652+
raise ValueError('invalid value for extendfrac')
653+
elif frac is not None:
654+
try:
655+
# Try to set min and max extension fractions directly.
656+
extendlength[:] = frac
657+
# If frac is a sequence contaning None then NaN may
658+
# be encountered. This is an error.
659+
if np.isnan(extendlength).any():
660+
raise ValueError()
661+
except (TypeError, ValueError):
662+
# Raise an error on encountering an invalid value for frac.
663+
raise ValueError('invalid value for extendfrac')
664+
return extendlength
665+
619666
def _uniform_y(self, N):
620667
'''
621668
Return colorbar data coordinates for *N* uniformly
@@ -624,16 +671,19 @@ def _uniform_y(self, N):
624671
if self.extend == 'neither':
625672
y = np.linspace(0, 1, N)
626673
else:
674+
automin = automax = 1. / (N - 1.)
675+
extendlength = self._get_extension_lengths(self.extendfrac,
676+
automin, automax, default=0.05)
627677
if self.extend == 'both':
628678
y = np.zeros(N + 2, 'd')
629-
y[0] = -0.05
630-
y[-1] = 1.05
679+
y[0] = 0. - extendlength[0]
680+
y[-1] = 1. + extendlength[1]
631681
elif self.extend == 'min':
632682
y = np.zeros(N + 1, 'd')
633-
y[0] = -0.05
683+
y[0] = 0. - extendlength[0]
634684
else:
635685
y = np.zeros(N + 1, 'd')
636-
y[-1] = 1.05
686+
y[-1] = 1. + extendlength[1]
637687
y[self._inside] = np.linspace(0, 1, N)
638688
return y
639689

@@ -648,10 +698,27 @@ def _proportional_y(self):
648698
y = y / (self._boundaries[-1] - self._boundaries[0])
649699
else:
650700
y = self.norm(self._boundaries.copy())
651-
if self._extend_lower():
652-
y[0] = -0.05
653-
if self._extend_upper():
654-
y[-1] = 1.05
701+
if self.extend == 'min':
702+
# Exclude leftmost interval of y.
703+
clen = y[-1] - y[1]
704+
automin = (y[2] - y[1]) / clen
705+
automax = (y[-1] - y[-2]) / clen
706+
elif self.extend == 'max':
707+
# Exclude rightmost interval in y.
708+
clen = y[-2] - y[0]
709+
automin = (y[1] - y[0]) / clen
710+
automax = (y[-2] - y[-3]) / clen
711+
else:
712+
# Exclude leftmost and rightmost intervals in y.
713+
clen = y[-2] - y[1]
714+
automin = (y[2] - y[1]) / clen
715+
automax = (y[-2] - y[-3]) / clen
716+
extendlength = self._get_extension_lengths(self.extendfrac,
717+
automin, automax, default=0.05)
718+
if self.extend in ('both', 'min'):
719+
y[0] = 0. - extendlength[0]
720+
if self.extend in ('both', 'max'):
721+
y[-1] = 1. + extendlength[1]
655722
yi = y[self._inside]
656723
norm = colors.Normalize(yi[0], yi[-1])
657724
y[self._inside] = norm(yi)

lib/matplotlib/tests/test_colorbar.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from matplotlib import rcParams, rcParamsDefault
2+
from matplotlib.testing.decorators import image_comparison
3+
import matplotlib.pyplot as plt
4+
from matplotlib.colors import BoundaryNorm
5+
from matplotlib.cm import get_cmap
6+
from matplotlib.colorbar import ColorbarBase
7+
8+
9+
def _colorbar_extensions(spacing):
10+
11+
# Create a color map and specify the levels it represents.
12+
cmap = get_cmap("RdBu", lut=5)
13+
clevs = [-5., -2.5, -.5, .5, 1.5, 3.5]
14+
15+
# Define norms for the color maps.
16+
norms = dict()
17+
norms['neither'] = BoundaryNorm(clevs, len(clevs)-1)
18+
norms['min'] = BoundaryNorm([-10]+clevs[1:], len(clevs)-1)
19+
norms['max'] = BoundaryNorm(clevs[:-1]+[10], len(clevs)-1)
20+
norms['both'] = BoundaryNorm([-10]+clevs[1:-1]+[10], len(clevs)-1)
21+
22+
# Create a figure and adjust whitespace for subplots.
23+
fig = plt.figure()
24+
fig.subplots_adjust(hspace=.6)
25+
26+
for i, extension_type in enumerate(('neither', 'min', 'max', 'both')):
27+
# Get the appropriate norm and use it to get colorbar boundaries.
28+
norm = norms[extension_type]
29+
boundaries = values = norm.boundaries
30+
for j, extendfrac in enumerate((None, 'auto', 0.1)):
31+
# Create a subplot.
32+
cax = fig.add_subplot(12, 1, i*3+j+1)
33+
# Turn off text and ticks.
34+
for item in cax.get_xticklabels() + cax.get_yticklabels() +\
35+
cax.get_xticklines() + cax.get_yticklines():
36+
item.set_visible(False)
37+
# Generate the colorbar.
38+
cb = ColorbarBase(cax, cmap=cmap, norm=norm,
39+
boundaries=boundaries, values=values,
40+
extend=extension_type, extendfrac=extendfrac,
41+
orientation='horizontal', spacing=spacing)
42+
43+
# Return the figure to the caller.
44+
return fig
45+
46+
47+
@image_comparison(
48+
baseline_images=['colorbar_extensions_uniform', 'colorbar_extensions_proportional'],
49+
extensions=['png'])
50+
def test_colorbar_extensions():
51+
# Use default params so .matplotlibrc doesn't cause the test to fail.
52+
rcParams.update(rcParamsDefault)
53+
# Create figures for uniform and proportionally spaced colorbars.
54+
fig1 = _colorbar_extensions('uniform')
55+
fig2 = _colorbar_extensions('proportional')
56+
57+
58+
if __name__ == '__main__':
59+
import nose
60+
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)
61+

0 commit comments

Comments
 (0)