Resize bug in tkagg backend

TypeError: resize() takes exactly 2 arguments (3 given)

Yes, this is basically broken on all GUIs except GTK*. There was a
fair amount of discussion back in octover on the devel list about how
to do this right, since basically it requires a child to call a method
on the parent and we don't know a-priori what the container will be.
In pylab we can make it work (but haven't yet across backends) because
we know the parent will be a FigureManager instance, but it would be
nice to come up with a generic method that works regardless of whether
you are using mpl in pylab or not. It's on the slow burner,
currently.

JDH

John & Fernando,
I think the basic problem can be solved in a number of ways. Normally a GUI widget will have an initial size and when it is placed in a window, the window will layout around it (or to it's own size). However, once the window is drawn, changing the size of one or more widget that it contains doesn't mean you're changing the size of the window.

Here's some ideas for how to fix this:

1) Tell people they have to use the resize method on the window object. Pro: simple. Con: not really the API that you want.

2) Write a resize method (but probably don't call it 'resize' because every widget system already uses that name) and have it emit a signal (callback). When you construct the window object, connect that signal to a resize method (again not called 'resize') on the window object that can compute it's correct size based on the new plot size. This way the plot doesn't know who's getting the signal and can remain independent of it's container.

3) Explore the different backends and see if there is a way to configure the window objects so they dynamically resize when a child widget is changed. I did a quick check through Qt and couldn't find anything for this but it might be there.

It seems like 2) would be pretty simple to implement. In the Qt backend, I think you could do this by:

backend_qtagg.py: In FigureCanvasQTAgg.resizeEvent, add an emit call to create a new signal.

self.emit( qt.PYSIGNAL( "plotResize" ), ( w, h ) )

backend_qt.py: In FigureManagerQT.__init__, connect that signal to a new method resizeFromPlot( w, h ). This method should contain the code from the current __init__ method that resizes the window based on a given plot size.

self.connect( self.canvas, PYSIGNAL( "plotResize" ), self.resizeFromPlot )

This should make it so that any size change to the plot will trigger a resize on the main window. There may be a circularity problem but we'd have to try it first an see if calling resize on the main window triggers a resize in the plot.

Ted

···

At 10:25 AM 1/22/2006, John Hunter wrote:

    > TypeError: resize() takes exactly 2 arguments (3 given)

Yes, this is basically broken on all GUIs except GTK*. There was a
fair amount of discussion back in octover on the devel list about how
to do this right, since basically it requires a child to call a method
on the parent and we don't know a-priori what the container will be.
In pylab we can make it work (but haven't yet across backends) because
we know the parent will be a FigureManager instance, but it would be
nice to come up with a generic method that works regardless of whether
you are using mpl in pylab or not. It's on the slow burner,
currently.

JDH

-------------------------------------------------------
This SF.net email is sponsored by: Splunk Inc. Do you grep through log files
for problems? Stop! Download the new AJAX search engine that makes
searching your log files as easy as surfing the web. DOWNLOAD SPLUNK!
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=103432&bid=230486&dat=121642
_______________________________________________
Matplotlib-devel mailing list
Matplotlib-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel

Ted Drain wrote:

John & Fernando,
I think the basic problem can be solved in a number of ways. Normally a GUI widget will have an initial size and when it is placed in a window, the window will layout around it (or to it's own size). However, once the window is drawn, changing the size of one or more widget that it contains doesn't mean you're changing the size of the window.

Here's some ideas for how to fix this:

Thanks for the feedback, Ted. I hope one of your suggestions can be implemented (2 sounds very reasonable). If not, at least I think the 'forward' option should then just be removed. There's no point in exposing a feature known to crash all but ONE backend, I think.

Regards,

f

Maybe one of you guys could refresh my memory. What is the calling sequence we're going for?

Ted

···

At 09:58 AM 1/23/2006, Fernando Perez wrote:

Ted Drain wrote:

John & Fernando,
I think the basic problem can be solved in a number of ways. Normally a GUI widget will have an initial size and when it is placed in a window, the window will layout around it (or to it's own size). However, once the window is drawn, changing the size of one or more widget that it contains doesn't mean you're changing the size of the window.
Here's some ideas for how to fix this:

Thanks for the feedback, Ted. I hope one of your suggestions can be implemented (2 sounds very reasonable). If not, at least I think the 'forward' option should then just be removed. There's no point in exposing a feature known to crash all but ONE backend, I think.

Regards,

f

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

Ted Drain wrote:

Maybe one of you guys could refresh my memory. What is the calling sequence we're going for?

My original message was this:

···

============================================================================
In [1]: gcf().set_figsize_inches((8,8),forward=True)
---------------------------------------------------------------------------
exceptions.TypeError Traceback (most recent
call last)

/home/fperez/code/python/pylab/arrows/<ipython console>

/usr/lib/python2.3/site-packages/matplotlib/figure.py in
set_figsize_inches(self, *args, **kwargs)
      266 canvasw = w*dpival
      267 canvash = h*dpival
--> 268 self.canvas.resize(int(canvasw), int(canvash))
      269
      270 def get_size_inches(self):

TypeError: resize() takes exactly 2 arguments (3 given)

A quick look at the backends code shows this:

      def resize(self, event):
          width, height = event.width, event.height
          self.toolbar.configure(width=width) # , height=height)

So quite obviously, this doesn't work: it's expecting an event object, and a
pair of numbers is being passed.

I'm not sure what the proper fix should be here, I don't really know the code
flow well enough.

I should also note that the gcf().set_figsize_inches((8,8),forward=True) seems
to produce a different on-screen result per backend (in some it doesn't do
anything, in Qt it stretches the figure only horizontally, ...) That code
seems to be pretty much broken.

I noticed that figure(figsize=(8,8)) seems to work fine, but I'm not sure how
to programmatically resize an existing figure, given the above problems.

Beyond this, I'll leave it to the backend experts as to what the right choices should be. I just noted that shipping a feature _known_ to break on all but one backend doesn't sound like the best approach :slight_smile:

Cheers,

f

Fernando,
This looks like you're using the Tk backend. This is one of the problems with trying to use a very common method name in widgets like resize(). Different GUI packages can define it differently. It looks like Tk uses this for it's resize event handling while Gtk and Qt use resize( w, h ) for controlling size and other method names for the event handling like resizeEvent().

After some digging, I think this looks like it might be a bug (or at least a head ache) in the Tk backend design. In backend_bases.py, there is this code:

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

Presumably, this is sort of a default implementation for a virtual method that others can rely on. However, in backends/backend_tkagg.py we have this:

class FigureCanvasTkAgg(FigureCanvasAgg):
    ...
     def resize(self, event):
         width, height = event.width, event.height
     ...

And if you look in backends/backend_agg.py you'll see this:

class FigureCanvasAgg(FigureCanvasBase):
    ...

So FigureCanvasTkAgg is inheriting from FigureCanvasAgg which inherits from FigureCanvasBase which has a resize( w, h ) method. However, FigureCanvasTkAgg re-implements resize with a different signature. At best this very confusing.

It looks like FigureCanvasTkAgg.resize is used as a callback in the ctor for that class for one of the Tk events. Here's what I'd suggest:

1) Rename FigureCanvasTkAgg.resize( event ) to FigureCanvasTkAgg.resize( w, h ).
2) Move the code that extracts the w, h from the event to a new method FigureCanvasTkAgg.resizeEvent( event ) like this:

class FigureCanvasTkAgg:
     def resizeEvent(self, event):
         width, height = event.width, event.height
         if self._resize_callback is not None:
             self._resize_callback(event)
         self.resize( width, height )

3) Change the FigureCanvasTkAgg ctor to use the resizeEvent callback instead of resize.

