Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Allow automatic use of tight_layout. #774

Merged
merged 2 commits into from

6 participants

@efiring
Owner

In common interactive usage, stretching a figure across the screen
leaves too much wasted space; Figure.tight_layout() can solve this
problem at least for simple plots. This changeset uses a previously
existing but unused rcParam and a Figure kwarg to allow the user
to have tight_layout executed automatically with every Figure.draw().

There may be all sorts of gotcha's with this, but at least for much routine work I think something along these lines will be a real help. The lack of fit between the contents of a figure and the figure boundary has been a long-time complaint of many users. Being able to manually fix it figure by figure by calling tight_layout is better than nothing, but I am hoping that tight_layout is now good enough to warrant letting it be used automatically when desired.

I recycled an inactive rcParams entry left over from 2008, figure.autolayout, but used the shorter "tight" as the name of the Figure kwarg and property. It would probably make sense to change the rcParams entry to match the kwarg name.

@efiring
Owner

This is definitely not ready for use as-is; I see that it does something half-way reasonable with a colorbar only if the colorbar has been created with use_gridspec=True.

@leejjoon
Owner

One thing we may do is to prevent tight_layout if the figure contains any Axes that are not instances of Subplot.

lib/matplotlib/figure.py
@@ -307,6 +314,16 @@ def _set_dpi(self, dpi):
self.callbacks.process('dpi_changed', self)
dpi = property(_get_dpi, _set_dpi)
+ def _get_tight(self):
+ return self._tight
+ def _set_tight(self, tight):
+ if tight is None:
+ tight = rcParams['figure.autolayout']
+ tight = bool(tight)
+ self._tight = tight
+ tight = property(_get_tight, _set_tight,
@jdh2358 Owner
jdh2358 added a note

I understand a foolish consistency is the hobgoblin of little minds, but shouldn't we stick with the normal setters and getters rather than introducing properties, so this will be exposed to the whole artist set/get introspection? It seems like it would add confusion to have some properties sets as plain-ol-attributes-or-properties, and some as setters/getters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@efiring
Owner

This is now ready for reconsideration; I followed the request of @jdh2358 , and the suggestion of @leejjoon . I also made the colorbar default be use_gridspec=True.

The remaining question include (1) should we have something like this at all? and (2) if so, instead of recycling the existing rcParam name figure.autolayout, should it be figure.tight_layout to match the function and kwarg? Or are there any other changes needed?

@WeatherGod
Collaborator

Here is another PR that might be just about ready for inclusion before the freeze.

@WeatherGod WeatherGod commented on the diff
lib/matplotlib/figure.py
@@ -835,6 +860,13 @@ def draw(self, renderer):
if not self.get_visible(): return
renderer.open_group('figure')
+ if self.get_tight_layout() and self.axes:
@WeatherGod Collaborator

What exactly is being tested wrt self.axes? Being None? Being of length greater than zero. I know PEP8 encourages this sort of semantics, but it is a pain in the butt to me to understand the intention.

@efiring Owner
efiring added a note

self.axes is used all over the place in Figure; it is a read-only list of axes. It is being tested for emptiness. I could enclose it in len(), but it would not add anything.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@WeatherGod
Collaborator

I think I am fine with autolayout being recycled for this. Since this does change a couple of defaults though (specifically, use_gridspec in the colorbar() method is changed from false to true), this is going to need to be noted somewhere. pyplot.py will need to be regenerated, and an entry added to users/whats_new.rst for this feature.

@efiring
Owner

@pelson: are you going to be able to support use_gridspec in your improved colorbar positioning scheme, #956?

@pwuertz
Collaborator

I like it! Embedding the layout functionality in the figure so it is called on demand is actually the right thing to do.

There is a usability glitch in the current design. When calling tight_layout, it grabs a renderer from the active backend for determining the font metrics. Most probably this is the renderer from an interactive backend like GtkAgg or QtAgg. If the user now chooses to save the figure to a file the font metrics might change. The layout should be based on the renderer of the target canvas only, which means that tight_layout must be called within draw when it is clear which renderer is actually used.

If autolayout is enabled/disabled via rc parameters, shouldn't the padding values go into rc and Figure as well?

@efiring
Owner

@pwuertz, I don't understand what you are suggesting in the middle paragraph of your comment above. tight_layout is called in the Figure.draw() method with whatever renderer is in use at the time. When savefig is called, tight_layout gets called with the non-interactive renderer being used. Is your point that what you see on the screen might not match what is printed? That is true even without tight_layout. Or is your question about manually calling the Figure.tight_layout() method without supplying a renderer? In other words, are suggesting a change to this pull request, apart from adding padding values to rc?

efiring added some commits
@efiring efiring Allow automatic use of tight_layout.
In common interactive usage, stretching a figure across the screen
leaves too much wasted space; Figure.tight_layout() can solve this
problem at least for simple plots.  This changeset uses a previously
existing but unused rcParam and a Figure kwarg to allow the user
to have tight_layout executed automatically with every Figure.draw().
329085e
@efiring efiring figure: improved auto tight_layout support
1) Use getter and setter instead of property
2) in tight_layout, don't proceed if an Axes is not a SubplotBase
3) in colorbar, make use_gridspec default to True.
ea0630a
@efiring
Owner

