Skip to content

Commit 81b962a

Browse files
committed
Rewrite of constrained layout.
1 parent 969513e commit 81b962a

32 files changed

Lines changed: 1340 additions & 1634 deletions

‎lib/matplotlib/_constrained_layout.py‎

Lines changed: 500 additions & 566 deletions
Large diffs are not rendered by default.

‎lib/matplotlib/_layoutbox.py‎

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

‎lib/matplotlib/_layoutgrid.py‎

Lines changed: 607 additions & 0 deletions
Large diffs are not rendered by default.

‎lib/matplotlib/axes/_base.py‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,7 @@ def __init__(self, fig, rect,
493493
self.set_figure(fig)
494494
self.set_box_aspect(box_aspect)
495495
self._axes_locator = None # Optionally set via update(kwargs).
496+
self._colorbars = []
496497

497498
self.spines = self._gen_axes_spines()
498499

@@ -565,15 +566,10 @@ def __init__(self, fig, rect,
565566
rcParams['ytick.major.right']),
566567
which='major')
567568

568-
self._layoutbox = None
569-
self._poslayoutbox = None
570-
571569
def __getstate__(self):
572570
# The renderer should be re-created by the figure, and then cached at
573571
# that point.
574572
state = super().__getstate__()
575-
for key in ['_layoutbox', '_poslayoutbox']:
576-
state[key] = None
577573
# Prune the sharing & twinning info to only contain the current group.
578574
for grouper_name in [
579575
'_shared_x_axes', '_shared_y_axes', '_twinned_axes']:
@@ -907,8 +903,6 @@ def set_position(self, pos, which='both'):
907903
self._set_position(pos, which=which)
908904
# because this is being called externally to the library we
909905
# zero the constrained layout parts.
910-
self._layoutbox = None
911-
self._poslayoutbox = None
912906

