Skip to content

WIP: CL place holder #17232

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 99 additions & 2 deletions lib/matplotlib/_constrained_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import numpy as np

import matplotlib.cbook as cbook
import matplotlib.transforms as mtransforms
import matplotlib._layoutbox as layoutbox

_log = logging.getLogger(__name__)
Expand All @@ -71,7 +72,7 @@ def _axes_all_finite_sized(fig):

######################################################
def do_constrained_layout(fig, renderer, h_pad, w_pad,
hspace=None, wspace=None):
hspace=None, wspace=None, reset=True):
"""
Do the constrained_layout. Called at draw time in
``figure.constrained_layout()``
Expand Down Expand Up @@ -154,12 +155,16 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad,
# change size after the first re-position (i.e. x/yticklabels get
# larger/smaller). This second reposition tends to be much milder,
# so doing twice makes things work OK.
bboxes = {} # need these for packing the layout later...
for ax in fig.axes:
_log.debug(ax._layoutbox)
if ax._layoutbox is not None:
# make margins for each layout box based on the size of
# the decorators.
_make_layout_margins(ax, renderer, h_pad, w_pad)
bbox = _make_layout_margins(ax, renderer, h_pad, w_pad)
else:
bbox = None
bboxes[ax] = bbox

# do layout for suptitle.
suptitle = fig._suptitle
Expand Down Expand Up @@ -200,6 +205,7 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad,
_align_spines(fig, gs)

fig._layoutbox.constrained_layout_called += 1
# call the kiwi solver:
fig._layoutbox.update_variables()

# check if any axes collapsed to zero. If not, don't change positions:
Expand All @@ -222,6 +228,7 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad,
else:
cbook._warn_external('constrained_layout not applied. At least '
'one axes collapsed to zero width or height.')
_squish(fig)


def _make_ghost_gridspec_slots(fig, gs):
Expand Down Expand Up @@ -256,6 +263,8 @@ def _make_layout_margins(ax, renderer, h_pad, w_pad):
For each axes, make a margin between the *pos* layoutbox and the
*axes* layoutbox be a minimum size that can accommodate the
decorations on the axis.

Returns the bbox for some width/heigth calcs outside this loop.
"""
fig = ax.figure
invTransFig = fig.transFigure.inverted().transform_bbox
Expand Down Expand Up @@ -300,6 +309,7 @@ def _make_layout_margins(ax, renderer, h_pad, w_pad):
ax._poslayoutbox.constrain_bottom_margin(0, strength='weak')
ax._poslayoutbox.constrain_right_margin(0, strength='weak')
ax._poslayoutbox.constrain_left_margin(0, strength='weak')
return bbox


def _align_spines(fig, gs):
Expand Down Expand Up @@ -668,3 +678,90 @@ def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
strength='medium')

return lb, lbpos

def _squish(fig, w_pad=0.0, h_pad=0.0):

gss = set()
bboxes = {} # need these for packing the layout later...
invTransFig = fig.transFigure.inverted().transform_bbox

for ax in fig.axes:
print(ax, hasattr(ax, 'get_subplotspec'))
if hasattr(ax, 'get_subplotspec'):
gs = ax.get_subplotspec().get_gridspec()
gss.add(gs)
bbox = ax.get_tightbbox(fig.canvas.get_renderer())
print('before', bbox)
for cba in ax._colorbars:
print('colorbar!!!', cba)
bboxcb = cba.get_tightbbox(fig.canvas.get_renderer())
bbox = mtransforms.BboxBase.union([bbox, bboxcb])
print('after', bbox)
bbox = invTransFig(bbox)
bboxes[ax] = bbox

# we placed everything, but what if there are huge gaps...
for gs in gss:
axs = [ax for ax in fig.axes
if (hasattr(ax, 'get_subplotspec')
and ax.get_subplotspec().get_gridspec() == gs)]
nrows, ncols = gs.get_geometry()
# get widths:
dxs = np.zeros((nrows, ncols))
dys = np.zeros((nrows, ncols))
margxs = np.zeros((nrows, ncols))

for i in range(nrows):
for j in range(ncols):
for ax in axs:
ss = ax.get_subplotspec()
if (i in ss.rowspan) and (j in ss.colspan):
di = ss.colspan[-1] - ss.colspan[0] + 1
dj = ss.rowspan[-1] - ss.rowspan[0] + 1
dxs[i, j] = bboxes[ax].bounds[2] / di
if ss.colspan[-1] < ncols - 1:
dxs[i, j] = dxs[i, j] + w_pad / di
dys[i, j] = bboxes[ax].bounds[3] / dj
if ss.rowspan[-1] < nrows - 1:
dys[i, j] = dys[i, j] + h_pad / dj
orpos = ax.get_position(original=True)
margxs[i, j] = bboxes[ax].x0 - orpos.x0

