8000 Support location="left"/"top" for gridspec-based colorbars. · matplotlib/matplotlib@88d895d · GitHub
[go: up one dir, main page]

Skip to content

Commit 88d895d

Browse files
committed
Support location="left"/"top" for gridspec-based colorbars.
They were already supported for non-gridspec-based colorbars, but adding support to make_axes_gridspec was not too hard.
1 parent 45d27c9 commit 88d895d

File tree

3 files changed

+84
-104
lines changed

3 files changed

+84
-104
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Gridspec-based colorbars can now be positioned above or to the right of the main axes
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
... by passing ``location="top"`` or ``location="left"`` to the ``colorbar()``
5+
call.

lib/matplotlib/colorbar.py

Lines changed: 70 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
import matplotlib.colors as colors
4444
import matplotlib.contour as contour
4545
import matplotlib.cm as cm
46-
import matplotlib.gridspec as gridspec
4746
import matplotlib.patches as mpatches
4847
import matplotlib.path as mpath
4948
import matplotlib.ticker as ticker
@@ -55,6 +54,18 @@
5554
_log = logging.getLogger(__name__)
5655

5756
_make_axes_param_doc = """
57+
location : None or {'left', 'right', 'top', 'bottom'}
58+
The location, relative to the parent axes, where the colorbar axes
59+
is created. It also determines the *orientation* of the colorbar
60+
(colorbars on the left and right are vertical, colorbars at the top
61+
and bottom are horizontal). If None, the location will come from the
62+
*orientation* if it is set (vertical colorbars on the right, horizontal
63+
ones at the bottom), or default to 'right' if *orientation* is unset.
64+
orientation : None or {'vertical', 'horizontal'}
65+
The orientation of the colorbar. It is preferrable to set the
66+
*location* of the colorbar, as that also determines the *orientation*;
67+
passing incompatible values for *location* and *orientation* raises an
68+
exception.
5869
fraction : float, default: 0.15
5970
Fraction of original axes to use for colorbar.
6071
shrink : float, default: 1.0
@@ -1363,6 +1374,27 @@ def remove(self):
13631374
ax.set_subplotspec(subplotspec)
13641375

13651376

1377+
def _normalize_location_orientation(location, orientation):
1378+
if location is None:
1379+
location = cbook._check_getitem(
1380+
{None: "right", "vertical": "right", "horizontal": "bottom"},
1381+
orientation=orientation)
1382+
loc_settings = cbook._check_getitem({
1383+
"left": {"location": "left", "orientation": "vertical",
1384+
"anchor": (1.0, 0.5), "panchor": (0.0, 0.5), "pad": 0.10},
1385+
"right": {"location": "right", "orientation": "vertical",
1386+
"anchor": (0.0, 0.5), "panchor": (1.0, 0.5), "pad": 0.05},
1387+
"top": {"location": "top", "orientation": "horizontal",
1388+
"anchor": (0.5, 0.0), "panchor": (0.5, 1.0), "pad": 0.05},
1389+
"bottom": {"location": "bottom", "orientation": "horizontal",
1390+
"anchor": (0.5, 1.0), "panchor": (0.5, 0.0), "pad": 0.15},
1391+
}, location=location)
1392+
if orientation is not None and orientation != loc_settings["orientation"]:
1393+
# Allow the user to pass both if they are consistent.
1394+
raise TypeError("location and orientation are mutually exclusive")
1395+
return loc_settings
1396+
1397+
13661398
@docstring.Substitution(_make_axes_param_doc, 1E79 _make_axes_other_param_doc)
13671399
def make_axes(parents, location=None, orientation=None, fraction=0.15,
13681400
shrink=1.0, aspect=20, **kw):
@@ -1376,16 +1408,6 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15,
13761408
----------
13771409
parents : `~.axes.Axes` or list of `~.axes.Axes`
13781410
The Axes to use as parents for placing the colorbar.
1379-
1380-
location : None or {'left', 'right', 'top', 'bottom'}
1381-
The position, relative to *parents*, where the colorbar axes
1382-
should be created. If None, the value will either come from the
1383-
given ``orientation``, else it will default to 'right'.
1384-
1385-
orientation : None or {'vertical', 'horizontal'}
1386-
The orientation of the colorbar. Typically, this keyword shouldn't
1387-
be used, as it can be derived from the ``location`` keyword.
1388-
13891411
%s
13901412
13911413
Returns
@@ -1400,47 +1422,11 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15,
14001422
----------------
14011423
%s
14021424
"""
1403-
locations = ["left", "right", "top", "bottom"]
1404-
if orientation is not None and location is not None:
1405-
raise TypeError('position and orientation are mutually exclusive. '
1406-
'Consider setting the position to any of {}'
1407-
.format(', '.join(locations)))
1408-
1409-
# provide a default location
1410-
if location is None and orientation is None:
1411-
location = 'right'
1412-
1413-
# allow the user to not specify the location by specifying the
1414-
# orientation instead
1415-
if location is None:
1416-
location = 'right' if orientation == 'vertical' else 'bottom'
1417-
1418-
cbook._check_in_list(locations, location=location)
1419-
1420-
default_location_settings = {'left': {'anchor': (1.0, 0.5),
1421-
'panchor': (0.0, 0.5),
1422-
'pad': 0.10,
1423-
'orientation': 'vertical'},
1424-
'right': {'anchor': (0.0, 0.5),
1425-
'panchor': (1.0, 0.5),
1426-
'pad': 0.05,
1427-
'orientation': 'vertical'},
1428-
'top': {'anchor': (0.5, 0.0),
1429-
'panchor': (0.5, 1.0),
1430-
'pad': 0.05,
1431-
'orientation': 'horizontal'},
1432-
'bottom': {'anchor': (0.5, 1.0),
1433-
'panchor': (0.5, 0.0),
1434-
'pad': 0.15, # backwards compat
1435-
'orientation': 'horizontal'},
1436-
}
1437-
1438-
loc_settings = default_location_settings[location]
1439-
1425+
loc_settings = _normalize_location_orientation(location, orientation)
14401426
# put appropriate values into the kw dict for passing back to
14411427
# the Colorbar class
14421428
kw['orientation'] = loc_settings['orientation']
1443-
kw['ticklocation'] = location
1429+
location = kw['ticklocation'] = loc_settings['location']
14441430

