10000 Merge pull request #956 from pelson/multiaxes_colorbar · matplotlib/matplotlib@d5b078b · GitHub
[go: up one dir, main page]

Skip to content

Commit d5b078b

Browse files
committed
Merge pull request #956 from pelson/multiaxes_colorbar
Shared axes colorbars & finer location control
2 parents 6b442b8 + 8952dee commit d5b078b

File tree

10 files changed

+236
-78
lines changed

10 files changed

+236
-78
lines changed

lib/matplotlib/colorbar.py

Lines changed: 137 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import matplotlib.patches as mpatches
3636
import matplotlib.path as mpath
3737
import matplotlib.ticker as ticker
38+
import matplotlib.transforms as mtrans
3839

3940
from matplotlib import docstring
4041

@@ -52,7 +53,8 @@
5253
*anchor* (0.0, 0.5) if vertical; (0.5, 1.0) if horizontal;
5354
the anchor point of the colorbar axes
5455
*panchor* (1.0, 0.5) if vertical; (0.5, 0.0) if horizontal;
55-
the anchor point of the colorbar parent axes
56+
the anchor point of the colorbar parent axes. If
57+
False, the parent axes' anchor will be unchanged
5658
============= ====================================================
5759
5860
'''
@@ -149,8 +151,9 @@
149151
*cax*
150152
None | axes object into which the colorbar will be drawn
151153
*ax*
152-
None | parent axes object from which space for a new
153-
colorbar axes will be stolen
154+
None | parent axes object(s) from which space for a new
155+
colorbar axes will be stolen. If a list of axes is given
156+
they will all be resized to make room for the colorbar axes.
154157
*use_gridspec*
155158
False | If *cax* is None, a new *cax* is created as an instance of
156159
Axes. If *ax* is an instance of Subplot and *use_gridspec* is True,
@@ -255,6 +258,7 @@ def __init__(self, ax, cmap=None,
255258
values=None,
256259
boundaries=None,
257260
orientation='vertical',
261+
ticklocation='auto',
258262
extend='neither',
259263
spacing='uniform', # uniform or proportional
260264
ticks=None,
@@ -263,6 +267,7 @@ def __init__(self, ax, cmap=None,
263267
filled=True,
264268
extendfrac=None,
265269
extendrect=False,
270+
label='',
266271
):
267272
self.ax = ax
268273
self._patch_ax()
@@ -287,7 +292,12 @@ def __init__(self, ax, cmap=None,
287292
self.outline = None
288293
self.patch = None
289294
self.dividers = None
290-
self.set_label('')
295+
296+
if ticklocation == 'auto':
297+
ticklocation = 'bottom' if orientation == 'horizontal' else 'right'
298+
self.ticklocation = ticklocation
299+
300+
self.set_label(label)
291301
if cbook.iterable(ticks):
292302
self.locator = ticker.FixedLocator(ticks, nbins=len(ticks))
293303
else:
@@ -336,11 +346,14 @@ def config_axis(self):
336346
ax = self.ax
337347
if self.orientation == 'vertical':
338348
ax.xaxis.set_ticks([])
339-
ax.yaxis.set_label_position('right')
340-
ax.yaxis.set_ticks_position('right')
349+
# location is either one of 'bottom' or 'top'
350+
ax.yaxis.set_label_position(self.ticklocation)
351+
ax.yaxis.set_ticks_position(self.ticklocation)
341352
else:
342353
ax.yaxis.set_ticks([])
343-
ax.xaxis.set_label_position('bottom')
354+
# location is either one of 'left' or 'right'
355+
ax.xaxis.set_label_position(self.ticklocation)
356+
ax.xaxis.set_ticks_position(self.ticklocation)
344357

345358
self._set_label()
346359

@@ -835,11 +848,10 @@ class Colorbar(ColorbarBase):
835848
836849
"""
837850
def __init__(self, ax, mappable, **kw):
838-
mappable.autoscale_None() # Ensure mappable.norm.vmin, vmax
839-
# are set when colorbar is called,
840-
# even if mappable.draw has not yet
841-
# been called. This will not change
842-
# vmin, vmax if they are already set.
851+
# Ensure the given mappable's norm has appropriate vmin and vmax set
852+
# even if mappable.draw has not yet been called.
853+
mappable.autoscale_None()
854+
843855
self.mappable = mappable
844856
kw['cmap'] = mappable.cmap
845857
kw['norm'] = mappable.norm
@@ -948,47 +960,118 @@ def update_bruteforce(self, mappable):
948960

949961

950962
@docstring.Substitution(make_axes_kw_doc)
951-
def make_axes(parent, **kw):
963+
def make_axes(parents, location=None, orientation=None, fraction=0.15,
964+
shrink=1.0, aspect=20, **kw):
952965
'''
953-
Resize and reposition a parent axes, and return a child
966+
Resize and reposition parent axes, and return a child
954967
axes suitable for a colorbar::
955968
956969
cax, kw = make_axes(parent, **kw)
957970
958971
Keyword arguments may include the following (with defaults):
959972
960-
*orientation*
961-
'vertical' or 'horizontal'
973+
*location*: [**None**|'left'|'right'|'top'|'bottom']
974+
The position, relative to **parents**, where the colorbar axes
975+
should be created. If None, the value will either come from the
976+
given **orientation**, else it will default to 'right'.
962977
963-
%s
978+
*orientation*: [**None**|'vertical' 10000 ;|'horizontal']
979+
The orientation of the colorbar. Typically, this keyword shouldn't
980+
be used, as it can be derived from the **location** keyword.
964981
965-
All but the first of these are stripped from the input kw set.
982+
%s
966983
967-
Returns (cax, kw), the child axes and the reduced kw dictionary.
984+
Returns (cax, kw), the child axes and the reduced kw dictionary to be
985+
passed when creating the colorbar instance.
968986
'''
969-
orientation = kw.setdefault('orientation', 'vertical')
970-
fraction = kw.pop('fraction', 0.15)
971-
shrink = kw.pop('shrink', 1.0)
972-
aspect = kw.pop('aspect', 20)
973-
#pb = transforms.PBox(parent.get_position())
974-
pb = parent.get_position(original=True).frozen()
975-
if orientation == 'vertical':
976-
pad = kw.pop('pad', 0.05)
977-
x1 = 1.0 - fraction
978-
pb1, pbx, pbcb = pb.splitx(x1 - pad, x1)
979-
pbcb = pbcb.shrunk(1.0, shrink).anchored('C', pbcb)
980-
anchor = kw.pop('anchor', (0.0, 0.5))
981-
panchor = kw.pop('panchor', (1.0, 0.5))
987+
locations = ["left", "right", "top", "bottom"]
988+
if orientation is not None and location is not None:
989+
raise TypeError('position and orientation are mutually exclusive. Consider ' \
990+
'setting the position to any of %s' % ', '.join(locations))
991+
992+
# provide a default location
993+
if location is None and orientation is None:
994+
location = 'right'
995+
996+
# allow the user to not specify the location by specifying the orientation instead
997+
if location is None:
998+
location = 'right' if orientation == 'vertical' else 'bottom'
999+
1000+
if location not in locations:
1001+
raise ValueError('Invalid colorbar location. Must be one of %s' % ', '.join(locations))
1002+
1003+
default_location_settings = {'left': {'anchor': (1.0, 0.5),
1004+
'panchor': (0.0, 0.5),
1005+
'pad': 0.10,
1006+
'orientation': 'vertical'},
1007+
'right': {'anchor': (0.0, 0.5),
1008+
'panchor': (1.0, 0.5),
1009+
'pad': 0.05,
1010+
'orientation': 'vertical'},
1011+
'top': {'anchor': (0.5, 0.0),
1012+
'panchor': (0.5, 1.0),
1013+
'pad': 0.05,
1014+
'orientation': 'horizontal'},
1015+
'bottom': {'anchor': (0.5, 1.0),
1016+
'panchor': (0.5, 0.0),
1017+
'pad': 0.15, # backwards compat
1018+
'orientation': 'horizontal'},
1019+
}
1020+
1021+
loc_settings = default_location_settings[location]
1022+
1023+
# put appropriate values into the kw dict for passing back to
1024+
# the Colorbar class
1025+
kw['orientation'] = loc_settings['orientation']
1026+
kw['ticklocation'] = location
1027+
1028+
anchor = kw.pop('anchor', loc_settings['anchor'])
1029+
parent_anchor = kw.pop('panchor', loc_settings['panchor'])
1030+
pad = kw.pop('pad', loc_settings['pad'])
1031+
1032+
1033+
# turn parents into a list if it is not already
1034+
if not isinstance(parents, (list, tuple)):
1035+
parents = [parents]
1036+
1037+
fig = parents[0].get_figure()
1038+
if not all(fig is ax.get_figure() for ax in parents):
1039+
raise ValueError('Unable to create a colorbar axes as not all ' + \
1040+
'parents share the same figure.')
1041+
1042+
# take a bounding box around all of the given axes
1043+
parents_bbox = mtrans.Bbox.union([ax.get_position(original=True).frozen() \
1044+
for ax in parents])
1045+
1046+
pb = parents_bbox
1047+
< 179B span class=pl-k>if location in ('left', 'right'):
1048+
if location == 'left':
1049+
pbcb, _, pb1 = pb.splitx(fraction, fraction + pad)
1050+
else:
1051+
pb1, _, pbcb = pb.splitx(1 - fraction - pad, 1 - fraction)
1052+
pbcb = pbcb.shrunk(1.0, shrink).anchored(anchor, pbcb)
9821053
else:
983-
pad = kw.pop('pad', 0.15)
984-
pbcb, pbx, pb1 = pb.splity(fraction, fraction + pad)
985-
pbcb = pbcb.shrunk(shrink, 1.0).anchored('C', pbcb)
986-
aspect = 1.0 / aspect
987-
anchor = kw.pop('anchor', (0.5, 1.0))
988-
panchor = kw.pop('panchor', (0.5, 0.0))
989-
parent.set_position(pb1)
990-
parent.set_anchor(panchor)
991-
fig = parent.get_figure()
1054+
if location == 'bottom':
1055+
pbcb, _, pb1 = pb.splity(fraction, fraction + pad)
1056+
else:
1057+
pb1, _, pbcb = pb.splity(1 - fraction - pad, 1 - fraction)
1058+
pbcb = pbcb.shrunk(shrink, 1.0).anchored(anchor, pbcb)
1059+
1060+
# define the aspect ratio in terms of y's per x rather than x's per y
1061+
aspect = 1.0/aspect
1062+
1063+
# define a transform which takes us from old axes coordinates to
1064+
# new axes coordinates
1065+
shrinking_trans = mtrans.BboxTransform(parents_bbox, pb1)
1066+
1067+
# transform each of the axes in parents using the new transform
1068+
for ax in parents:
1069+
new_posn = shrinking_trans.transform(ax.get_position())
1070+
new_posn = mtrans.Bbox(new_posn)
1071+
ax.set_position(new_posn)
1072+
if parent_anchor is not False:
1073+
ax.set_anchor(parent_anchor)
1074+
9921075
cax = fig.add_axes(pbcb)
9931076
cax.set_aspect(aspect, anchor=anchor, adjustable='box')
9941077
return cax, kw
@@ -1001,6 +1084,9 @@ def make_axes_gridspec(parent, **kw):
10011084
suitable for a colorbar. This function is similar to
10021085
make_axes. Prmary differences are
10031086
1087+
* *make_axes_gridspec* only handles the *orientation* keyword
1088+
and cannot handle the "location" keyword.
1089+
10041090
* *make_axes_gridspec* should only be used with a subplot parent.
10051091
10061092
* *make_axes* creates an instance of Axes. *make_axes_gridspec*
@@ -1018,16 +1104,19 @@ def make_axes_gridspec(parent, **kw):
10181104
Keyword arguments may include the following (with defaults):
10191105
10201106
*orientation*
1021-
'vertical' or 'horizontal'
1107+
'vertical' or 'horizontal'
10221108
10231109
%s
10241110
10251111
All but the first of these are stripped from the input kw set.
10261112
1027-
Returns (cax, kw), the child axes and the reduced kw dictionary.
1113+
Returns (cax, kw), the child axes and the reduced kw dictionary to be
1114+
passed when creating the colorbar instance.
10281115
'''
10291116