913907
def _set_position(self, pos, which='both'):
914908
"""

‎lib/matplotlib/axes/_secondary_axes.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,6 @@ def __init__(self, parent, orientation, location, functions, **kwargs):
6363
self._otherstrings = ['top', 'bottom']
6464
self._parentscale = None
6565
# this gets positioned w/o constrained_layout so exclude:
66-
self._layoutbox = None
67-
self._poslayoutbox = None
6866

6967
self.set_location(location)
7068
self.set_functions(functions)

‎lib/matplotlib/axes/_subplots.py‎

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import matplotlib.artist as martist
66
from matplotlib.axes._axes import Axes
77
from matplotlib.gridspec import GridSpec, SubplotSpec
8-
import matplotlib._layoutbox as layoutbox
98

109

1110
class SubplotBase:
@@ -37,23 +36,6 @@ def __init__(self, fig, *args, **kwargs):
3736
self.update_params()
3837
# _axes_class is set in the subplot_class_factory
3938
self._axes_class.__init__(self, fig, self.figbox, **kwargs)
40-
# add a layout box to this, for both the full axis, and the poss
41-
# of the axis. We need both because the axes may become smaller
42-
# due to parasitic axes and hence no longer fill the subplotspec.
43-
if self._subplotspec._layoutbox is None:
44-
self._layoutbox = None
45-
self._poslayoutbox = None
46-
else:
47-
name = self._subplotspec._layoutbox.name + '.ax'
48-
name = name + layoutbox.seq_id()
49-
self._layoutbox = layoutbox.LayoutBox(
50-
parent=self._subplotspec._layoutbox,
51-
name=name,
52-
artist=self)
53-
self._poslayoutbox = layoutbox.LayoutBox(
54-
parent=self._layoutbox,
55-
name=self._layoutbox.name+'.pos',
56-
pos=True, subplot=True, artist=self)
5739

5840
def __reduce__(self):
5941
# get the first axes class which does not inherit from a subplotbase
@@ -156,10 +138,6 @@ def _make_twin_axes(self, *args, **kwargs):
156138
twin.set_label(real_label)
157139
self.set_adjustable('datalim')
158140
twin.set_adjustable('datalim')
159-
if self._layoutbox is not None and twin._layoutbox is not None:
160-
# make the layout boxes be explicitly the same
161-
twin._layoutbox.constrain_same(self._layoutbox)
162-
twin._poslayoutbox.constrain_same(self._poslayoutbox)
163141
self._twinned_axes.join(self, twin)
164142
return twin
165143

‎lib/matplotlib/colorbar.py‎

Lines changed: 14 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@
4848
import matplotlib.path as mpath
4949
import matplotlib.ticker as ticker
5050
import matplotlib.transforms as mtransforms
51-
import matplotlib._layoutbox as layoutbox
52-
import matplotlib._constrained_layout as constrained_layout
5351
from matplotlib import docstring
5452

5553
_log = logging.getLogger(__name__)
@@ -1433,17 +1431,7 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15,
14331431
# pass to `colorbar`.
14341432
parents = np.atleast_1d(parents).ravel()
14351433

1436-
# check if using constrained_layout:
1437-
try:
1438-
gs = parents[0].get_subplotspec().get_gridspec()
1439-
using_constrained_layout = (gs._layoutbox is not None)
1440-
except AttributeError:
1441-
using_constrained_layout = False
1442-
1443-
# defaults are not appropriate for constrained_layout:
14441434
pad0 = loc_settings['pad']
1445-
if using_constrained_layout:
1446-
pad0 = 0.02
14471435
pad = kw.pop('pad', pad0)
14481436

14491437
fig = parents[0].get_figure()
@@ -1485,31 +1473,20 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15,
14851473
ax.set_anchor(parent_anchor)
14861474

14871475
cax = fig.add_axes(pbcb, label="<colorbar>")
1488-
1489-
# OK, now make a layoutbox for the cb axis. Later, we will use this
1490-
# to make the colorbar fit nicely.
1491-
if not using_constrained_layout:
1492-
# no layout boxes:
1493-
lb = None
1494-
lbpos = None
1495-
# and we need to set the aspect ratio by hand...
1496-
cax.set_aspect(aspect, anchor=anchor, adjustable='box')
1497-
else:
1498-
if not parents_iterable:
1499-
# this is a single axis...
1500-
ax = parents[0]
1501-
lb, lbpos = constrained_layout.layoutcolorbarsingle(
1502-
ax, cax, shrink, aspect, location, pad=pad)
1503-
else: # there is more than one parent, so lets use gridspec
1504-
# the colorbar will be a sibling of this gridspec, so the
1505-
# parent is the same parent as the gridspec. Either the figure,
1506-
# or a subplotspec.
1507-
1508-
lb, lbpos = constrained_layout.layoutcolorbargridspec(
1509-
parents, cax, shrink, aspect, location, pad)
1510-
1511-
cax._layoutbox = lb
1512-
cax._poslayoutbox = lbpos
1476+
for a in parents:
1477+
# tell the parent it has a colorbar
1478+
a._colorbars += [cax]
1479+
cax._colorbar_info = {}
1480+
cax._colorbar_info['location'] = location
1481+
cax._colorbar_info['parents'] = parents
1482+
cax._colorbar_info['shrink'] = shrink
1483+
cax._colorbar_info['anchor'] = anchor
1484+
cax._colorbar_info['panchor'] = parent_anchor
1485+
cax._colorbar_info['fraction'] = fraction
1486+
cax._colorbar_info['aspect'] = aspect
1487+
cax._colorbar_info['pad'] = pad
1488+
# and we need to set the aspect ratio by hand...
1489+
cax.set_aspect(aspect, anchor=anchor, adjustable='box')
15131490

15141491
return cax, kw
15151492

@@ -1560,11 +1537,7 @@ def make_axes_gridspec(parent, *, fraction=0.15, shrink=1.0, aspect=20, **kw):
15601537
pad_s = (1 - shrink) * 0.5
15611538
wh_ratios = [pad_s, shrink, pad_s]
15621539

1563-
# we need to none the tree of layoutboxes because
1564-
# constrained_layout can't remove and replace the tree
1565-
# hierarchy w/o a seg fault.
15661540
gs = parent.get_subplotspec().get_gridspec()
1567-
layoutbox.nonetree(gs._layoutbox)
15681541
gs_from_subplotspec = gridspec.GridSpecFromSubplotSpec
15691542
if orientation == 'vertical':
15701543
pad = kw.pop('pad', 0.05)

‎lib/matplotlib/figure.py‎

Lines changed: 27 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@
2727

2828
from matplotlib.axes import Axes, SubplotBase, subplot_class_factory
2929
from matplotlib.blocking_input import BlockingMouseInput, BlockingKeyMouseInput
30-
from matplotlib.gridspec import GridSpec, SubplotSpec
30+
from matplotlib.gridspec import GridSpec
3131
import matplotlib.legend as mlegend
3232
from matplotlib.patches import Rectangle
3333
from matplotlib.text import Text
3434
from matplotlib.transforms import (Affine2D, Bbox, BboxTransformTo,
3535
TransformedBbox)
36-
import matplotlib._layoutbox as layoutbox
36+
import matplotlib._layoutgrid as layoutgrid
3737

3838
_log = logging.getLogger(__name__)
3939

@@ -344,9 +344,9 @@ def __init__(self,
344344
subplotpars = SubplotParams()
345345

346346
self.subplotpars = subplotpars
347+
347348
# constrained_layout:
348-
self._layoutbox = None
349-
# set in set_constrained_layout_pads()
349+
self._layoutgrid = None
350350
self.set_constrained_layout(constrained_layout)
351351

352352
self.set_tight_layout(tight_layout)
@@ -355,6 +355,13 @@ def __init__(self,
355355
self.clf()
356356
self._cachedRenderer = None
357357

358+
if self.get_constrained_layout():
359+
# must be done *after* clf!
360+
self.init_layoutgrid()
361+
# stub for subpanels:
362+
self.panels = []
363+
self.transPanel = self.transFigure
364+
358365
# groupers to keep track of x and y labels we want to align.
359366
# see self.align_xlabels and self.align_ylabels and
360367
# axis._get_tick_boxes_siblings
@@ -570,7 +577,7 @@ def get_constrained_layout_pads(self, relative=False):
570577
hspace = self._constrained_layout_pads['hspace']
571578

572579
if relative and (w_pad is not None or h_pad is not None):
573-
renderer0 = layoutbox.get_renderer(self)
580+
renderer0 = layoutgrid.get_renderer(self)
574581
dpi = renderer0.dpi
575582
w_pad = w_pad * dpi / renderer0.width
576583
h_pad = h_pad * dpi / renderer0.height
@@ -729,22 +736,8 @@ def suptitle(self, t, **kwargs):
729736
sup.remove()
730737
else:
731738
self._suptitle = sup
732-
self._suptitle._layoutbox = None
733-
if self._layoutbox is not None and not manual_position:
734-
w_pad, h_pad, wspace, hspace = \
735-
self.get_constrained_layout_pads(relative=True)
736-
figlb = self._layoutbox
737-
self._suptitle._layoutbox = layoutbox.LayoutBox(
738-
parent=figlb, artist=self._suptitle,
739-
name=figlb.name+'.suptitle')
740-
# stack the suptitle on top of all the children.
741-
# Some day this should be on top of all the children in the
742-
# gridspec only.
743-
for child in figlb.children:
744-
if child is not self._suptitle._layoutbox:
745-
layoutbox.vstack([self._suptitle._layoutbox,
746-
child],
747-
padding=h_pad*2., strength='required')
739+
if manual_position:
740+
self._suptitle.set_in_layout(False)
748741
self.stale = True
749742
return self._suptitle
750743

@@ -1405,6 +1398,7 @@ def add_subplot(self, *args, **kwargs):
14051398

14061399
def _add_axes_internal(self, key, ax):
14071400
"""Private helper for `add_axes` and `add_subplot`."""
1401+
#self._localaxes += [ax]
14081402
self._axstack.add(key, ax)
14091403
self.sca(ax)
14101404
ax._remove_method = self.delaxes
@@ -1570,6 +1564,7 @@ def _break_share_link(ax, grouper):
15701564
return None
15711565

15721566
self._axstack.remove(ax)
1567+
# self._localaxes.remove(ax)
15731568
self._axobservers.process("_axes_change_event", self)
15741569
self.stale = True
15751570

@@ -1609,7 +1604,7 @@ def clf(self, keep_observers=False):
16091604
self._axobservers = cbook.CallbackRegistry()
16101605
self._suptitle = None
16111606
if self.get_constrained_layout():
1612-
layoutbox.nonetree(self._layoutbox)
1607+
layoutgrid.nonetree(self._layoutgrid)
16131608
self.stale = True
16141609

16151610
def clear(self, keep_observers=False):
@@ -1919,12 +1914,9 @@ def __getstate__(self):
19191914
in _pylab_helpers.Gcf.figs.values():
19201915
state['_restore_to_pylab'] = True
19211916

1922-
# set all the layoutbox information to None. kiwisolver objects can't
1917+
# set all the layoutgrid information to None. kiwisolver objects can't
19231918
# be pickled, so we lose the layout options at this point.
1924-
state.pop('_layoutbox', None)
1925-
# suptitle:
1926-
if self._suptitle is not None:
1927-
self._suptitle._layoutbox = None
1919+
state.pop('_layoutgrid', None)
19281920

19291921
return state
19301922

@@ -1941,7 +1933,7 @@ def __setstate__(self, state):
19411933

19421934
# re-initialise some of the unstored state information
19431935
FigureCanvasBase(self) # Set self.canvas.
1944-
self._layoutbox = None
1936+
self._layoutgrid = None
19451937

19461938
if restore_to_pylab:
19471939
# lazy import to avoid circularity
@@ -2323,24 +2315,22 @@ def get_tightbbox(self, renderer, bbox_extra_artists=None):
23232315

23242316
return bbox_inches
23252317

2326-
def init_layoutbox(self):
2327-
"""Initialize the layoutbox for use in constrained_layout."""
2328-
if self._layoutbox is None:
2329-
self._layoutbox = layoutbox.LayoutBox(
2330-
parent=None, name='figlb', artist=self)
2331-
self._layoutbox.constrain_geometry(0., 0., 1., 1.)
2318+
def init_layoutgrid(self):
2319+
"""Initialize the layoutgrid for use in constrained_layout."""
2320+
self._layoutgrid = layoutgrid.LayoutGrid(
2321+
parent=None, name='figlb')
23322322

23332323
def execute_constrained_layout(self, renderer=None):
23342324
"""
2335-
Use ``layoutbox`` to determine pos positions within axes.
2325+
Use ``layoutgrid`` to determine pos positions within axes.
23362326
23372327
See also `.set_constrained_layout_pads`.
23382328
"""
23392329

23402330
from matplotlib._constrained_layout import do_constrained_layout
23412331

23422332
_log.debug('Executing constrainedlayout')
2343-
if self._layoutbox is None:
2333+
if self._layoutgrid is None:
23442334
cbook._warn_external("Calling figure.constrained_layout, but "
23452335
"figure not setup to do constrained layout. "
23462336
" You either called GridSpec without the "
@@ -2355,7 +2345,7 @@ def execute_constrained_layout(self, renderer=None):
23552345
w_pad = w_pad / width
23562346
h_pad = h_pad / height
23572347
if renderer is None:
2358-
renderer = layoutbox.get_renderer(fig)
2348+
renderer = layoutgrid.get_renderer(fig)
23592349
do_constrained_layout(fig, renderer, h_pad, w_pad, hspace, wspace)
23602350

23612351
@cbook._delete_parameter("3.2", "renderer")

0 commit comments

Comments
 (0)