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

Skip to content

Commit 8b4ad6c

Browse files
committed
Add location keyword argument to Colorbar
1 parent 98b1f82 commit 8b4ad6c

File tree

3 files changed

+114
-15
lines changed

3 files changed

+114
-15
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
``colorbar`` now has a *location* keyword argument
2+
==================================================
3+
4+
The ``colorbar`` method now supports a *location* keyword argument to more
5+
easily position the color bar. This is useful when providing your own inset
6+
axes using the *cax* keyword argument and behaves similar to the case where
7+
axes are not provided (where the *location* keyword is passed through).
8+
*orientation* and *ticklocation* are no longer required as they are
9+
determined by *location*. *ticklocation* can still be provided if the
10+
automatic setting is not preferred. (*orientation* can also be provided but
11+
must be compatible with the *location*.)
12+
13+
An example is:
14+
15+
.. plot::
16+
:include-source: true
17+
18+
import matplotlib.pyplot as plt
19+
import numpy as np
20+
rng = np.random.default_rng(19680801)
21+
imdata = rng.random((10, 10))
22+
fig, ax = plt.subplots()
23+
im = ax.imshow(imdata)
24+
fig.colorbar(im, cax=ax.inset_axes([0, 1.05, 1, 0.05]),
25+
location='top')

lib/matplotlib/colorbar.py

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -246,14 +246,35 @@ class Colorbar:
246246
alpha : float
247247
The colorbar transparency between 0 (transparent) and 1 (opaque).
248248
249-
orientation : {'vertical', 'horizontal'}
249+
orientation : None or {'vertical', 'horizontal'}
250+
If None, use the value determined by *location*. If both
251+
*orientation* and *location* are None then defaults to 'vertical'.
250252
251253
ticklocation : {'auto', 'left', 'right', 'top', 'bottom'}
254+
The location of the colorbar ticks. The *ticklocation* must match
255+
*orientation*. For example, a horizontal colorbar can only have ticks
256+
at the top or the bottom. If 'auto', the ticks will be the same as
257+
*location*, so a colorbar to the left will have ticks to the left. If
258+
*location* is None, the ticks will be at the bottom for a horizontal
259+
colorbar and at the right for a vertical.
252260
253261
drawedges : bool
262+
Whether to draw lines at color boundaries.
254263
255264
filled : bool
265+
256266
%(_colormap_kw_doc)s
267+
268+
location : None or {'left', 'right', 'top', 'bottom'}
269+
Set the *orientation* and *ticklocation* of the colorbar using a
270+
single argument. Colorbars on the left and right are vertical,
271+
colorbars at the top and bottom are horizontal. The *ticklocation* is
272+
the same as *location*, so if *location* is 'top', the ticks are on
273+
the top. *orientation* and/or *ticklocation* can be provided as well
274+
and overrides the value set by *location*, but there will be an error
275+
for incompatible combinations.
276+
277+
.. versionadded:: 3.7
257278
"""
258279

259280
n_rasterize = 50 # rasterize solids if number of colors >= n_rasterize
@@ -264,7 +285,7 @@ def __init__(self, ax, mappable=None, *, cmap=None,
264285
alpha=None,
265286
values=None,
266287
boundaries=None,
267-
orientation='vertical',
288+
orientation=None,
268289
ticklocation='auto',
269290
extend=None,
270291
spacing='uniform', # uniform or proportional
@@ -275,6 +296,7 @@ def __init__(self, ax, mappable=None, *, cmap=None,
275296
extendfrac=None,
276297
extendrect=False,
277298
label='',
299+
location=None,
278300
):
279301

280302
if mappable is None:
@@ -305,14 +327,23 @@ def __init__(self, ax, mappable=None, *, cmap=None,
305327
mappable.colorbar_cid = mappable.callbacks.connect(
306328
'changed', self.update_normal)
307329

330+
location_orientation = _get_orientation_from_location(location)
331+
308332
_api.check_in_list(
309-
['vertical', 'horizontal'], orientation=orientation)
333+
[None, 'vertical', 'horizontal'], orientation=orientation)
310334
_api.check_in_list(
311335
['auto', 'left', 'right', 'top', 'bottom'],
312336
ticklocation=ticklocation)
313337
_api.check_in_list(
314338
['uniform', 'proportional'], spacing=spacing)
315339

340+
if location_orientation is not None and orientation is not None:
341+
if location_orientation != orientation:
342+
raise TypeError(
343+
"location and orientation are mutually exclusive")
344+
else:
345+
orientation = orientation or location_orientation or "vertical"
346+
316347
self.ax = ax
317348
self.ax._axes_locator = _ColorbarAxesLocator(self)
318349

@@ -365,7 +396,8 @@ def __init__(self, ax, mappable=None, *, cmap=None,
365396
self.__scale = None # linear, log10 for now. Hopefully more?
366397

367398
if ticklocation == 'auto':
368-
ticklocation = 'bottom' if orientation == 'horizontal' else 'right'
399+
ticklocation = _get_ticklocation_from_orientation(
400+
orientation) if location is None else location
369401
self.ticklocation = ticklocation
370402

371403
self.set_label(label)
@@ -1330,25 +1362,36 @@ def drag_pan(self, button, key, x, y):
13301362

13311363
def _normalize_location_orientation(location, orientation):
13321364
if location is None:
1333-
location = _api.check_getitem(
1334-
{None: "right", "vertical": "right", "horizontal": "bottom"},
1335-
orientation=orientation)
1365+
location = _get_ticklocation_from_orientation(orientation)
13361366
loc_settings = _api.check_getitem({
1337-
"left": {"location": "left", "orientation": "vertical",
1338-
"anchor": (1.0, 0.5), "panchor": (0.0, 0.5), "pad": 0.10},
1339-
"right": {"location": "right", "orientation": "vertical",
1340-
"anchor": (0.0, 0.5), "panchor": (1.0, 0.5), "pad": 0.05},
1341-
"top": {"location": "top", "orientation": "horizontal",
1342-
"anchor": (0.5, 0.0), "panchor": (0.5, 1.0), "pad": 0.05},
1343-
"bottom": {"location": "bottom", "orientation": "horizontal",
1344-
"anchor": (0.5, 1.0), "panchor": (0.5, 0.0), "pad": 0.15},
1367+
"left": {"location": "left", "anchor": (1.0, 0.5),
1368+
"panchor": (0.0, 0.5), "pad": 0.10},
1369+
"right": {"location": "right", "anchor": (0.0, 0.5),
1370+
"panchor": (1.0, 0.5), "pad": 0.05},
1371+
"top": {"location": "top", "anchor": (0.5, 0.0),
1372+
"panchor": (0.5, 1.0), "pad": 0.05},
1373+
"bottom": {"location": "bottom", "anchor": (0.5, 1.0),
1374+
"panchor": (0.5, 0.0), "pad": 0.15},
13451375
}, location=location)
1376+
loc_settings["orientation"] = _get_orientation_from_location(location)
13461377
if orientation is not None and orientation != loc_settings["orientation"]:
13471378
# Allow the user to pass both if they are consistent.
13481379
raise TypeError("location and orientation are mutually exclusive")
13491380
return loc_settings
13501381

13511382

1383+
def _get_orientation_from_location(location):
1384+
return _api.check_getitem(
1385+
{None: None, "left": "vertical", "right": "vertical",
1386+
"top": "horizontal", "bottom": "horizontal"}, location=location)
1387+
1388+
1389+
def _get_ticklocation_from_orientation(orientation):
1390+
return _api.check_getitem(
1391+
{None: "right", "vertical": "right", "horizontal": "bottom"},
1392+
orientation=orientation)
1393+
1394+
13521395
@_docstring.interpd
13531396
def make_axes(parents, location=None, orientation=None, fraction=0.15,
13541397
shrink=1.0, aspect=20, **kwargs):

lib/matplotlib/tests/test_colorbar.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,3 +1149,34 @@ def test_title_text_loc():
11491149
# colorbar axes, including its extend triangles....
11501150
assert (cb.ax.title.get_window_extent(fig.canvas.get_renderer()).ymax >
11511151
cb.ax.spines['outline'].get_window_extent().ymax)
1152+
1153+
1154+
@check_figures_equal(extensions=["png"])
1155+
def test_passing_location(fig_ref, fig_test):
1156+
ax_ref = fig_ref.add_subplot()
1157+
im = ax_ref.imshow([[0, 1], [2, 3]])
1158+
ax_ref.figure.colorbar(im, cax=ax_ref.inset_axes([0, 1.05, 1, 0.05]),
1159+
orientation="horizontal", ticklocation="top")
1160+
ax_test = fig_test.add_subplot()
1161+
im = ax_test.imshow([[0, 1], [2, 3]])
1162+
ax_test.figure.colorbar(im, cax=ax_test.inset_axes([0, 1.05, 1, 0.05]),
1163+
location="top")
1164+
1165+
1166+
@pytest.mark.parametrize("kwargs,error,message", [
1167+
({'location': 'top', 'orientation': 'vertical'}, TypeError,
1168+
"location and orientation are mutually exclusive"),
1169+
({'location': 'top', 'orientation': 'vertical', 'cax': True}, TypeError,
1170+
"location and orientation are mutually exclusive"), # Different to above
1171+
({'ticklocation': 'top', 'orientation': 'vertical', 'cax': True},
1172+
ValueError, "'top' is not a valid value for position"),
1173+
({'location': 'top', 'extendfrac': (0, None)}, ValueError,
1174+
"invalid value for extendfrac"),
1175+
])
1176+
def test_colorbar_errors(kwargs, error, message):
1177+
fig, ax = plt.subplots()
1178+
im = ax.imshow([[0, 1], [2, 3]])
1179+
if kwargs.get('cax', None) is True:
1180+
kwargs['cax'] = ax.inset_axes([0, 1.05, 1, 0.05])
1181+
with pytest.raises(error, match=message):
1182+
fig.colorbar(im, **kwargs)

0 commit comments

Comments
 (0)