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

Skip to content

Commit f4baa7d

Browse files
ImportanceOfBeingErnestMeeseeksDev[bot]
authored and
MeeseeksDev[bot]
committed
Backport PR #11648: FIX: colorbar placement in constrained layout
1 parent 316e173 commit f4baa7d

File tree

7 files changed

+202
-56
lines changed

7 files changed

+202
-56
lines changed

.flake8

Lines 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
Lines 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

Lines 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

Lines 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. '

lib/matplotlib/tests/test_constrainedlayout.py

Lines 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

Lines 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)