dx = np.max(np.sum(dxs, axis=0))
dy = np.max(np.sum(dys, axis=1))
print('dxs', dxs)
print('margxs', margxs)
ddxs = np.max(dxs, axis=0)
dx = np.sum(ddxs)
margx = np.min(margxs, axis=0)
print(ddxs, margx, np.cumsum(ddxs))
if (dx < dy) and (dx < 0.98):
extra = (1 - dx) / 2
print('Squish X', extra)
for ax in axs:
# newpos = ax._poslayoutbox.get_rect()
ss = ax.get_subplotspec()
orpos = ax.get_position(original=True)
# acpos = ax.get_position(original=False)
# margx = bboxes[ax].x0 - orpos.x0
x = extra
for j in range(0, ss.colspan[0]):
x += ddxs[j]
# x = 0.5
print('x', x, margx[ss.colspan[0]])
orpos.x1 = x - margx[ss.colspan[0]] + orpos.bounds[2]
deltax = -orpos.x0 + x - margx[ss.colspan[0]]
orpos.x0 = x - margx[ss.colspan[0]]
# Now set the new position.
# ax.set_position will zero out the layout for
# this axis, allowing users to hard-code the position,
# so this does the same w/o zeroing layout.
ax._set_position(orpos, which='original')
# place any colorbars:
for cba in ax._colorbars:
#bbox = cba.get_tightbbox(fig.canvas.get_renderer())
#bbox = invTransFig(bbox)
pos = cba.get_position(original=True)
pos.x0 = pos.x0 + deltax
pos.x1 = pos.x1 + deltax
cba._set_position(pos, which='original')
8 changes: 6 additions & 2 deletions lib/matplotlib/_layoutbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ def constrain_width(self, width, strength='strong'):
"""
c = (self.width == width)
self.solver.addConstraint(c | strength)
return c

def constrain_width_min(self, width, strength='strong'):
c = (self.width >= width)
Expand Down Expand Up @@ -652,11 +653,13 @@ def print_tree(lb):
print_tree(lb.parent)


def plot_children(fig, box, level=0, printit=True):
def plot_children(fig, box, level=0, printit=True, stop=1000):
"""Simple plotting to show where boxes are."""
import matplotlib
import matplotlib.pyplot as plt

if stop < 0:
return
if isinstance(fig, matplotlib.figure.Figure):
ax = fig.add_axes([0., 0., 1., 1.])
ax.set_facecolor([1., 1., 1., 0.7])
Expand All @@ -665,6 +668,7 @@ def plot_children(fig, box, level=0, printit=True):
else:
ax = fig


import matplotlib.patches as patches
colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]
if printit:
Expand Down Expand Up @@ -692,4 +696,4 @@ def plot_children(fig, box, level=0, printit=True):
ha='right', va='top', size=12-level,
color=colors[level])

plot_children(ax, child, level=level+1, printit=printit)
plot_children(ax, child, level=level+1, printit=printit, stop=stop-1)
1 change: 1 addition & 0 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,7 @@ def __init__(self, fig, rect,

self._layoutbox = None
self._poslayoutbox = None
self._colorbars = [] # a list of colorbars attached to this axes.

def __getstate__(self):
# The renderer should be re-created by the figure, and then cached at
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/axes/_subplots.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def get_gridspec(self):
def update_params(self):
"""Update the subplot position from ``self.figure.subplotpars``."""
self.figbox, _, _, self.numRows, self.numCols = \
self.get_subplotspec().get_position(self.figure,
self.get_subplotspec().get_position(self.figure,
return_all=True)

@cbook.deprecated("3.2", alternative="ax.get_subplotspec().rowspan.start")
Expand Down
5 changes: 5 additions & 0 deletions lib/matplotlib/colorbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -1395,6 +1395,7 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15,
Returns (cax, kw), the child axes and the reduced kw dictionary to be
passed when creating the colorbar instance.
"""

locations = ["left", "right", "top", "bottom"]
if orientation is not None and location is not None:
raise TypeError('position and orientation are mutually exclusive. '
Expand Down Expand Up @@ -1498,6 +1499,10 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15,
ax.set_anchor(parent_anchor)

cax = fig.add_axes(pbcb, label="<colorbar>")
if len(parents) == 1:
# single colorbar on an axes...
ax._colorbars += [cax]


# OK, now make a layoutbox for the cb axis. Later, we will use this
# to make the colorbar fit nicely.
Expand Down
13 changes: 10 additions & 3 deletions lib/matplotlib/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -1719,7 +1719,14 @@ def draw(self, renderer):
try:
renderer.open_group('figure', gid=self.get_gid())
if self.get_constrained_layout() and self.axes:
self.execute_constrained_layout(renderer)
self.execute_constrained_layout(renderer, reset=True)
# yay extra draw!
self.patch.draw(renderer)
mimage._draw_list_compositing_images(
renderer, self, artists, self.suppressComposite)
self.execute_constrained_layout(renderer, reset=False)


if self.get_tight_layout() and self.axes:
try:
self.tight_layout(**self._tight_parameters)
Expand Down Expand Up @@ -2377,7 +2384,7 @@ def init_layoutbox(self):
parent=None, name='figlb', artist=self)
self._layoutbox.constrain_geometry(0., 0., 1., 1.)

def execute_constrained_layout(self, renderer=None):
def execute_constrained_layout(self, renderer=None, reset=True):
"""
Use ``layoutbox`` to determine pos positions within axes.

Expand All @@ -2403,7 +2410,7 @@ def execute_constrained_layout(self, renderer=None):
h_pad = h_pad / height
if renderer is None:
renderer = layoutbox.get_renderer(fig)
do_constrained_layout(fig, renderer, h_pad, w_pad, hspace, wspace)
do_constrained_layout(fig, renderer, h_pad, w_pad, hspace, wspace, reset=reset)

@cbook._delete_parameter("3.2", "renderer")
def tight_layout(self, renderer=None, pad=1.08, h_pad=None, w_pad=None,
Expand Down
1 change: 1 addition & 0 deletions lib/matplotlib/gridspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ def get_grid_positions(self, fig, raw=False):

fig_tops, fig_bottoms = (top - cell_hs).reshape((-1, 2)).T
fig_lefts, fig_rights = (left + cell_ws).reshape((-1, 2)).T

return fig_bottoms, fig_tops, fig_lefts, fig_rights

def __getitem__(self, key):
Expand Down