Rebased against master to take into account the refactoring from commit 5154615.

@pwuertz
Collaborator

@efiring Ah sorry, with "current design" I meant the way we have been using tight_layout up to this point. The way you embedded it into draw() is perfect because now it will always use the right renderer. So what I was trying to say is that this doesn't just add more convenience, this is actually the right way of using tight layout ^^

@pelson
Collaborator

I see no reason not to merge this issue. +1 from me.

@efiring
Owner

In spite of being the author, I am a bit worried about it for two reasons:

1) To be half-way reasonable, it really requires the change to colorbar defaults that I have included, use_gridspec=True. This conflicts with @pelson's #956, which makes more fundamental colorbar improvements, but does not support gridspec.

2) tight_layout is a stopgap in the absence of a real layout manager. If there is a realistic prospect of getting such a manager in, say, a 6-month time frame, then it might be better not to make tight_layout more prominent.

A counterpoint to point 2 is that the use of the autolayout rc param could simply be switched to apply to the geometry manager if/when it becomes available, so code using it would still work reasonably.

@pelson
Collaborator

I can't imagine a geometry manager implementation that doesn't break a whole host of things and my feeling is that it would be a version 2.0 magnitude change.

Many people are aware of tight layout, so I see no reason to not making it configurable via the rc params. I do take your point about changing the default behaviour though. Is there a reason why it is not enough to simply be able to enable the feature, rather than make it the default?

@efiring
Owner

There may be some confusion here. The pull request does not make tight_layout the default, it makes it possible for one to make it default via an rcParam. The only change in default behavior is that colorbar defaults to use_gridspec=True. The reason for this is that if tight_layout is not used, using gridspec for the colorbar makes no difference; but if tight_layout is used, and a colorbar is included, and that colorbar does not use gridspec, the result is horrible--always. I don't see any point in making it easy to enable tight_layout by default if one then has to remember to override the obscure and recent use_gridspec kwarg for colorbar.

As far as I can see, the only problem with the change in colorbar default to use_gridspec=True is that it conflicts with your #956 in its present form.

One option would be to merge this tight_layout changeset now, and wait on #956 until it supports gridspec. I don't know how hard adding that support will be; so far, I have avoided looking closely at gridspec and axesgrid. My guess is that it will not actually be very difficult--but then that is easy for me to say, as I am not volunteering to do it.

@pelson
Collaborator

Agreed. Merging.

@pelson pelson merged commit 62fe17f into matplotlib:master
@pwuertz
Collaborator

Please consider making auto-tight-layout configurable
#1136

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 17, 2012
  1. @efiring

    Allow automatic use of tight_layout.

    efiring authored
    In common interactive usage, stretching a figure across the screen
    leaves too much wasted space; Figure.tight_layout() can solve this
    problem at least for simple plots.  This changeset uses a previously
    existing but unused rcParam and a Figure kwarg to allow the user
    to have tight_layout executed automatically with every Figure.draw().
  2. @efiring

    figure: improved auto tight_layout support

    efiring authored
    1) Use getter and setter instead of property
    2) in tight_layout, don't proceed if an Axes is not a SubplotBase
    3) in colorbar, make use_gridspec default to True.