WARNING: I don't know anything about Tk! I'm hoping someone that does now Tk can check this over and make sure it sounds right.

This of course doesn't actually do anything for fixing the problem we've been discussing about how to resize a plot after it's been created and have the window update accordingly.

Ted

···

At 11:08 AM 1/25/2006, Fernando Perez wrote:

Ted Drain wrote:

Maybe one of you guys could refresh my memory. What is the calling sequence we're going for?

My original message was this:

============================================================================
In [1]: gcf().set_figsize_inches((8,8),forward=True)
---------------------------------------------------------------------------
exceptions.TypeError Traceback (most recent
call last)

/home/fperez/code/python/pylab/arrows/<ipython console>

/usr/lib/python2.3/site-packages/matplotlib/figure.py in
set_figsize_inches(self, *args, **kwargs)
     266 canvasw = w*dpival
     267 canvash = h*dpival
--> 268 self.canvas.resize(int(canvasw), int(canvash))
     269
     270 def get_size_inches(self):

TypeError: resize() takes exactly 2 arguments (3 given)

A quick look at the backends code shows this:

     def resize(self, event):
         width, height = event.width, event.height
         self.toolbar.configure(width=width) # , height=height)

So quite obviously, this doesn't work: it's expecting an event object, and a
pair of numbers is being passed.

I'm not sure what the proper fix should be here, I don't really know the code
flow well enough.

I should also note that the gcf().set_figsize_inches((8,8),forward=True) seems
to produce a different on-screen result per backend (in some it doesn't do
anything, in Qt it stretches the figure only horizontally, ...) That code
seems to be pretty much broken.

I noticed that figure(figsize=(8,8)) seems to work fine, but I'm not sure how
to programmatically resize an existing figure, given the above problems.

Beyond this, I'll leave it to the backend experts as to what the right choices should be. I just noted that shipping a feature _known_ to break on all but one backend doesn't sound like the best approach :slight_smile:

Cheers,

f

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

Hi Ted,

Ted Drain wrote:

Fernando,
This looks like you're using the Tk backend. This is one of the problems with trying to use a very common method name in widgets like resize(). Different GUI packages can define it differently. It looks like Tk uses this for it's resize event handling while Gtk and Qt use resize( w, h ) for controlling size and other method names for the event handling like resizeEvent().

After some digging, I think this looks like it might be a bug (or at least a head ache) in the Tk backend design. In backend_bases.py, there is this code:

[snip detailed look at the problem]

Thanks for the detective work: I was mostly reporting the issue as a TkAgg backend user. I certainly hope that the backend developers can use your info and track this problem down in a clean manner, as this is obviously broken, but I don't know anything about Tk either (I just use it), so I'm afraid I won't be the one doing the work :slight_smile:

Cheers,

f