This is my implementation of the geometry manager (See #1109).
This is heavily based on exploratory work by @Tillsten using the
kiwi solver, and of course on
Latest what's new: https://5396-1385122-gh.circle-artifacts.com/0/home/circleci/project/doc/build/html/users/next_whats_new/constrained_layout.html
Latest tutorial: https://5398-1385122-gh.circle-artifacts.com/0/home/circleci/project/doc/build/html/tutorials/intermediate/constrainedlayout_guide.html#sphx-glr-tutorials-intermediate-constrainedlayout-guide-py
tight_layout() is great for what it does, but it doesn't handle some cases generally. i.e. if we have two
GridSpecFromSubplotSpec instances, they need to be positioned manually using
tight_layout(). Colorbars for individual axes work fine, but colorbars for more than one axis doesn't work. Etc.
This PR implements "constrained_layout" via
plt.figure(constrained_layout=True) for automatic re-drawing. Currently, it works for subplots and colorbars, legends, and suptitles. Nested gridspecs are respected. Spacing is provided both as a pad around axes and as a space between subplots.
The following code gives this plot:
fig = plt.figure(constrained_layout=True)
gs = gridspec.GridSpec(1, 2, fig=fig)
gsl = gridspec.GridSpecFromSubplotSpec(2, 2, gs)
gsr = gridspec.GridSpecFromSubplotSpec(1, 2, gs)
axsl = 
for gs in gsl:
ax = fig.add_subplot(gs)
axsl += [ax]
axsr = 
for gs in gsr:
ax = fig.add_subplot(gs)
axsr += [ax]
pcm = example_pcolor(ax, fontsize=12)
fig.colorbar(pcm, ax = axsr, pad=0.01,
A few things to note in the above:
- all the subplots in the left side set of plots (
gsl) have the same margins. They are a bit big because the bottom-right plot has a two-line x-label.
- the x-ticklabels for the rhs plot overrun, because
constrained_layout doesn't do any management at the axis level.
In addition to the normal
matplotlib installation, you will need to involve kiwisolver:
pip install kiwisolver now works.
API dfferences (so far)
In order to get this to work, there are a couple of API changes:
gridspec.GridSpec now has a
fig keyword. If this isn't supplied then
constrained_layout won't work.
figure now has a
constrained_layout keyword. Currently, I don't have it checking for
tight_layout, so they could both run at the same time. These upper level user-facing decisions probaby require input.
- Internal calling issue: We don't want axes that have user-called
ax.set_position() to participate in
constrained_layout, presumably because the user was purposefully putting the axis where they put it. So, that means internally, we set the
layoutbox properties of this axis to
ax.set_position gets called internally (i.e. by
aspect) and for those we presumably want to keep constrained_layout as a possibility. So I made a
ax._set_position() for internal calls, which the public
ax.set_position() uses. Internal calls to
set_position should use
_set_position if the axis is to continue to participate in constrained_layout.
I think most of these changes are backwards compatible, in that if you don't want to use
constrained_layout you don't need to change any calls. Backwards compatibility introduces some awkwardness, but is probably worth it.
Implementation is via a new package
layoutbox.py. Basically a nested array of layoutboxe objects are set up, attached to the figure, the gridspec(s), the subplotspecs. An axes has two layoutboxes: one that contains the tight bounding box of the axes, and the second that contains the "position" of the axes (i.e the rectangle that is passed to
A linear constraint solver is used to decide dimensions. The trick here is that we define margins between an axes bounding box and its position. These margins, and the outer dimesnion of the plot (0,0,1,1) are what constrains the problem externally. Internally, the problem is constrained by relationships between the layoutboxes.
These are in
Release critical topic: geometry manager
- [x] Sort out padding in a non-figure unit
- [x] Add
legend to constrained objects
- [x] Add kiwi to requirements: need a new release to make OK w/ 2.7. Current requirement works 3.6
- [x] Has Pytest style unit tests.
- [x] Code is PEP 8 compliant
- [x] New features are documented, with examples if plot related
- [x] Documentation is sphinx and numpydoc compliant
- [x] Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
- [x] Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way
- [x] Add
suptitle to constrained objects
- [x] Adjust spacing for
- [x] Implement respect for
hspace variables. Actually pretty easy.
- [x] Check
- [x] Spacing between gridspecs
- [x] gridspec width_ratios and height_ratios
- [x] Extend to putting labels outside tick labels and titles outside of all?