This page is out of date. Refresh to see the latest.
View
42 lib/matplotlib/figure.py
@@ -240,7 +240,7 @@ class Figure(Artist):
For multiple figure images, the figure will make composite
images depending on the renderer option_image_nocomposite
function. If suppressComposite is True|False, this will
- override the renderer
+ override the renderer.
"""
def __str__(self):
@@ -254,6 +254,7 @@ def __init__(self,
linewidth = 0.0, # the default linewidth of the frame
frameon = True, # whether or not to draw the figure frame
subplotpars = None, # default to rc
+ tight_layout = None, # default to rc figure.autolayout
):
"""
*figsize*
@@ -276,6 +277,11 @@ def __init__(self,
*subplotpars*
A :class:`SubplotParams` instance, defaults to rc
+
+ *tight_layout*
+ If *False* use *subplotpars*; if *True* adjust subplot
+ parameters using :meth:`tight_layout`. Defaults to
+ rc ``figure.autolayout``.
"""
Artist.__init__(self)
@@ -311,6 +317,7 @@ def __init__(self,
subplotpars = SubplotParams()
self.subplotpars = subplotpars
+ self.set_tight_layout(tight_layout)
self._axstack = AxesStack() # track all figure axes and current axes
self.clf()
@@ -329,6 +336,24 @@ def _set_dpi(self, dpi):
self.callbacks.process('dpi_changed', self)
dpi = property(_get_dpi, _set_dpi)
+ def get_tight_layout(self):
+ """
+ Return the Boolean flag, True to use :meth`tight_layout` when drawing.
+ """
+ return self._tight
+
+ def set_tight_layout(self, tight):
+ """
+ Set whether :meth:`tight_layout` is used upon drawing.
+ If None, the rcParams['figure.autolayout'] value will be set.
+
+ ACCEPTS: [True | False | None]
+ """
+ if tight is None:
+ tight = rcParams['figure.autolayout']
+ tight = bool(tight)
+ self._tight = tight
+
def autofmt_xdate(self, bottom=0.2, rotation=30, ha='right'):
"""
Date ticklabels often overlap, so it is useful to rotate them
@@ -863,6 +888,13 @@ def draw(self, renderer):
if not self.get_visible(): return
renderer.open_group('figure')
+ if self.get_tight_layout() and self.axes:
@WeatherGod Collaborator

What exactly is being tested wrt self.axes? Being None? Being of length greater than zero. I know PEP8 encourages this sort of semantics, but it is a pain in the butt to me to understand the intention.

@efiring Owner
efiring added a note

self.axes is used all over the place in Figure; it is a read-only list of axes. It is being tested for emptiness. I could enclose it in len(), but it would not add anything.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ try:
+ self.tight_layout(renderer)
+ except ValueError:
+ pass
+ # ValueError can occur when resizing a window.
+
if self.frameon: self.patch.draw(renderer)
# a list of (zorder, func_to_call, list_of_args)
@@ -1238,7 +1270,7 @@ def colorbar(self, mappable, cax=None, ax=None, **kw):
"""
if ax is None:
ax = self.gca()
- use_gridspec = kw.pop("use_gridspec", False)
+ use_gridspec = kw.pop("use_gridspec", True)
if cax is None:
if use_gridspec and isinstance(ax, SubplotBase):
cax, kw = cbar.make_axes_gridspec(ax, **kw)
@@ -1381,6 +1413,12 @@ def tight_layout(self, renderer=None, pad=1.08, h_pad=None, w_pad=None, rect=Non
from tight_layout import get_renderer, get_tight_layout_figure
+ no_go = [ax for ax in self.axes if not isinstance(ax, SubplotBase)]
+ if no_go:
+ warnings.Warn("Cannot use tight_layout;"
+ " all Axes must descend from SubplotBase")
+ return
+
if renderer is None:
renderer = get_renderer(self)
View
2  lib/matplotlib/rcsetup.py
@@ -544,7 +544,7 @@ def __call__(self, s):
'figure.dpi' : [ 80, validate_float], # DPI
'figure.facecolor' : [ '0.75', validate_color], # facecolor; scalar gray
'figure.edgecolor' : [ 'w', validate_color], # edgecolor; white
- 'figure.autolayout' : [ False, validate_autolayout],
+ 'figure.autolayout' : [ False, validate_bool],
'figure.subplot.left' : [0.125, ValidateInterval(0, 1, closedmin=True, closedmax=True)],
'figure.subplot.right' : [0.9, ValidateInterval(0, 1, closedmin=True, closedmax=True)],
View
2  matplotlibrc.template
@@ -301,6 +301,8 @@ text.hinting_factor : 8 # Specifies the amount of softness for hinting in the
#figure.dpi : 80 # figure dots per inch
#figure.facecolor : 0.75 # figure facecolor; 0.75 is scalar gray
#figure.edgecolor : white # figure edgecolor
+#figure.autolayout : False # When True, automatically adjust subplot
+ # parameters to make the plot fit the figure
# The figure subplot parameters. All dimensions are a fraction of the
# figure width or height
Something went wrong with that request. Please try again.