14451431
anchor = kw.pop('anchor', loc_settings['anchor'])
14461432
parent_anchor = kw.pop('panchor', loc_settings['panchor'])
@@ -1533,7 +1519,8 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15,
15331519

15341520

15351521
@docstring.Substitution(_make_axes_param_doc, _make_axes_other_param_doc)
1536-
def make_axes_gridspec(parent, *, fraction=0.15, shrink=1.0, aspect=20, **kw):
1522+
def make_axes_gridspec(parent, *, location=None, orientation=None,
1523+
fraction=0.15, shrink=1.0, aspect=20, **kw):
15371524
"""
15381525
Create a `~.SubplotBase` suitable for a colorbar.
15391526
@@ -1542,9 +1529,6 @@ def make_axes_gridspec(parent, *, fraction=0.15, shrink=1.0, aspect=20, **kw):
15421529
15431530
This function is similar to `.make_axes`. Primary differences are
15441531
1545-
- `.make_axes_gridspec` only handles the *orientation* keyword
1546-
and cannot handle the *location* keyword.
1547-
15481532
- `.make_axes_gridspec` should only be used with a `.SubplotBase` parent.
15491533
15501534
- `.make_axes` creates an `~.axes.Axes`; `.make_axes_gridspec` creates a
@@ -1560,7 +1544,6 @@ def make_axes_gridspec(parent, *, fraction=0.15, shrink=1.0, aspect=20, **kw):
15601544
----------
15611545
parent : `~.axes.Axes`
15621546
The Axes to use as parent for placing the colorbar.
1563-
15641547
%s
15651548
15661549
Returns
@@ -1573,63 +1556,54 @@ def make_axes_gridspec(parent, *, fraction=0.15, shrink=1.0, aspect=20, **kw):
15731556
15741557
Other Parameters
15751558
----------------
1576-
orientation : {'vertical', 'horizontal'}, default: 'vertical'
1577-
The orientation of the colorbar.
1578-
15791559
%s
15801560
"""
15811561

