tight subplot parameters

Unused whitespace is a pet-peeve of mine, so I tend to use bbox_inches='tight' when saving figures. However, when producing publications, I want figures with a very specific size (i.e., fit column width of page), but calling bbox_inches='tight' changes the figure size. Stretching to fit is out of the question (screws up the font sizes).

Anyway, I coded up a way to automatically choose values for subplots_adjust. My main goal was to tighten the borders (top, bottom, left, right). Nevertheless, I ended up coding up a solution to automatically adjust 'hspace' and 'wspace'.

Just curious if there's any interest in adding this functionality.

Cheers,
-Tony

Code Notes:

tight_layout.py (5.88 KB)

···

-------------
* The code to tighten the subplot spacing only works for regular grids: not with subplots that span multiple columns/rows.
* The code to tighten up the borders is short and fairly intuitive, but the code for subplot spacing is pretty messy (mainly because wspace/hspace depends on the border spacing and the number of rows/columns).
* The code draws the figure twice to calculate subplot parameters (not sure if this is a big issue, but I thought it was worth mentioning).
* Just execute the file to plot some examples with random label sizes to demonstrate subplot adjustment.

Not sure if I am missing something obvious, but on my system (ubuntu
linux, mpl svn HEAD, backend GTKAgg) the figures look wrong. I am
attaching the results from one run for fig5 for comparison, but almost
all of the figs appear unusable. I modified your script slightly to
use the np random number generator and seeded it so we can (hopefully)
get comparable results on our systems w/o the confusions of different
random choices. Revised code and example fig attached.

tight_layout.py (5.99 KB)

tight5.png

···

On Wed, Jul 21, 2010 at 7:16 PM, Tony S Yu <tsyu80@...149...> wrote:

Unused whitespace is a pet-peeve of mine, so I tend to use bbox_inches='tight' when saving figures. However, when producing publications, I want figures with a very specific size (i.e., fit column width of page), but calling bbox_inches='tight' changes the figure size. Stretching to fit is out of the question (screws up the font sizes).

Anyway, I coded up a way to automatically choose values for subplots_adjust. My main goal was to tighten the borders (top, bottom, left, right). Nevertheless, I ended up coding up a solution to automatically adjust 'hspace' and 'wspace'.

Just curious if there's any interest in adding this functionality.
Code Notes:
-------------
* The code to tighten the subplot spacing only works for regular grids: not with subplots that span multiple columns/rows.
* The code to tighten up the borders is short and fairly intuitive, but the code for subplot spacing is pretty messy (mainly because wspace/hspace depends on the border spacing and the number of rows/columns).
* The code draws the figure twice to calculate subplot parameters (not sure if this is a big issue, but I thought it was worth mentioning).
* Just execute the file to plot some examples with random label sizes to demonstrate subplot adjustment.

Anyway, I coded up a way to automatically choose values for subplots_adjust. My main goal was to tighten the borders (top, bottom, left, right). Nevertheless, I ended up coding up a solution to automatically adjust 'hspace' and 'wspace'.

[Snip]

Not sure if I am missing something obvious, but on my system (ubuntu
linux, mpl svn HEAD, backend GTKAgg) the figures look wrong. I am
attaching the results from one run for fig5 for comparison, but almost
all of the figs appear unusable. I modified your script slightly to
use the np random number generator and seeded it so we can (hopefully)
get comparable results on our systems w/o the confusions of different
random choices. Revised code and example fig attached.
<tight_layout.py><tight5.png>

Wow, I don't see that at all. I'm on OS X, mpl svn HEAD (r8567), and Qt4Agg. I don't have GTK installed, so unfortunately, I can't really do a proper comparison. Here's the output I get from your modified script. I get something similar with TkAgg (unfortunately, I get an AttributeError with the macosx backend).

tight5.png

···

On Jul 21, 2010, at 10:44 PM, John Hunter wrote:

On Wed, Jul 21, 2010 at 7:16 PM, Tony S Yu <tsyu80@...149...> wrote:

