Skip to content

Commit f6eb58a

Browse files
committed
ENH: constrained_layout simple compress axes
1 parent cd33ed2 commit f6eb58a

File tree

3 files changed

+64
-9
lines changed

3 files changed

+64
-9
lines changed

lib/matplotlib/_constrained_layout.py

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161

6262
######################################################
6363
def do_constrained_layout(fig, renderer, h_pad, w_pad,
64-
hspace=None, wspace=None):
64+
hspace=None, wspace=None, compress=False):
6565
"""
6666
Do the constrained_layout. Called at draw time in
6767
``figure.constrained_layout()``
@@ -83,6 +83,11 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad,
8383
A value of 0.2 for a three-column layout would have a space
8484
of 0.1 of the figure width between each column.
8585
If h/wspace < h/w_pad, then the pads are used instead.
86+
87+
compress : boolean, False
88+
Whether to try and push axes together if their aspect ratios
89+
make it so that the they will have lots of extra white space
90+
between them. Useful for grids of images or maps.
8691
"""
8792

8893
# list of unique gridspecs that contain child axes:
@@ -98,7 +103,7 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad,
98103
'Possibly did not call parent GridSpec with the'
99104
' "figure" keyword')
100105

101-
for _ in range(2):
106+
for nn in range(2):
102107
# do the algorithm twice. This has to be done because decorations
103108
# change size after the first re-position (i.e. x/yticklabels get
104109
# larger/smaller). This second reposition tends to be much milder,
@@ -118,16 +123,56 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad,
118123
# update all the variables in the layout.
119124
fig._layoutgrid.update_variables()
120125

126+
warn_collapsed = ('constrained_layout not applied because '
127+
'axes sizes collapsed to zero. Try making '
128+
'figure larger or axes decorations smaller.')
121129
if _check_no_collapsed_axes(fig):
122130
_reposition_axes(fig, renderer, h_pad=h_pad, w_pad=w_pad,
123131
hspace=hspace, wspace=wspace)
132+
if compress:
133+
_compress_fixed_aspect(fig)
134+
135+
# update all the variables in the layout.
136+
fig._layoutgrid.update_variables()
137+
if _check_no_collapsed_axes(fig):
138+
_reposition_axes(fig, renderer, h_pad=h_pad, w_pad=w_pad,
139+
hspace=hspace, wspace=wspace)
140+
else:
141+
_api.warn_external(warn_collapsed)
124142
else:
125-
_api.warn_external('constrained_layout not applied because '
126-
'axes sizes collapsed to zero. Try making '
127-
'figure larger or axes decorations smaller.')
143+
_api.warn_external(warn_collapsed)
128144
_reset_margins(fig)
129145

130146