1582-
orientation = kw.setdefault('orientation', 'vertical')
1583-
kw['ticklocation'] = 'auto'
1562+
loc_settings = _normalize_location_orientation(location, orientation)
1563+
kw['orientation'] = loc_settings['orientation']
1564+
location = kw['ticklocation'] = loc_settings['location']
15841565

1585-
x1 = 1 - fraction
1566+
pad = loc_settings["pad"]
1567+
wh_space = 2 * pad / (1 - pad)
15861568

15871569
# for shrinking
15881570
pad_s = (1 - shrink) * 0.5
15891571
wh_ratios = [pad_s, shrink, pad_s]
15901572

1591-
# we need to none the tree of layoutboxes because
1592-
# constrained_layout can't remove and replace the tree
1593-
# hierarchy w/o a seg fault.
1594-
gs = parent.get_subplotspec().get_gridspec()
1595-
layoutbox.nonetree(gs._layoutbox)
1596-
gs_from_subplotspec = gridspec.GridSpecFromSubplotSpec
1597-
if orientation == 'vertical':
1598-
pad = kw.pop('pad', 0.05)
1599-
wh_space = 2 * pad / (1 - pad)
1600-
gs = gs_from_subplotspec(1, 2,
1601-
subplot_spec=parent.get_subplotspec(),
1602-
wspace=wh_space,
1603-
width_ratios=[x1 - pad, fraction])
1604-
gs2 = gs_from_subplotspec(3, 1,
1605-
subplot_spec=gs[1],
1606-
hspace=0.,
1607-
height_ratios=wh_ratios)
1608-
anchor = (0.0, 0.5)
1609-
panchor = (1.0, 0.5)
1610-
else:
1611-
pad = kw.pop('pad', 0.15)
1612-
wh_space = 2 * pad / (1 - pad)
1613-
gs = gs_from_subplotspec(2, 1,
1614-
subplot_spec=parent.get_subplotspec(),
1615-
hspace=wh_space,
1616-
height_ratios=[x1 - pad, fraction])
1617-
gs2 = gs_from_subplotspec(1, 3,
1618-
subplot_spec=gs[1],
1619-
wspace=0.,
1620-
width_ratios=wh_ratios)
1573+
# we need to none the tree of layoutboxes because constrained_layout can't
1574+
# remove and replace the tree hierarchy w/o a segfault.
1575+
layoutbox.nonetree(parent.get_subplotspec().get_gridspec()._layoutbox)
1576+
if location == "left":
1577+
gs = parent.get_subplotspec().subgridspec(
1578+
1, 2, wspace=wh_space, width_ratios=[fraction, 1-fraction-pad])
1579+
ss_main = gs[1]
1580+
ss_cb = gs[0].subgridspec(3, 1, hspace=0, height_ratios=wh_ratios)[1]
1581+
elif location == "right":
1582+
gs = parent.get_subplotspec().subgridspec(
1583+
1, 2, wspace=wh_space, width_ratios=[1-fraction-pad, fraction])
1584+
ss_main = gs[0]
1585+
ss_cb = gs[1].subgridspec(3, 1, hspace=0, height_ratios=wh_ratios)[1]
1586+
elif location == "top":
1587+
gs = parent.get_subplotspec().subgridspec(
1588+
2, 1, hspace=wh_space, height_ratios=[fraction, 1-fraction-pad])
1589+
ss_main = gs[1]
1590+
ss_cb = gs[0].subgridspec(1, 3, wspace=0, width_ratios=wh_ratios)[1]
1591+
aspect = 1 / aspect
1592+
else: # "bottom"
1593+
gs = parent.get_subplotspec().subgridspec(
1594+
2, 1, hspace=wh_space, height_ratios=[1-fraction-pad, fraction])
1595+
ss_main = gs[0]
1596+
ss_cb = gs[1].subgridspec(1, 3, wspace=0, width_ratios=wh_ratios)[1]
16211597
aspect = 1 / aspect
1622-
anchor = (0.5, 1.0)
1623-
panchor = (0.5, 0.0)
16241598