Works on my system for tkagg and qtagg4, so the bug appears gtkagg
specific. Must be something in the draw vs window realized and sized
pipeline. It appears you are getting some bogus size info. Wonder
if connecting to the draw_event might help here (longshot) or if any
any of Eric's recent work on show is impacting th behavior here.

···

On Wed, Jul 21, 2010 at 10:09 PM, Tony S Yu <tsyu80@...149...> wrote:

Wow, I don't see that at all. I'm on OS X, mpl svn HEAD (r8567), and Qt4Agg. I don't have GTK installed, so unfortunately, I can't really do a proper comparison. Here's the output I get from your modified script. I get something similar with TkAgg (unfortunately, I get an AttributeError with the macosx backend).

Well, since I don't have access to GTK, it's not really something I can debug (kind of a cop out, I know).

Your original GTK image is really strange: it seems as though the script moves the ticks, axis labels, and titles relative to their associated axes-frame. But the only function I use to affect the plot spacing is Figure.subplots_adjust (everything else in the script serves to calculate appropriate spacing-values). I don't think Figure.subplots_adjust is designed to move the ticks/axis-labels/titles relative to its own axes,.... right?

-Tony

···

On Jul 21, 2010, at 11:26 PM, John Hunter wrote:

On Wed, Jul 21, 2010 at 10:09 PM, Tony S Yu <tsyu80@...149...> wrote:

Wow, I don't see that at all. I'm on OS X, mpl svn HEAD (r8567), and Qt4Agg. I don't have GTK installed, so unfortunately, I can't really do a proper comparison. Here's the output I get from your modified script. I get something similar with TkAgg (unfortunately, I get an AttributeError with the macosx backend).

Works on my system for tkagg and qtagg4, so the bug appears gtkagg
specific. Must be something in the draw vs window realized and sized
pipeline. It appears you are getting some bogus size info. Wonder
if connecting to the draw_event might help here (longshot) or if any
any of Eric's recent work on show is impacting th behavior here.

No. I modified the script to eliminate the subplots() calls so that I could run it on 0.99.3 (the first 2 figures only), and it still doesn't work on gtkagg, but does work on qt4agg.

Eric

···

On 07/21/2010 05:26 PM, John Hunter wrote:

On Wed, Jul 21, 2010 at 10:09 PM, Tony S Yu<tsyu80@...149...> wrote:

Wow, I don't see that at all. I'm on OS X, mpl svn HEAD (r8567), and Qt4Agg. I don't have GTK installed, so unfortunately, I can't really do a proper comparison. Here's the output I get from your modified script. I get something similar with TkAgg (unfortunately, I get an AttributeError with the macosx backend).

Works on my system for tkagg and qtagg4, so the bug appears gtkagg
specific. Must be something in the draw vs window realized and sized
pipeline. It appears you are getting some bogus size info. Wonder
if connecting to the draw_event might help here (longshot) or if any
any of Eric's recent work on show is impacting th behavior here.

Wow, I don't see that at all. I'm on OS X, mpl svn HEAD (r8567), and Qt4Agg. I don't have GTK installed, so unfortunately, I can't really do a proper comparison. Here's the output I get from your modified script. I get something similar with TkAgg (unfortunately, I get an AttributeError with the macosx backend).

Works on my system for tkagg and qtagg4, so the bug appears gtkagg
specific. Must be something in the draw vs window realized and sized
pipeline. It appears you are getting some bogus size info. Wonder
if connecting to the draw_event might help here (longshot) or if any
any of Eric's recent work on show is impacting th behavior here.

It is not anything I changed, but it is related to when drawing occurs. The following change makes it work with gtk, and keep working with qt4agg:

efiring@...340...:~/temp$ diff -u tight_layout.py tight_layout_gtk.py
--- tight_layout.py 2010-07-21 20:28:16.000000000 -1000
+++ tight_layout_gtk.py 2010-07-21 20:29:05.000000000 -1000
@@ -131,6 +131,7 @@
  if __name__ == '__main__':
      import numpy as np
      np.random.seed(1234)
+ plt.ion()
      fontsizes = [8, 16, 24, 32]
      def example_plot(ax):
          ax.plot([1, 2])
