Skip to content

Commit 9537906

Browse files
authored
Merge pull request matplotlib#22627 from jklymak/enh-rect-for-CL
ENH: rect for constrained_layout
2 parents a768fd3 + 21263a5 commit 9537906

File tree

6 files changed

+55
-60
lines changed

6 files changed

+55
-60
lines changed

lib/matplotlib/_constrained_layout.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262

6363
######################################################
6464
def do_constrained_layout(fig, h_pad, w_pad,
65-
hspace=None, wspace=None):
65+
hspace=None, wspace=None, rect=(0, 0, 1, 1)):
6666
"""
6767
Do the constrained_layout. Called at draw time in
6868
``figure.constrained_layout()``
@@ -85,14 +85,18 @@ def do_constrained_layout(fig, h_pad, w_pad,
8585
of 0.1 of the figure width between each column.
8686
If h/wspace < h/w_pad, then the pads are used instead.
8787
88+
rect : tuple of 4 floats
89+
Rectangle in figure coordinates to perform constrained layout in
90+
[left, bottom, width, height], each from 0-1.
91+
8892
Returns
8993
-------
9094
layoutgrid : private debugging structure
9195
"""
9296

9397
renderer = get_renderer(fig)
9498
# make layoutgrid tree...
95-
layoutgrids = make_layoutgrids(fig, None)
99+
layoutgrids = make_layoutgrids(fig, None, rect=rect)
96100
if not layoutgrids['hasgrids']:
97101
_api.warn_external('There are no gridspecs with layoutgrids. '
98102
'Possibly did not call parent GridSpec with the'
@@ -131,7 +135,7 @@ def do_constrained_layout(fig, h_pad, w_pad,
131135
return layoutgrids
132136

133137

134-
def make_layoutgrids(fig, layoutgrids):
138+
def make_layoutgrids(fig, layoutgrids, rect=(0, 0, 1, 1)):
135139
"""
136140
Make the layoutgrid tree.
137141
@@ -145,8 +149,9 @@ def make_layoutgrids(fig, layoutgrids):
145149
layoutgrids = dict()
146150
layoutgrids['hasgrids'] = False
147151
if not hasattr(fig, '_parent'):
148-
# top figure
149-
layoutgrids[fig] = mlayoutgrid.LayoutGrid(parent=None, name='figlb')
152+
# top figure; pass rect as parent to allow user-specified
153+
# margins
154+
layoutgrids[fig] = mlayoutgrid.LayoutGrid(parent=rect, name='figlb')
150155
else:
151156
# subfigure
152157
gs = fig._subplotspec.get_gridspec()

lib/matplotlib/_layoutgrid.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def __init__(self, parent=None, parent_pos=(0, 0),
3939
self.parent_pos = parent_pos
4040
self.parent_inner = parent_inner
4141
self.name = name + seq_id()
42-
if parent is not None:
42+
if isinstance(parent, LayoutGrid):
4343
self.name = f'{parent.name}.{self.name}'
4444
self.nrows = nrows
4545
self.ncols = ncols
@@ -51,8 +51,10 @@ def __init__(self, parent=None, parent_pos=(0, 0),
5151
self.width_ratios = np.ones(ncols)
5252

5353
sn = self.name + '_'
54-
if parent is None:
55-
self.parent = None
54+
if not isinstance(parent, LayoutGrid):
55+
# parent can be a rect if not a LayoutGrid
56+
# allows specifying a rectangle to contain the layout.
57+
self.parent = parent
5658
self.solver = kiwi.Solver()
5759
else:
5860
self.parent = parent
@@ -178,12 +180,13 @@ def parent_constraints(self):
178180
# parent's left, the last column right equal to the
179181
# parent's right...
180182
parent = self.parent
181-
if parent is None:
182-
hc = [self.lefts[0] == 0,
183-
self.rights[-1] == 1,
183+
if not isinstance(parent, LayoutGrid):
184+
# specify a rectangle in figure coordinates
185+
hc = [self.lefts[0] == parent[0],
186+
self.rights[-1] == parent[0] + parent[2],
184187
# top and bottom reversed order...
185-
self.tops[0] == 1,
186-
self.bottoms[-1] == 0]
188+
self.tops[0] == parent[1] + parent[3],
189+
self.bottoms[-1] == parent[1]]
187190
else:
188191
rows, cols = self.parent_pos
189192
rows = np.atleast_1d(rows)

lib/matplotlib/layout_engine.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def __init__(self, *, pad=1.08, h_pad=None, w_pad=None,
123123
h_pad, w_pad : float
124124
Padding (height/width) between edges of adjacent subplots.
125125
Defaults to *pad*.
126-
rect : tuple[float, float, float, float], optional
126+
rect : tuple of 4 floats, optional
127127
(left, bottom, right, top) rectangle in normalized figure
128128
coordinates that the subplots (including labels)
129129
will fit into. Defaults to using the entire figure.
@@ -179,7 +179,8 @@ class ConstrainedLayoutEngine(LayoutEngine):
179179
_colorbar_gridspec = False
180180

181181
def __init__(self, *, h_pad=None, w_pad=None,
182-
hspace=None, wspace=None, **kwargs):
182+
hspace=None, wspace=None, rect=(0, 0, 1, 1),
183+
**kwargs):
183184
"""
184185
Initialize ``constrained_layout`` settings.
185186
@@ -197,15 +198,20 @@ def __init__(self, *, h_pad=None, w_pad=None,
197198
If h/wspace < h/w_pad, then the pads are used instead.
198199
Default to :rc:`figure.constrained_layout.hspace` and
199200
:rc:`figure.constrained_layout.wspace`.
201+
rect : tuple of 4 floats
202+
Rectangle in figure coordinates to perform constrained layout in
203+
(left, bottom, width, height), each from 0-1.
200204
"""
201205
super().__init__(**kwargs)
202206
# set the defaults:
203207
self.set(w_pad=mpl.rcParams['figure.constrained_layout.w_pad'],
204208
h_pad=mpl.rcParams['figure.constrained_layout.h_pad'],
205209
wspace=mpl.rcParams['figure.constrained_layout.wspace'],
206-
hspace=mpl.rcParams['figure.constrained_layout.hspace'])
210+
hspace=mpl.rcParams['figure.constrained_layout.hspace'],
211+
rect=(0, 0, 1, 1))
207212
# set anything that was passed in (None will be ignored):
208-
self.set(w_pad=w_pad, h_pad=h_pad, wspace=wspace, hspace=hspace)
213+
self.set(w_pad=w_pad, h_pad=h_pad, wspace=wspace, hspace=hspace,
214+
rect=rect)
209215

210216
def execute(self, fig):
211217
"""
@@ -222,10 +228,11 @@ def execute(self, fig):
222228

223229
return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
224230
wspace=self._params['wspace'],
225-
hspace=self._params['hspace'])
231+
hspace=self._params['hspace'],
232+
rect=self._params['rect'])
226233

227234
def set(self, *, h_pad=None, w_pad=None,
228-
hspace=None, wspace=None):
235+
hspace=None, wspace=None, rect=None):
229236
"""
230237
Set the pads for constrained_layout.
231238
@@ -243,6 +250,9 @@ def set(self, *, h_pad=None, w_pad=None,
243250
If h/wspace < h/w_pad, then the pads are used instead.
244251
Default to :rc:`figure.constrained_layout.hspace` and
245252
:rc:`figure.constrained_layout.wspace`.
253+
rect : tuple of 4 floats
254+
Rectangle in figure coordinates to perform constrained layout in
255+
(left, bottom, width, height), each from 0-1.
246256
"""
247257
for td in self.set.__kwdefaults__:
248258
if locals()[td] is not None:

lib/matplotlib/tests/test_constrainedlayout.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,3 +606,21 @@ def test_discouraged_api():
606606
def test_kwargs():
607607
fig, ax = plt.subplots(constrained_layout={'h_pad': 0.02})
608608
fig.draw_without_rendering()
609+
610+
611+
def test_rect():
612+
fig, ax = plt.subplots(layout='constrained')
613+
fig.get_layout_engine().set(rect=[0, 0, 0.5, 0.5])
614+
fig.draw_without_rendering()
615+
ppos = ax.get_position()
616+
assert ppos.x1 < 0.5
617+
assert ppos.y1 < 0.5
618+
619+
fig, ax = plt.subplots(layout='constrained')
620+
fig.get_layout_engine().set(rect=[0.2, 0.2, 0.3, 0.3])
621+
fig.draw_without_rendering()
622+
ppos = ax.get_position()
623+
assert ppos.x1 < 0.5
624+
assert ppos.y1 < 0.5
625+
assert ppos.x0 > 0.2
626+
assert ppos.y0 > 0.2

pytest.ini

Lines changed: 0 additions & 7 deletions
This file was deleted.

tests.py

Lines changed: 0 additions & 34 deletions
This file was deleted.

0 commit comments

Comments
 (0)