147+
def _compress_fixed_aspect(fig):
148+
extraw = dict()
149+
extrah = dict()
150+
for ax in fig.axes:
151+
if hasattr(ax, 'get_subplotspec'):
152+
actual = ax.get_position(original=False)
153+
ax.apply_aspect()
154+
sub = ax.get_subplotspec()
155+
gs = sub.get_gridspec()
156+
if gs not in extraw.keys():
157+
extraw[gs] = np.zeros(gs.ncols)
158+
extrah[gs] = np.zeros(gs.nrows)
159+
orig = ax.get_position(original=True)
160+
actual = ax.get_position(original=False)
161+
dw = orig.width - actual.width
162+
if dw > 0:
163+
for i in sub.colspan:
164+
extraw[gs][i] = max(extraw[gs][i], dw)
165+
dh = orig.height - actual.height
166+
if dh > 0:
167+
for i in sub.rowspan:
168+
extrah[gs][i] = max(extrah[gs][i], dh)
169+
170+
fig._layoutgrid.edit_margin_min('left', np.sum(extraw[gs]) / 2)
171+
fig._layoutgrid.edit_margin_min('right', np.sum(extraw[gs]) / 2)
172+
173+
fig._layoutgrid.edit_margin_min('top', np.sum(extrah[gs]) / 2)
174+
fig._layoutgrid.edit_margin_min('bottom', np.sum(extrah[gs]) / 2)
175+
131176
def _check_no_collapsed_axes(fig):
132177
"""
133178
Check that no axes have collapsed to zero size.

lib/matplotlib/_layoutgrid.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,9 @@ def __repr__(self):
120120
f'innerW{self.inner_widths[j].value():1.3f}, ' \
121121
f'innerH{self.inner_heights[i].value():1.3f}, ' \
122122
f'ML{self.margins["left"][j].value():1.3f}, ' \
123-
f'MR{self.margins["right"][j].value():1.3f}, \n'
123+
f'MR{self.margins["right"][j].value():1.3f}, ' \
124+
f'MB{self.margins["bottom"][j].value():1.3f}, ' \
125+
f'MT{self.margins["top"][j].value():1.3f}, \n'
124126
return str
125127

126128
def reset_margins(self):

lib/matplotlib/figure.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2449,6 +2449,10 @@ def set_constrained_layout_pads(self, **kwargs):
24492449
Height padding between subplots, expressed as a fraction of the
24502450
subplot width. The total padding ends up being h_pad + hspace.
24512451
2452+
compress : boolean
2453+
Try to compress axes in constrained layout. Useful when
2454+
axes aspect ratios make it so that there is substantial
2455+
white space between them.
24522456
"""
24532457

24542458
todo = ['w_pad', 'h_pad', 'wspace', 'hspace']
@@ -2458,6 +2462,8 @@ def set_constrained_layout_pads(self, **kwargs):
24582462
else:
24592463
self._constrained_layout_pads[td] = (
24602464
mpl.rcParams['figure.constrained_layout.' + td])
2465+
self._constrained_layout_pads['compress'] = (
2466+
kwargs.get('compress', False))
24612467

24622468
def get_constrained_layout_pads(self, relative=False):
24632469
"""
@@ -2477,14 +2483,15 @@ def get_constrained_layout_pads(self, relative=False):
24772483
h_pad = self._constrained_layout_pads['h_pad']
24782484
wspace = self._constrained_layout_pads['wspace']
24792485
hspace = self._constrained_layout_pads['hspace']
2486+
compress = self._constrained_layout_pads['compress']
24802487

24812488
if relative and (w_pad is not None or h_pad is not None):
24822489
renderer0 = layoutgrid.get_renderer(self)
24832490
dpi = renderer0.dpi
24842491
w_pad = w_pad * dpi / renderer0.width
24852492
h_pad = h_pad * dpi / renderer0.height
24862493

2487-
return w_pad, h_pad, wspace, hspace
2494+
return w_pad, h_pad, wspace, hspace, compress
24882495

24892496
def set_canvas(self, canvas):
24902497
"""
@@ -3073,15 +3080,16 @@ def execute_constrained_layout(self, renderer=None):
30733080
"or you need to call figure or subplots "
30743081
"with the constrained_layout=True kwarg.")
30753082
return
3076-
w_pad, h_pad, wspace, hspace = self.get_constrained_layout_pads()
3083+
w_pad, h_pad, wspace, hspace, comp = self.get_constrained_layout_pads()
30773084
# convert to unit-relative lengths
30783085
fig = self
30793086
width, height = fig.get_size_inches()
30803087
w_pad = w_pad / width
30813088
h_pad = h_pad / height
30823089
if renderer is None:
30833090
renderer = _get_renderer(fig)
3084-
do_constrained_layout(fig, renderer, h_pad, w_pad, hspace, wspace)
3091+
do_constrained_layout(fig, renderer, h_pad, w_pad, hspace, wspace,
3092+
compress=comp)
30853093

30863094
def tight_layout(self, *, pad=1.08, h_pad=None, w_pad=None, rect=None):
30873095
"""

0 commit comments

Comments
 (0)