Skip to content

Commit 21263a5

Browse files
committed
ENH: add rect parameter to constrained_layout
1 parent a436253 commit 21263a5

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
@@ -64,7 +64,7 @@
6464

6565
######################################################
6666
def do_constrained_layout(fig, h_pad, w_pad,
67-
hspace=None, wspace=None):
67+
hspace=None, wspace=None, rect=(0, 0, 1, 1)):
6868
"""
6969
Do the constrained_layout. Called at draw time in
7070
``figure.constrained_layout()``
@@ -87,14 +87,18 @@ def do_constrained_layout(fig, h_pad, w_pad,
8787
of 0.1 of the figure width between each column.
8888
If h/wspace < h/w_pad, then the pads are used instead.
8989
90+
rect : tuple of 4 floats
91+
Rectangle in figure coordinates to perform constrained layout in
92+
[left, bottom, width, height], each from 0-1.
93+
9094
Returns
9195
-------
9296
layoutgrid : private debugging structure
9397
"""
9498

9599
renderer = get_renderer(fig)
96100
# make layoutgrid tree...
97-
layoutgrids = make_layoutgrids(fig, None)
101+
layoutgrids = make_layoutgrids(fig, None, rect=rect)
98102
if not layoutgrids['hasgrids']:
99103
_api.warn_external('There are no gridspecs with layoutgrids. '
100104
'Possibly did not call parent GridSpec with the'
@@ -133,7 +137,7 @@ def do_constrained_layout(fig, h_pad, w_pad,
133137
return layoutgrids
134138

135139

136-
def make_layoutgrids(fig, layoutgrids):
140+
def make_layoutgrids(fig, layoutgrids, rect=(0, 0, 1, 1)):
137141
"""
138142
Make the layoutgrid tree.
139143
@@ -147,8 +151,9 @@ def make_layoutgrids(fig, layoutgrids):
147151
layoutgrids = dict()
148152
layoutgrids['hasgrids'] = False
149153
if not hasattr(fig, '_parent'):
150-
# top figure
151-
layoutgrids[fig] = mlayoutgrid.LayoutGrid(parent=None, name='figlb')
154+
# top figure; pass rect as parent to allow user-specified
155+
# margins
156+
layoutgrids[fig] = mlayoutgrid.LayoutGrid(parent=rect, name='figlb')
152157
else:
153158
# subfigure
154159
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
@@ -608,3 +608,21 @@ def test_discouraged_api():
608608
def test_kwargs():
609609
fig, ax = plt.subplots(constrained_layout={'h_pad': 0.02})
610610
fig.draw_without_rendering()
611+
612+
613+
def test_rect():
614+
fig, ax = plt.subplots(layout='constrained')
615+
fig.get_layout_engine().set(rect=[0, 0, 0.5, 0.5])
616+
fig.draw_without_rendering()
617+
ppos = ax.get_position()
618+
assert ppos.x1 < 0.5
619+
assert ppos.y1 < 0.5
620+
621+
fig, ax = plt.subplots(layout='constrained')
622+
fig.get_layout_engine().set(rect=[0.2, 0.2, 0.3, 0.3])
623+
fig.draw_without_rendering()
624+
ppos = ax.get_position()
625+
assert ppos.x1 < 0.5
626+
assert ppos.y1 < 0.5
627+
assert ppos.x0 > 0.2
628+
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)