10301117
orientation = kw.setdefault('orientation', 'vertical')
1118+
kw['ticklocation'] = 'auto'
1119+
10311120
fraction = kw.pop('fraction', 0.15)
10321121
shrink = kw.pop('shrink', 1.0)
10331122
aspect = kw.pop('aspect', 20)
@@ -1139,11 +1228,8 @@ def _add_solids(self, X, Y, C):
11391228

11401229
patch = mpatches.PathPatch(mpath.Path(xy),
11411230
facecolor=self.cmap(self.norm(val)),
1142-
hatch=hatch,
1143-
edgecolor='none', linewidth=0,
1144-
antialiased=False, **kw
1145-
)
1146-
1231+
hatch=hatch, linewidth=0,
1232+
antialiased=False, **kw)
11471233
self.ax.add_patch(patch)
11481234
patches.append(patch)
11491235

@@ -1158,12 +1244,9 @@ def _add_solids(self, X, Y, C):
11581244
self.dividers = None
11591245

11601246
if self.drawedges:
1161-
self.dividers = collections.LineCollection(
1162-
self._edges(X, Y),
1163-
colors=(mpl.rcParams['axes.edgecolor'],),
1164-
linewidths=(
1165-
0.5 * mpl.rcParams['axes.linewidth'],)
1166-
)
1247+
self.dividers = collections.LineCollection(self._edges(X, Y),
1248+
colors=(mpl.rcParams['axes.edgecolor'],),
1249+
linewidths=(0.5 * mpl.rcParams['axes.linewidth'],))
11671250
self.ax.add_collection(self.dividers)
11681251

