Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f4baa7d

Browse filesBrowse files
ImportanceOfBeingErnestMeeseeksDev[bot]
authored andcommitted
Backport PR #11648: FIX: colorbar placement in constrained layout
1 parent 316e173 commit f4baa7d
Copy full SHA for f4baa7d

File tree

Expand file treeCollapse file tree

7 files changed

+202
-56
lines changed
Filter options
Expand file treeCollapse file tree

7 files changed

+202
-56
lines changed

‎.flake8

Copy file name to clipboardExpand all lines: .flake8
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ per-file-ignores =
9595
tutorials/colors/colormaps.py: E501
9696
tutorials/colors/colors.py: E402
9797
tutorials/intermediate/artists.py: E402, E501
98-
tutorials/intermediate/constrainedlayout_guide.py: E402, E501
98+
tutorials/intermediate/constrainedlayout_guide.py: E402
9999
tutorials/intermediate/gridspec.py: E402, E501
100100
tutorials/intermediate/legend_guide.py: E402, E501
101101
tutorials/intermediate/tight_layout_guide.py: E402, E501
+54Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""
2+
=================
3+
Placing Colorbars
4+
=================
5+
6+
Colorbars indicate the quantitative extent of image data. Placing in
7+
a figure is non-trivial because room needs to be made for them.
8+
9+
The simplest case is just attaching a colorbar to each axes:
10+
"""
11+
import matplotlib.pyplot as plt
12+
import numpy as np
13+
14+
fig, axs = plt.subplots(2, 2)
15+
cm = ['RdBu_r', 'viridis']
16+
for col in range(2):
17+
for row in range(2):
18+
ax = axs[row, col]
19+
pcm = ax.pcolormesh(np.random.random((20, 20)) * (col + 1),
20+
cmap=cm[col])
21+
fig.colorbar(pcm, ax=ax)
22+
plt.show()
23+
24+
######################################################################
25+
# The first column has the same type of data in both rows, so it may
26+
# be desirable to combine the colorbar which we do by calling
27+
# `.Figure.colorbar` with a list of axes instead of a single axes.
28+
29+
fig, axs = plt.subplots(2, 2)
30+
cm = ['RdBu_r', 'viridis']
31+
for col in range(2):
32+
for row in range(2):
33+
ax = axs[row, col]
34+
pcm = ax.pcolormesh(np.random.random((20, 20)) * (col + 1),
35+
cmap=cm[col])
36+
fig.colorbar(pcm, ax=axs[:, col], shrink=0.6)
37+
plt.show()
38+
39+
######################################################################
40+
# Relatively complicated colorbar layouts are possible using this
41+
# paradigm. Note that this example works far better with
42+
# ``constrained_layout=True``
43+
44+
fig, axs = plt.subplots(3, 3, constrained_layout=True)
45+
for ax in axs.flat:
46+
pcm = ax.pcolormesh(np.random.random((20, 20)))
47+
48+
fig.colorbar(pcm, ax=axs[0, :2], shrink=0.6, location='bottom')
49+
fig.colorbar(pcm, ax=[axs[0, 2]], location='bottom')
50+
fig.colorbar(pcm, ax=axs[1:, :], location='right', shrink=0.6)
51+
fig.colorbar(pcm, ax=[axs[2, 1]], location='left')
52+
53+
54+
plt.show()

‎lib/matplotlib/_constrained_layout.py

Copy file name to clipboardExpand all lines: lib/matplotlib/_constrained_layout.py
+93-49Lines changed: 93 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,36 @@ def layoutcolorbarsingle(ax, cax, shrink, aspect, location, pad=0.05):
572572
return lb, lbpos
573573

574574

575+
def _getmaxminrowcolumn(axs):
576+
# helper to get the min/max rows and columns of a list of axes.
577+
maxrow = -100000
578+
minrow = 1000000
579+
maxax = None
580+
minax = None
581+
maxcol = -100000
582+
mincol = 1000000
583+
maxax_col = None
584+
minax_col = None
585+
586+
for ax in axs:
587+
subspec = ax.get_subplotspec()
588+
nrows, ncols, row_start, row_stop, col_start, col_stop = \
589+
subspec.get_rows_columns()
590+
if row_stop > maxrow:
591+
maxrow = row_stop
592+
maxax = ax
593+
if row_start < minrow:
594+
minrow = row_start
595+
minax = ax
596+
if col_stop > maxcol:
597+
maxcol = col_stop
598+
maxax_col = ax
599+
if col_start < mincol:
600+
mincol = col_start
601+
minax_col = ax
602+
return (minrow, maxrow, minax, maxax, mincol, maxcol, minax_col, maxax_col)
603+
604+
575605
def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
576606
"""
577607
Do the layout for a colorbar, to not oeverly pollute colorbar.py
@@ -586,6 +616,10 @@ def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
586616
lb = layoutbox.LayoutBox(parent=gslb.parent,
587617
name=gslb.parent.name + '.cbar',
588618
artist=cax)
619+
# figure out the row and column extent of the parents.
620+
(minrow, maxrow, minax_row, maxax_row,
621+
mincol, maxcol, minax_col, maxax_col) = _getmaxminrowcolumn(parents)
622+
589623
if location in ('left', 'right'):
590624
lbpos = layoutbox.LayoutBox(
591625
parent=lb,
@@ -594,39 +628,43 @@ def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
594628
pos=True,
595629
subplot=False,
596630
artist=cax)
597-
598-
if location == 'right':
599-
# arrange to right of the gridpec sibbling
600-
layoutbox.hstack([gslb, lb], padding=pad * gslb.width,
601-
strength='strong')
602-
else:
603-
layoutbox.hstack([lb, gslb], padding=pad * gslb.width)
631+
for ax in parents:
632+
if location == 'right':
633+
order = [ax._layoutbox, lb]
634+
else:
635+
order = [lb, ax._layoutbox]
636+
layoutbox.hstack(order, padding=pad * gslb.width,
637+
strength='strong')
604638
# constrain the height and center...
605639
# This isn't quite right. We'd like the colorbar
606640
# pos to line up w/ the axes poss, not the size of the
607641
# gs.
608-
maxrow = -100000
609-
minrow = 1000000
610-
maxax = None
611-
minax = None
612642

