Patch for having legends outside right of plot

Hmm, I must admit I knew nothing about figures when I

    > posted. But now that I've done some homework, I see that
    > using Figure.legend is suboptimal as well because it can
    > neither resize the axes accordingly nor place outside
    > legends for each subplot automatically.

Hey Killian,

OK, I am happy to include this because I think the auto-resizing
capability is useful (eg following the colorbar model). Now that you
are aware of the figure legend, please take a look at your self.parent
handling because parent can be an Axes or a Figure. You will want to
check for isaxes before calling

  self.parent.get_position()

and associated functions.

Also, as a matter of style and efficiency, I prefer to avoid multiple
function calls in lines like

    figwidth = self.get_figure().get_figwidth()*float(self.get_figure().get_dpi())
    figheight = self.get_figure().get_figheight()*float(self.get_figure().get_dpi())

rather

fig = self.get_figure()
dpi = fig.get_dpi()
figheight = fig.get_figheight()*dpi
figwidth = fig.get_figwidth()*dpi

dpi is already a float....

But even more pithily <wink>

  w, h = fig.canvas.get_width_height()

Also, although these aren't written down anywhere (wiki entry needed
so feel free to make one!) the matplotlib coding conventions are

  classes: UpperCase
  functions and methods: underscore_separated
  attributes and variables : lower of lowerUpper

so vars like space_needed should be spaceneeded or spaceNeeded. Ditto
for the fudge_* vars and new_aw and so on.

Finally, I think your patch against your own tree, because it contains
lines like

- 'upper outside right' : 11,
+ 'upper outside right' : 11, # these only make sense with axes legends

Eg, it is removing things that do not exist in matplotlib CVS -- make
sure you apply diff against mpl CVS and not your own tree !

JDH

John,
I had some questions about this resize work. Here is the code for resize from backend_gtk.py:

     def resize(self, w, h):
         'set the drawing area size in pixels'
         winw, winh = self.parent.parent.get_size()
         tmp, tmp, myw, myh = self.allocation
         padw = winw-myw
         padh = winh-myh
         self.parent.parent.resize(w+padw, h+padh)

I'm a little concerned about this implementation. It looks like the widget is telling it's parent's parent to resize. Doesn't this mean that the ability of the widget to be used as a modular component is reduced because this code requires a certain parent child relationship?

I think it's fairly important that a widget have only very minimum interactions with it's parent. Is there some way this could be implemented that doesn't require the child widget to be calling methods on the parent?

If I understand the basic premise you've outlined below, you want a resize in the child (the drawing widget) to cause the parent window to resize. Only the parent can figure out what it's size needs to be (since it knows about it's margins, toolbars, etc). On the surface, it seems like there are two ways to handling this:
1) Tell the window to resize the canvas. The window can use it's own layout classes, etc and correctly resize itself and the canvas.
2) Tell the canvas to resize and have this trigger a window resize.

I think the first option is much cleaner. However, if that isn't possible, I suggest that we do something like this:

- Tell the canvas to resize. It should resize itself and emit some type of 'plotCanvasResize' signal (GUI callback).
- When the window is originally constructed, it would attach a method on the window to this signal so that we basically get the same behavior as 1) above. Telling the canvas to resize emits the signal which calls the window method to do the real resizing. The canvas widget never knows that it's part of the window at all.

Of course this assumes that we have access to signal/slot (in Qt terms) systems in the Python layer. I'd also suggest that we stay away from calling it 'resize' since most GUI toolkits already have resize methods/attributes that mean very specific things.

Thoughts?
Ted

There is a new method in the figure canvas in CVS that I would like
the maintainers of the various GUI backends to implement

class FigureCanvasYourBackend

     def resize(self, w, h):
         """
         set the canvas size in pixels
         """
         pass

This should set the canvas (not window) size and trigger a GUI resize
event so that the window is resized accordingly. There is a reference
implementation in backend_gtk.py. You should be able to lift the
logic for computing the new canvas size directly from that code.

Among other things, this will allow better control of the canvas size
from a script or shell. Eg, the following works with GTKAgg in an
interactive session:

     In [1]: fig = figure()
     In [2]: fig.set_figsize_inches(3,4,forward=True)
     In [3]: fig.canvas.resize(500,600)

Ie, you can set the canvas size either in pixels or inches depending
on which method you choose.

Also, I added a new connect signal 'resize_event' that triggers a
backend_bases.ResizeEvent on a canvas.resize. You should call

   self.resize_event()

from the part of your code that handles GUI configure events (see for
example the GTK and GTKAgg backends). Note depending on your toolkit,
you may not want to call this from the FigureCanvas.resize method.
Eg, in GTK* calling "canvas.resize" triggers a call to
canvas.configure_event, which in turn sets the new figure size
properties and once all this is done, calls canvas.resize_event.

Here is some test code

     from pylab import figure, connect, show
     fig = figure()
     def resize(event):
         print 'resize canvas', event.width, event.height

     connect('resize_event', resize)
     show()

Checking in lib/matplotlib/backend_bases.py;
/cvsroot/matplotlib/matplotlib/lib/matplotlib/backend_bases.py,v <-- backend_bases.py
new revision: 1.69; previous revision: 1.68

Thanks!
JDH

Ted Drain Jet Propulsion Laboratory ted.drain@...179...

On Friday 07 October 2005 16:30, John Hunter pondered:

OK, I am happy to include this because I think the auto-resizing
capability is useful (eg following the colorbar model). Now that you
are aware of the figure legend, please take a look at your self.parent
handling because parent can be an Axes or a Figure. You will want to
check for isaxes before calling

  self.parent.get_position()

and associated functions.

Also, as a matter of style and efficiency, (snip)

Cool, I've rewritten my code with the suggested simplification and style
concerns you put forward.

Finally, I think your patch against your own tree, because it contains
lines like

- 'upper outside right' : 11,
+ 'upper outside right' : 11, # these only make sense with axes
legends

Eg, it is removing things that do not exist in matplotlib CVS -- make
sure you apply diff against mpl CVS and not your own tree !

Oops, I diff'ed my most recent change against my first attempt instead of the
original source, my bad. The original source tree I worked from was the
mpl-0.84 tarball, but I guess using CVS is safer. Now, being a newbie to open
source *developing* and CVS, how do I access and use mpl's CVS repository? Do
you prefer my patches to be sent to this list or should I commit my changes
to cvs?

More importantly though, after I made my changes I noticed that my axes
resizing magic is based on the assumption that the plot the legend belongs to
spans the width of the entire figure. This is not true in general and
accordingly my new code, with resizing enabled, produces horrible results for
e.g. subplot(121)/subplot(122), i.e. with multiple columns.

So, I thought just retrieve the dimensions of the current subplot somehow, but
to no avail. Is there a way to retrieve the dimensions and/or position of the
subplot, not the axes it contains? Something like lbwh=(0,0,0.5,1) for row 1,
col 1 and lbwh=(0.5,0,0.5,1) for row 1, col 2 in the above example? That way
I'd be able to calculate how much space axes + outside legend is supposed to
occupy (my spaceNeeded variable).

Also, I see that this will only work when subplots are used, as soon as custom
axes positioning/sizing is used such as that in examples/figlegend_demo.py,
there seems to be no way to calculate this required space. Any way to check
for this? Or should we leave it up to the user to turn resizing off if it
doesn't give the desired results?

···

--
Kilian Hagemann

Climate Systems Analysis Group
University of Cape Town
Republic of South Africa
Tel(w): ++27 21 650 2748