11691252
self.ax.hold(_hold)

lib/matplotlib/figure.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,7 +1418,7 @@ def savefig(self, *args, **kwargs):
14181418
ax.patch.set_edgecolor(cc[1])
14191419

14201420
@docstring.dedent_interpd
1421-
def colorbar(self, mappable, cax=None, ax=None, **kw):
1421+
def colorbar(self, mappable, cax=None, ax=None, use_gridspec=True, **kw):
14221422
"""
14231423
Create a colorbar for a ScalarMappable instance, *mappable*.
14241424
@@ -1427,7 +1427,10 @@ def colorbar(self, mappable, cax=None, ax=None, **kw):
14271427
"""
14281428
if ax is None:
14291429
ax = self.gca()
1430-
use_gridspec = kw.pop("use_gridspec", True)
1430+
1431+
# Store the value of gca so that we can set it back later on.
1432+
current_ax = self.gca()
1433+
14311434
if cax is None:
14321435
if use_gridspec and isinstance(ax, SubplotBase):
14331436
cax, kw = cbar.make_axes_gridspec(ax, **kw)
@@ -1436,7 +1439,7 @@ def colorbar(self, mappable, cax=None, ax=None, **kw):
14361439
cax.hold(True)
14371440
cb = cbar.colorbar_factory(cax, mappable, **kw)
14381441

1439-
self.sca(ax)
1442+
self.sca(current_ax)
14401443
return cb
14411444

14421445
def subplots_adjust(self, *args, **kwargs):
@@ -1451,17 +1454,15 @@ def subplots_adjust(self, *args, **kwargs):
14511454
14521455
"""
14531456
self.subplotpars.update(*args, **kwargs)
1454-
import matplotlib.axes
14551457
for ax in self.axes:
1456-
if not isinstance(ax, matplotlib.axes.SubplotBase):
1458+
if not isinstance(ax, SubplotBase):
14571459
# Check if sharing a subplots axis
14581460
if (ax._sharex is not None and
1459-
isinstance(ax._sharex,
1460-
matplotlib.axes.SubplotBase)):
1461+
isinstance(ax._sharex, SubplotBase)):
14611462
ax._sharex.update_params()
14621463
ax.set_position(ax._sharex.figbox)
14631464
elif (ax._sharey is not None and
1464-
isinstance(ax._sharey, matplotlib.axes.SubplotBase)):
1465+
isinstance(ax._sharey, SubplotBase)):
14651466
ax._sharey.update_params()
14661467
ax.set_position(ax._sharey.figbox)
14671468
else:

0 commit comments

Comments
 (0)
0