@@ -164,5 +165,6 @@
          for ax in row:
              example_plot(ax)
      tight_layout()
+ plt.ioff()
      fig.savefig('tight5')
      plt.show()

I have not tracked down the reason why gtk is behaving differently; drawing with gtkagg is a bit convoluted. It appears that gtk is more efficient in delaying drawing, and in drawing only once at the end, while the others are less clever, and actually draw when the canvas.draw() method is executed.

Eric

···

On 07/21/2010 05:26 PM, John Hunter wrote:

On Wed, Jul 21, 2010 at 10:09 PM, Tony S Yu<tsyu80@...149...> wrote:

------------------------------------------------------------------------------
This SF.net email is sponsored by Sprint
What will you do first with EVO, the first 4G phone?
Visit sprint.com/first -- http://p.sf.net/sfu/sprint-com-first
_______________________________________________
Matplotlib-devel mailing list
Matplotlib-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel

I get something similar with TkAgg (unfortunately, I get an
AttributeError with the macosx backend).

This is the AttributeError:

Traceback (most recent call last):
  File "Desktop/tight_layout.py", line 142, in <module>
    tight_layout()
  File "Desktop/tight_layout.py", line 28, in tight_layout
    tight_subplot_spacing(fig, h_pad_inches, w_pad_inches)
  File "Desktop/tight_layout.py", line 64, in tight_subplot_spacing
    ax_bottom, ax_top, ax_left, ax_right = _get_grid_boundaries(fig)
  File "Desktop/tight_layout.py", line 99, in _get_grid_boundaries
    renderer = fig.canvas.get_renderer()
AttributeError: 'FigureCanvasMac' object has no attribute 'get_renderer'

Is a backend required to implement a get_renderer method? I only see it implemented in backend_agg.py, and it's missing in backend_bases.py, backend_template.py, backend_cairo.py, and in the macosx backend. If you want to try your code with the macosx backend, you can use fig.canvas.renderer instead of fig.canvas.get_renderer().

--Michiel.

···

--- On Wed, 7/21/10, Tony S Yu <tsyu80@...149...> wrote:

According to backend_bases.FigureCanvas, a renderer attr is not
guaranteed either.
The Agg* backends rely on get_renderer so that they can get a properly
sized renderer on figure resizes, dpi changes, etc. We could handle
this on the agg side with a property, or require all canvases to
supply get renderer.

The tricky bit is that renderers may not be available until draw time
for some backends, and thus may not have proper size information if
accessed too early (this is probably at the heart of the gtk bug we
are seeing). It is probably a good idea to settle on something so
developers can get access to the renderer in a consistent state, and
this might require support a "raise_event" which one could connect to
and get the figure as soon as it is raised (which would be triggered
on canvas creation for non gui backends presumably and after the
window is raised for gui backends). In the callback, accessing
canvas.renderer or canvas.get_renderer would return a renderer with
proper sizing.

JDH

···

On Thu, Jul 22, 2010 at 7:40 AM, Michiel de Hoon <mjldehoon@...42...> wrote:

Is a backend required to implement a get_renderer method? I only see it implemented in backend_agg.py, and it's missing in backend_bases.py, backend_template.py, backend_cairo.py, and in the macosx backend. If you want to try your code with the macosx backend, you can use fig.canvas.renderer instead of fig.canvas.get_renderer().

Sorry, I don't know much about how the backends are structured. But this talk of backends got me thinking: Since the Agg backend (non-GUI version) is guaranteed to be installed (correct?), you could create an Agg renderer, and just use that to calculate all the sizing information. This experiment worked for all GUI backends on my system (Qt4Agg, TkAgg, MacOSX).

Are there any downsides to using Agg to calculate sizing info and then rendering again on a GUI backend? Performance issues? I guess it's possible differences in sizing between backends will show up in the resized subplots, but these differences should be small, right?

-Tony

P.S. I've removed calls to "subplots" in example code for easier testing.

tight_layout_agg.py (6.07 KB)

···

On Jul 22, 2010, at 8:59 AM, John Hunter wrote:

