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

Skip to content

Commit e87cd5e

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

File tree

3 files changed

+119
-15
lines changed

3 files changed

+119
-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: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@
4242
of the colorbar, as that also determines the *orientation*; passing
4343
incompatible values for *location* and *orientation* raises an exception.
4444
45+
ticklocation : {'auto', 'left', 'right', 'top', 'bottom'}
46+
The location of the colorbar ticks. The *ticklocation* must match
47+
*orientation*. For example, a horizontal colorbar can only have ticks at
48+
the top or the bottom. If 'auto', the ticks will be the same as *location*,
49+
so a colorbar to the left will have ticks to the left. If *location* is
50+
None, the ticks will be at the bottom for horizontal colorbar and at the
51+
right for a vertical.
52+
4553
fraction : float, default: 0.15
4654
Fraction of original axes to use for colorbar.
4755
@@ -246,13 +254,31 @@ class Colorbar:
246254
alpha : float
247255
The colorbar transparency between 0 (transparent) and 1 (opaque).
248256
249-
orientation : {'vertical', 'horizontal'}
257+
orientation : None or {'vertical', 'horizontal'}
258+
If None, use the value determined by *location*. If both
259+
*orientation* and *location* are None then defaults to 'vertical'.
250260
251261
ticklocation : {'auto', 'left', 'right', 'top', 'bottom'}
262+
The location of the colorbar ticks. The *ticklocation* must match
263+
*orientation*. For example, a horizontal colorbar can only have ticks
264+
at the top or the bottom. If 'auto', the ticks will be the same as
265+
*location*, so a colorbar to the left will have ticks to the left. If
266+
*location* is None, the ticks will be at the bottom for horizontal
267+
colorbar and at the right for a vertical.
252268
253269
drawedges : bool
270+
Whether to draw lines at color boundaries.
254271
255272
filled : bool
273+
274+
location : None or {'left', 'right', 'top', 'bottom'}
275+
The location, relative to the parent axes, where the colorbar axes
276+
is created. It also determines the *orientation* of the colorbar
277+
(colorbars on the left and right are vertical, colorbars at the top
278+
and bottom are horizontal). If None, the location will come from the
279+
*orientation* if it is set (vertical colorbars on the right, horizontal
280+
ones at the bottom), or default to 'right' if *orientation* is unset.
281+
256282
%(_colormap_kw_doc)s
257283
"""
258284

@@ -264,7 +290,7 @@ def __init__(self, ax, mappable=None, *, cmap=None,
264290
alpha=None,
265291
values=None,
266292
boundaries=None,
267-
orientation='vertical',
293+
orientation=None,
268294
ticklocation='auto',
269295
extend=None,
270296
spacing='uniform', # uniform or proportional
@@ -275,6 +301,7 @@ def __init__(self, ax, mappable=None, *, cmap=None,
275301
extendfrac=None,
276302
extendrect=False,
277303
label='',
304+
location=None,
278305
):
279306

280307
if mappable is None:
@@ -305,14 +332,23 @@ def __init__(self, ax, mappable=None, *, cmap=None,
305332
mappable.colorbar_cid = mappable.callbacks.connect(
306333
'changed', self.update_normal)
307334

335+
location_orientation = _get_orientation_from_location(location)
336+
308337
_api.check_in_list(
309-
['vertical', 'horizontal'], orientation=orientation)
338+
[None, 'vertical', 'horizontal'], orientation=orientation)
310339
_api.check_in_list(
311340
['auto', 'left', 'right', 'top', 'bottom'],
312341
ticklocation=ticklocation)
313342
_api.check_in_list(
314343
['uniform', 'proportional'], spacing=spacing)
315344

345+
if location_orientation is not None and orientation is not None:
346+
if location_orientation != orientation:
347+
raise TypeError(
348+
"location and orientation are mutually exclusive")
349+
else:
350+
orientation = orientation or location_orientation or "vertical"
351+
316352
self.ax = ax
317353
self.ax._axes_locator = _ColorbarAxesLocator(self)
318354

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

367403
if ticklocation == 'auto':
368-
ticklocation = 'bottom' if orientation == 'horizontal' else 'right'
404+
ticklocation = _get_ticklocation_from_orientation(
405+
orientation) if location is None else location
369406
self.ticklocation = ticklocation
370407

371408
self.set_label(label)
@@ -1330,25 +1367,36 @@ def drag_pan(self, button, key, x, y):
13301367

13311368
def _normalize_location_orientation(location, orientation):
13321369
if location is None:
1333-
location = _api.check_getitem(
1334-
{None: "right", "vertical": "right", "horizontal": "bottom"},
1335-
orientation=orientation)
1370+
location = _get_ticklocation_from_orientation(orientation)
13361371
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},
1372+
"left": {"location": "left", "anchor": (1.0, 0.5),
1373+
"panchor": (0.0, 0.5), "pad": 0.10},
1374+
"right": {"location": "right", "anchor": (0.0, 0.5),
1375+
"panchor": (1.0, 0.5), "pad": 0.05},
1376+
"top": {"location": "top", "anchor": (0.5, 0.0),
1377+
"panchor": (0.5, 1.0), "pad": 0.05},
1378+
"bottom": {"location": "bottom", "anchor": (0.5, 1.0),
1379+
"panchor": (0.5, 0.0), "pad": 0.15},
13451380
}, location=location)
1381+
loc_settings["orientation"] = _get_orientation_from_location(location)
13461382
if orientation is not None and orientation != loc_settings["orientation"]:
13471383
# Allow the user to pass both if they are consistent.
13481384
raise TypeError("location and orientation are mutually exclusive")
13491385
return loc_settings
13501386

13511387

1388+
def _get_orientation_from_location(location):
1389+
return _api.check_getitem(
1390+
{None: None, "left": "vertical", "right": "vertical",
1391+
"top": "horizontal", "bottom": "horizontal"}, location=location)
1392+
1393+
1394+
def _get_ticklocation_from_orientation(orientation):
1395+
return _api.check_getitem(
1396+
{None: "right", "vertical": "right", "horizontal": "bottom"},
1397+
orientation=orientation)
1398+
1399+
13521400
@_docstring.interpd
13531401
def make_axes(parents, location=None, orientation=None, fraction=0.15,
13541402
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)