613-
for ax in parents:
614-
subspec = ax.get_subplotspec()
615-
nrows, ncols = subspec.get_gridspec().get_geometry()
616-
for num in [subspec.num1, subspec.num2]:
617-
rownum1, colnum1 = divmod(subspec.num1, ncols)
618-
if rownum1 > maxrow:
619-
maxrow = rownum1
620-
maxax = ax
621-
if rownum1 < minrow:
622-
minrow = rownum1
623-
minax = ax
624-
# invert the order so these are bottom to top:
625-
maxposlb = minax._poslayoutbox
626-
minposlb = maxax._poslayoutbox
643+
# Horizontal Layout: need to check all the axes in this gridspec
644+
for ch in gslb.children:
645+
subspec = ch.artist
646+
nrows, ncols, row_start, row_stop, col_start, col_stop = \
647+
subspec.get_rows_columns()
648+
if location == 'right':
649+
if col_stop <= maxcol:
650+
order = [subspec._layoutbox, lb]
651+
# arrange to right of the parents
652+
if col_start > maxcol:
653+
order = [lb, subspec._layoutbox]
654+
elif location == 'left':
655+
if col_start >= mincol:
656+
order = [lb, subspec._layoutbox]
657+
if col_stop < mincol:
658+
order = [subspec._layoutbox, lb]
659+
layoutbox.hstack(order, padding=pad * gslb.width,
660+
strength='strong')
661+
662+
# Vertical layout:
663+
maxposlb = minax_row._poslayoutbox
664+
minposlb = maxax_row._poslayoutbox
627665
# now we want the height of the colorbar pos to be
628-
# set by the top and bottom of these poss
629-
# bottom top
666+
# set by the top and bottom of the min/max axes...
667+
# bottom top
630668
# b t
631669
# h = (top-bottom)*shrink
632670
# b = bottom + (top-bottom - h) / 2.
@@ -650,29 +688,35 @@ def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
650688
subplot=False,
651689
artist=cax)
652690