On Thu, Jul 22, 2010 at 7:40 AM, Michiel de Hoon <mjldehoon@...42...> wrote:

Is a backend required to implement a get_renderer method? I only see it implemented in backend_agg.py, and it's missing in backend_bases.py, backend_template.py, backend_cairo.py, and in the macosx backend. If you want to try your code with the macosx backend, you can use fig.canvas.renderer instead of fig.canvas.get_renderer().

According to backend_bases.FigureCanvas, a renderer attr is not
guaranteed either.
The Agg* backends rely on get_renderer so that they can get a properly
sized renderer on figure resizes, dpi changes, etc. We could handle
this on the agg side with a property, or require all canvases to
supply get renderer.

The tricky bit is that renderers may not be available until draw time
for some backends, and thus may not have proper size information if
accessed too early (this is probably at the heart of the gtk bug we
are seeing). It is probably a good idea to settle on something so
developers can get access to the renderer in a consistent state, and
this might require support a "raise_event" which one could connect to
and get the figure as soon as it is raised (which would be triggered
on canvas creation for non gui backends presumably and after the
window is raised for gui backends). In the callback, accessing
canvas.renderer or canvas.get_renderer would return a renderer with
proper sizing.

JDH

On Thu, Jul 22, 2010 at 8:57 AM, Tony S Yu <tsyu80@

According to backend_bases.FigureCanvas, a renderer attr is not
guaranteed either.
The Agg* backends rely on get_renderer so that they can get a properly
sized renderer on figure resizes, dpi changes, etc. We could handle
this on the agg side with a property, or require all canvases to
supply get renderer.

No, this won't work because the sizing information depends on the GUI
window size. Agg adapts the renderer to the GUI window size. So in
GTKAgg we are using the Agg renderer, but GTK is determining the
window size when it is raised. We try to get GTK to produce a canvas
of a fixed size, but cannot guarantee it so we reset the renderer size
in necessary when the canvas is raised.

JDH

Do we actually need a renderer in each of the backends? At least when I was writing the Mac OS X backend, it was not clear to me what functionality should go in the renderer and what should go in the graphics context. I expect that the same is true for the other postscript-style backends such as the cairo, pdf, and ps backends.

In the end, I decided to mainly use the graphics context because it nicely maps to the Quartz graphics context at the C level, whereas the renderer doesn't have a corresponding data structure at the C level. In the Mac OS X backend, basically what the renderer does is to forward to the graphics context. In the ps backend most of the work is being done in the renderer instead of the graphics context, but again this is just a choice. I think that we should first remove such inconsistencies between backends.

--Michiel

···

--- On Thu, 7/22/10, John Hunter <jdh2358@...149...> wrote:

We could handle this on the agg side with a property,
or require all canvases to supply get renderer.

I'm not sure if I understand. Are you talking about responding to window resizing events, or are you saying that Figure.get_height/get_width don't match the window size for some GUI backends?

I create the Agg renderer based on Figure.get_height/get_width. If the window is resized, then the layout would definitely change, but you could call `tight_layout` again to adjust subplot spacing (which would create a new Agg renderer based on the new window size). Am I missing something here? Were you thinking I wanted to interactively adapt the spacings? (If that's the case, I wasn't that ambitious.)

-Tony

···

On Jul 22, 2010, at 10:07 AM, John Hunter wrote:

On Thu, Jul 22, 2010 at 8:57 AM, Tony S Yu <tsyu80@

According to backend_bases.FigureCanvas, a renderer attr is not
guaranteed either.
The Agg* backends rely on get_renderer so that they can get a properly
sized renderer on figure resizes, dpi changes, etc. We could handle
this on the agg side with a property, or require all canvases to
supply get renderer.

No, this won't work because the sizing information depends on the GUI
window size. Agg adapts the renderer to the GUI window size. So in
GTKAgg we are using the Agg renderer, but GTK is determining the
window size when it is raised. We try to get GTK to produce a canvas
of a fixed size, but cannot guarantee it so we reset the renderer size
in necessary when the canvas is raised.

JDH