1625-
parent.set_subplotspec(gs[0])
1599+
parent.set_subplotspec(ss_main)
16261600
parent.update_params()
16271601
parent._set_position(parent.figbox)
1628-
parent.set_anchor(panchor)
1602+
parent.set_anchor(loc_settings["panchor"])
16291603

16301604
fig = parent.get_figure()
1631-
cax = fig.add_subplot(gs2[1], label="<colorbar>")
1632-
cax.set_aspect(aspect, anchor=anchor, adjustable='box')
1605+
cax = fig.add_subplot(ss_cb, label="<colorbar>")
1606+
cax.set_aspect(aspect, anchor=loc_settings["anchor"], adjustable='box')
16331607
return cax, kw
16341608

16351609

lib/matplotlib/tests/test_colorbar.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -115,28 +115,29 @@ def test_colorbar_extension_length():
115115
_colorbar_extension_length('proportional')
116116

117117

118+
@pytest.mark.parametrize('use_gridspec', [True, False])
118119
@image_comparison(['cbar_with_orientation',
119120
'cbar_locationing',
120121
'double_cbar',
121122
'cbar_sharing',
122123
],
123124
extensions=['png'], remove_text=True,
124125
savefig_kwarg={'dpi': 40})
125-
def test_colorbar_positioning():
126+
def test_colorbar_positioning(use_gridspec):
126127
data = np.arange(1200).reshape(30, 40)
127128
levels = [0, 200, 400, 600, 800, 1000, 1200]
128129

129130
# -------------------
130131
plt.figure()
131132
plt.contourf(data, levels=levels)
132-
plt.colorbar(orientation='horizontal', use_gridspec=False)
133+
plt.colorbar(orientation='horizontal', use_gridspec=use_gridspec)
133134

134135
locations = ['left', 'right', 'top', 'bottom']
135136
plt.figure()
136137
for i, location in enumerate(locations):
137138
plt.subplot(2, 2, i + 1)
138139
plt.contourf(data, levels=levels)
139-
plt.colorbar(location=location, use_gridspec=False)
140+
plt.colorbar(location=location, use_gridspec=use_gridspec)
140141

141142
# -------------------
142143
plt.figure()
@@ -152,9 +153,9 @@ def test_colorbar_positioning():
152153
plt.contour(hatch_mappable, colors='black')
153154

154155
plt.colorbar(color_mappable, location='left', label='variable 1',
155-
use_gridspec=False)
156+
use_gridspec=use_gridspec)
156157
plt.colorbar(hatch_mappable, location='right', label='variable 2',
157-
use_gridspec=False)
158+
use_gridspec=use_gridspec)
158159

159160
# -------------------
160161
plt.figure()
@@ -166,11 +167,11 @@ def test_colorbar_positioning():
166167
plt.contourf(data, levels=levels)
167168

168169
plt.colorbar(ax=[ax2, ax3, ax1], location='right', pad=0.0, shrink=0.5,
169-
panchor=False, use_gridspec=False)
170+
panchor=False, use_gridspec=use_gridspec)
170171
plt.colorbar(ax=[ax2, ax3, ax1], location='left', shrink=0.5,
171-
panchor=False, use_gridspec=False)
172+
panchor=False, use_gridspec=use_gridspec)
172173
plt.colorbar(ax=[ax1], location='bottom', panchor=False,
173-
anchor=(0.8, 0.5), shrink=0.6, use_gridspec=False)
174+
anchor=(0.8, 0.5), shrink=0.6, use_gridspec=use_gridspec)
174175

175176

176177
@image_comparison(['cbar_with_subplots_adjust.png'], remove_text=True,

0 commit comments

Comments
 (0)
0