653-
if location == 'bottom':
654-
layoutbox.vstack([gslb, lb], padding=pad * gslb.width)
655-
else:
656-
layoutbox.vstack([lb, gslb], padding=pad * gslb.width)
657-
658-
maxcol = -100000
659-
mincol = 1000000
660-
maxax = None
661-
minax = None
662-
663691
for ax in parents:
664-
subspec = ax.get_subplotspec()
665-
nrows, ncols = subspec.get_gridspec().get_geometry()
666-
for num in [subspec.num1, subspec.num2]:
667-
rownum1, colnum1 = divmod(subspec.num1, ncols)
668-
if colnum1 > maxcol:
669-
maxcol = colnum1
670-
maxax = ax
671-
if rownum1 < mincol:
672-
mincol = colnum1
673-
minax = ax
674-
maxposlb = maxax._poslayoutbox
675-
minposlb = minax._poslayoutbox
692+
if location == 'bottom':
693+
order = [ax._layoutbox, lb]
694+
else:
695+
order = [lb, ax._layoutbox]
696+
layoutbox.vstack(order, padding=pad * gslb.width,
697+
strength='strong')
698+
699+
# Vertical Layout: need to check all the axes in this gridspec
700+
for ch in gslb.children:
701+
subspec = ch.artist
702+
nrows, ncols, row_start, row_stop, col_start, col_stop = \
703+
subspec.get_rows_columns()
704+
if location == 'bottom':
705+
if row_stop <= minrow:
706+
order = [subspec._layoutbox, lb]
707+
if row_start > maxrow:
708+
order = [lb, subspec._layoutbox]
709+
elif location == 'top':
710+
if row_stop < minrow:
711+
order = [subspec._layoutbox, lb]
712+
if row_start >= maxrow:
713+
order = [lb, subspec._layoutbox]
714+
layoutbox.vstack(order, padding=pad * gslb.width,
715+
strength='strong')
716+
717+
# Do horizontal layout...
718+
maxposlb = maxax_col._poslayoutbox
719+
minposlb = minax_col._poslayoutbox
676720
lbpos.constrain_width((maxposlb.right - minposlb.left) *
677721
shrink)
678722
lbpos.constrain_left(

‎lib/matplotlib/colorbar.py

Copy file name to clipboardExpand all lines: lib/matplotlib/colorbar.py
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,6 +1243,7 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15,
12431243
Returns (cax, kw), the child axes and the reduced kw dictionary to be
12441244
passed when creating the colorbar instance.
12451245
'''
1246+
12461247
locations = ["left", "right", "top", "bottom"]
12471248
if orientation is not None and location is not None:
12481249
raise TypeError('position and orientation are mutually exclusive. '
Loading

‎lib/matplotlib/tests/test_constrainedlayout.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_constrainedlayout.py
+21Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,3 +378,24 @@ def test_constrained_layout23():
378378
for i in range(2):
379379
fig, ax = plt.subplots(num="123", constrained_layout=True, clear=True)
380380
fig.suptitle("Suptitle{}".format(i))
381+
382+
383+
@image_comparison(baseline_images=['test_colorbar_location'],
384+
extensions=['png'], remove_text=True, style='mpl20')
385+
def test_colorbar_location():
386+
"""
387+
Test that colorbar handling is as expected for various complicated
388+
cases...
389+
"""
390+
391+
fig, axs = plt.subplots(4, 5, constrained_layout=True)
392+
for ax in axs.flatten():
393+
pcm = example_pcolor(ax)
394+
ax.set_xlabel('')
395+
ax.set_ylabel('')
396+
fig.colorbar(pcm, ax=axs[:, 1], shrink=0.4)
397+
fig.colorbar(pcm, ax=axs[-1, :2], shrink=0.5, location='bottom')
398+
fig.colorbar(pcm, ax=axs[0, 2:], shrink=0.5, location='bottom')
399+
fig.colorbar(pcm, ax=axs[-2, 3:], shrink=0.5, location='top')
400+
fig.colorbar(pcm, ax=axs[0, 0], shrink=0.5, location='left')
401+
fig.colorbar(pcm, ax=axs[1:3, 2], shrink=0.5, location='right')

‎tutorials/intermediate/constrainedlayout_guide.py

Copy file name to clipboardExpand all lines: tutorials/intermediate/constrainedlayout_guide.py
+32-6Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,10 @@ def example_plot(ax, fontsize=12, nodec=False):
118118
#
119119
# .. note::
120120
#
121-
# For the `~.axes.Axes.pcolormesh` kwargs (``pc_kwargs``) we use a dictionary.
122-
# Below we will assign one colorbar to a number of axes each containing
123-
# a `~.cm.ScalarMappable`; specifying the norm and colormap ensures
124-
# the colorbar is accurate for all the axes.
121+
# For the `~.axes.Axes.pcolormesh` kwargs (``pc_kwargs``) we use a
122+
# dictionary. Below we will assign one colorbar to a number of axes each
123+
# containing a `~.cm.ScalarMappable`; specifying the norm and colormap
124+
# ensures the colorbar is accurate for all the axes.
125125

126126
arr = np.arange(100).reshape((10, 10))
127127
norm = mcolors.Normalize(vmin=0., vmax=100.)
@@ -133,14 +133,25 @@ def example_plot(ax, fontsize=12, nodec=False):
133133

134134
############################################################################
135135
# If you specify a list of axes (or other iterable container) to the
136-
# ``ax`` argument of ``colorbar``, constrained_layout will take space from all
137-
# axes that share the same gridspec.
136+
# ``ax`` argument of ``colorbar``, constrained_layout will take space from
137+
# the specified axes.
138138

139139
fig, axs = plt.subplots(2, 2, figsize=(4, 4), constrained_layout=True)
140140
for ax in axs.flatten():
141141
im = ax.pcolormesh(arr, **pc_kwargs)
142142
fig.colorbar(im, ax=axs, shrink=0.6)
143143

144+
############################################################################
145+
# If you specify a list of axes from inside a grid of axes, the colorbar
146+
# will steal space appropriately, and leave a gap, but all subplots will
147+
# still be the same size.
148+
149+
fig, axs = plt.subplots(3, 3, figsize=(4, 4), constrained_layout=True)
150+
for ax in axs.flatten():
151+
im = ax.pcolormesh(arr, **pc_kwargs)
152+
fig.colorbar(im, ax=axs[1:, ][:, 1], shrink=0.8)
153+
fig.colorbar(im, ax=axs[:, -1], shrink=0.6)
154+
144155
############################################################################
145156
# Note that there is a bit of a subtlety when specifying a single axes
146157
# as the parent. In the following, it might be desirable and expected
@@ -458,6 +469,21 @@ def docomplicated(suptitle=None):
458469
ax2 = fig.add_axes(bb_ax2)
459470

460471
###############################################################################
472+
# Manually turning off ``constrained_layout``
473+
# ===========================================
474+
#
475+
# ``constrained_layout`` usually adjusts the axes positions on each draw
476+
# of the figure. If you want to get the spacing provided by
477+
# ``constrained_layout`` but not have it update, then do the initial
478+
# draw and then call ``fig.set_constrained_layout(False)``.
479+
# This is potentially useful for animations where the tick labels may
480+
# change length.
481+
#
482+
# Note that ``constrained_layout`` is turned off for ``ZOOM`` and ``PAN``
483+
# GUI events for the backends that use the toolbar. This prevents the
484+
# axes from changing position during zooming and panning.
485+
#
486+
#
461487
# Limitations
462488
# ========================
463489
#

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.