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

Skip to content

Commit 50168a7

Browse files
Merge pull request #11648 from jklymak/fix-colorbars-constrained-layout
FIX: colorbar placement in constrained layout
2 parents f268e3a + f4438fb commit 50168a7

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
@@ -75,7 +75,7 @@ per-file-ignores =
7575
tutorials/colors/colors.py: E402
7676
tutorials/colors/colormap-manipulation.py: E402
7777
tutorials/intermediate/artists.py: E402, E501
78-
tutorials/intermediate/constrainedlayout_guide.py: E402, E501
78+
tutorials/intermediate/constrainedlayout_guide.py: E402
7979
tutorials/intermediate/gridspec.py: E402, E501
8080
tutorials/intermediate/legend_guide.py: E402, E501
8181
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
@@ -568,6 +568,36 @@ def layoutcolorbarsingle(ax, cax, shrink, aspect, location, pad=0.05):
568568
return lb, lbpos
569569

570570

571+
def _getmaxminrowcolumn(axs):
572+
# helper to get the min/max rows and columns of a list of axes.
573+
maxrow = -100000
574+
minrow = 1000000
575+
maxax = None
576+
minax = None
577+
maxcol = -100000
578+
mincol = 1000000
579+
maxax_col = None
580+
minax_col = None
581+
582+
for ax in axs:
583+
subspec = ax.get_subplotspec()
584+
nrows, ncols, row_start, row_stop, col_start, col_stop = \
585+
subspec.get_rows_columns()
586+
if row_stop > maxrow:
587+
maxrow = row_stop
588+
maxax = ax
589+
if row_start < minrow:
590+
minrow = row_start
591+
minax = ax
592+
if col_stop > maxcol:
593+
maxcol = col_stop
594+
maxax_col = ax
595+
if col_start < mincol:
596+
mincol = col_start
597+
minax_col = ax
598+
return (minrow, maxrow, minax, maxax, mincol, maxcol, minax_col, maxax_col)
599+
600+
571601
def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
572602
"""
573603
Do the layout for a colorbar, to not oeverly pollute colorbar.py
@@ -582,6 +612,10 @@ def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
582612
lb = layoutbox.LayoutBox(parent=gslb.parent,
583613
name=gslb.parent.name + '.cbar',
584614
artist=cax)
615+
# figure out the row and column extent of the parents.
616+
(minrow, maxrow, minax_row, maxax_row,
617+
mincol, maxcol, minax_col, maxax_col) = _getmaxminrowcolumn(parents)
618+
585619
if location in ('left', 'right'):
586620
lbpos = layoutbox.LayoutBox(
587621
parent=lb,
@@ -590,39 +624,43 @@ def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
590624
pos=True,
591625
subplot=False,
592626
artist=cax)
593-
594-
if location == 'right':
595-
# arrange to right of the gridpec sibbling
596-
layoutbox.hstack([gslb, lb], padding=pad * gslb.width,
597-
strength='strong')
598-
else:
599-
layoutbox.hstack([lb, gslb], padding=pad * gslb.width)
627+
for ax in parents:
628+
if location == 'right':
629+
order = [ax._layoutbox, lb]
630+
else:
631+
order = [lb, ax._layoutbox]
632+
layoutbox.hstack(order, padding=pad * gslb.width,
633+
strength='strong')
600634
# constrain the height and center...
601635
# This isn't quite right. We'd like the colorbar
602636
# pos to line up w/ the axes poss, not the size of the
603637
# gs.
604-
maxrow = -100000
605-
minrow = 1000000
606-
maxax = None
607-
minax = None
608638

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

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

lib/matplotlib/colorbar.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,6 +1242,7 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15,
12421242
Returns (cax, kw), the child axes and the reduced kw dictionary to be
12431243
passed when creating the colorbar instance.
12441244
'''
1245+
12451246
locations = ["left", "right", "top", "bottom"]
12461247
if orientation is not None and location is not None:
12471248
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
@@ -113,10 +113,10 @@ def example_plot(ax, fontsize=12, nodec=False):
113113
#
114114
# .. note::
115115
#
116-
# For the `~.axes.Axes.pcolormesh` kwargs (``pc_kwargs``) we use a dictionary.
117-
# Below we will assign one colorbar to a number of axes each containing
118-
# a `~.cm.ScalarMappable`; specifying the norm and colormap ensures
119-
# the colorbar is accurate for all the axes.
116+
# For the `~.axes.Axes.pcolormesh` kwargs (``pc_kwargs``) we use a
117+
# dictionary. Below we will assign one colorbar to a number of axes each
118+
# containing a `~.cm.ScalarMappable`; specifying the norm and colormap
119+
# ensures the colorbar is accurate for all the axes.
120120

121121
arr = np.arange(100).reshape((10, 10))
122122
norm = mcolors.Normalize(vmin=0., vmax=100.)
@@ -128,14 +128,25 @@ def example_plot(ax, fontsize=12, nodec=False):
128128

129129
############################################################################
130130
# If you specify a list of axes (or other iterable container) to the
131-
# ``ax`` argument of ``colorbar``, constrained_layout will take space from all
132-
# axes that share the same gridspec.
131+
# ``ax`` argument of ``colorbar``, constrained_layout will take space from
132+
# the specified axes.
133133

134134
fig, axs = plt.subplots(2, 2, figsize=(4, 4), constrained_layout=True)
135135
for ax in axs.flatten():
136136
im = ax.pcolormesh(arr, **pc_kwargs)
137137
fig.colorbar(im, ax=axs, shrink=0.6)
138138

139+
############################################################################
140+
# If you specify a list of axes from inside a grid of axes, the colorbar
141+
# will steal space appropriately, and leave a gap, but all subplots will
142+
# still be the same size.
143+
144+
fig, axs = plt.subplots(3, 3, figsize=(4, 4), constrained_layout=True)
145+
for ax in axs.flatten():
146+
im = ax.pcolormesh(arr, **pc_kwargs)
147+
fig.colorbar(im, ax=axs[1:, ][:, 1], shrink=0.8)
148+
fig.colorbar(im, ax=axs[:, -1], shrink=0.6)
149+
139150
############################################################################
140151
# Note that there is a bit of a subtlety when specifying a single axes
141152
# as the parent. In the following, it might be desirable and expected
@@ -453,6 +464,21 @@ def docomplicated(suptitle=None):
453464
ax2 = fig.add_axes(bb_ax2)
454465

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

0 commit comments

Comments
 (0)