backend_gtk and expose_event

        Hello everyone,
        
        I have the following problem with my application, which allows
        you to
        manipulate some of properties of a plot through the use of a
        gui-style
        property editor:
        
        Whenever I set the y-axis scale to logarithmic, but have the y
        range
        include a negative value, I will get a ValueError upon
        expose_event. The
        bad thing is now, that my application catches all exceptions
        and
        displays a user-friendly dialog, explaining or listing the
        exception and
        offering the user to mail the output to the dumb author of the
        program.
        But whenever the user quits the dialog, a new expose event of
        the canvas
        will be triggered, leading to an unstoppable series of
        exceptions
        (except when using xkill of course).
        
        So what I would like to propose is a modification of
        expose_event
        located in backends/backend_gtk.py to better handle any
        exceptions that
        it might trigger.
        One not so nice solution of mine was to wrap the method in a
        try...finally statement, which ensures that False is returned
        and the
        expose_event is stopped:
        
            def expose_event(self, widget, event):
                """Expose_event for all GTK backends. Should not be
        overridden.
                """
                if _debug: print 'FigureCanvasGTK.%s' % fn_name()
        
            try:
                if GTK_WIDGET_DRAWABLE(self):
                if self._need_redraw:
                    x, y, w, h = self.allocation
                    self._pixmap_prepare (w, h)
                    self._render_figure(self._pixmap, w, h)
                    self._need_redraw = False
        
                x, y, w, h = event.area
                self.window.draw_drawable
        (self.style.fg_gc[self.state],
                               self._pixmap, x, y, x, y, w, h)
            finally:
                return False # finish event propagation?
        
        Of course, this has the obvious disadvantage, that no
        exception is
        triggered at all, which is undesirable.

I don't understand, the exception is still raised after the 'finally'
clause.

        Are there any better approaches to this?
        
        Best regards,
        
        Niklas Volbers.

You could simply popup the user-friendly dialog and then kill the
program after the user has read the message and clicked "OK".

When you use semilogy(), matplotlib ignores the values <= 0 (I'm not
sure where the code is that does that), I think an application should do
the same.

If we add try ... finally to expose_event() that would mean we are
handling application errors within library code, which I don't think is
right. Also, this problem would affect all backends so it would probably
be better to handle it (if it is needed) in the Figure.draw() rather
than having to modify every backend.

Steve

Send instant messages to your online friends http://au.messenger.yahoo.com

···

On Tue, 2006-04-18 at 20:11 -0700, matplotlib-devel-request@lists.sourceforge.net wrote:

Steven,

thanks for your reply to my rather confusing email.

Steven Chaplin wrote:

       [...]
              So what I would like to propose is a modification of
       expose_event located in backends/backend_gtk.py to better handle any
       exceptions that it might trigger.
       One not so nice solution of mine was to wrap the method in a try...finally statement, which ensures that False is returned
       and the expose_event is stopped:
       
               """Expose_event for all GTK backends. Should not be
       overridden.
               """
               if _debug: print 'FigureCanvasGTK.%s' % fn_name()
                  try:
               if GTK_WIDGET_DRAWABLE(self):
               if self._need_redraw:
                   x, y, w, h = self.allocation
                   self._pixmap_prepare (w, h)
                   self._render_figure(self._pixmap, w, h)
                   self._need_redraw = False
                      x, y, w, h = event.area
               self.window.draw_drawable
       (self.style.fg_gc[self.state],
                              self._pixmap, x, y, x, y, w, h)
           finally:
               return False # finish event propagation?
       
       exception is triggered at all, which is undesirable.

I don't understand, the exception is still raised after the 'finally'
clause.

Yes, you are right, it _should_ be triggered. However, I have now realized that expose_event wasn't the problem at all in my situation:

Consider the following settings for the Axes ax:

ax.set_yscale('log')
ax.set_ylim(min=-5)

and then redraw the axes. The negative minimum value raised an exception in my case:

Traceback (most recent call last):
  File "/usr/lib/python2.4/site-packages/matplotlib/backends/backend_gtk.py", line 294, in expose_event
    self._render_figure(self._pixmap, w, h)
  File "/usr/lib/python2.4/site-packages/matplotlib/backends/backend_gtk.py", line 282, in _render_figure
    self.figure.draw (self._renderer)
  File "/usr/lib/python2.4/site-packages/matplotlib/figure.py", line 529, in draw
    for a in self.axes: a.draw(renderer)
  File "/usr/lib/python2.4/site-packages/matplotlib/axes.py", line 1469, in draw
    self.legend_.draw(renderer)
  File "/usr/lib/python2.4/site-packages/matplotlib/legend.py", line 208, in draw
    self._update_positions(renderer)
  File "/usr/lib/python2.4/site-packages/matplotlib/legend.py", line 574, in _update_positions
    ox, oy = self._find_best_position(w, h)
  File "/usr/lib/python2.4/site-packages/matplotlib/legend.py", line 444, in _find_best_position
    verts, bboxes, lines = self._auto_legend_data()
  File "/usr/lib/python2.4/site-packages/matplotlib/legend.py", line 340, in _auto_legend_data
    xt, yt = trans.numerix_x_y(xdata, ydata)
ValueError: Domain error on Transformation::numerix_x_y

So somehow the _auto_legend_data method would fail in handling the negative limit (which of course should not have been there).
This caused my error box to pop up, which would yet again trigger another expose_event, which of course would again cause the same exception again! My try...finally statement omitted this _somehow_, even though I agree with you that the exception should have been triggered anyway.

You could simply popup the user-friendly dialog and then kill the
program after the user has read the message and clicked "OK".

No way!

When you use semilogy(), matplotlib ignores the values <= 0 (I'm not
sure where the code is that does that), I think an application should do
the same.

If we add try ... finally to expose_event() that would mean we are
handling application errors within library code, which I don't think is
right. Also, this problem would affect all backends so it would probably
be better to handle it (if it is needed) in the Figure.draw() rather
than having to modify every backend.

Basically there are three options:

(1) Leave everything as it is and let matplotlib blame the user/library user if he/she set a negative limit for a logarithmic plot.

(2) Catch the ValueError in _auto_legend_data and continue graciously. Is there any way to output friendly informative warnings? I did
the following in _auto_legend_data in legend.py:

            if isinstance(handle, Line2D):

                try:
                    xdata = handle.get_xdata(valid_only = True)
                    ydata = handle.get_ydata(valid_only = True)
                    trans = handle.get_transform()
                    xt, yt = trans.numerix_x_y(xdata, ydata)
                except ValueError:
                    pass # maybe output an error message here?
                else:
                    # XXX need a special method in transform to do a list of ve$ averts = [inv(v) for v in zip(xt, yt)]
                    lines.append(averts)

(3) Omit the ValueError: Use 'None' as limit, if we have a logarithmic plot and a negative limit. Output a hint. Unfortunately I am too unfamiliar with matplotlib to decide where this check should be done.

Best regards,

Niklas Volbers.

···

On Tue, 2006-04-18 at 20:11 -0700, >matplotlib-devel-request@lists.sourceforge.net wrote:

I think it should be "ax.set_ylim(ymin=-5)".
set_ylim() already catches this type of error.

#!/usr/bin/env python
from pylab import *
x = arange(0.0, 20.0, 0.01)
y = x - 10
semilogy(x, y)
ylabel('semilogy')
grid(True)
ylim(ymin=-5)
show()

Running the above script gives:
  File "./logtest.py", line 8, in ?
    ylim(ymin=-5)
  File "/usr/lib/python2.4/site-packages/matplotlib/pylab.py", line
1131, in ylim
    ret = ax.set_ylim(*args, **kwargs)
  File "/usr/lib/python2.4/site-packages/matplotlib/axes.py", line 1314,
in set_ylim
    raise ValueError('Cannot set nonpositive limits with log transform')
ValueError: Cannot set nonpositive limits with log transform

Steve

Send instant messages to your online friends http://au.messenger.yahoo.com

···

On Wed, 2006-04-19 at 17:47 +0200, N. Volbers wrote:

Consider the following settings for the Axes ax:

ax.set_yscale('log')
ax.set_ylim(min=-5)

and then redraw the axes. The negative minimum value raised an exception
in my case:

Hello Steven,

Steven Chaplin wrote:

Consider the following settings for the Axes ax:

ax.set_yscale('log')
ax.set_ylim(min=-5)

and then redraw the axes. The negative minimum value raised an exception in my case:
   
I think it should be "ax.set_ylim(ymin=-5)".
set_ylim() already catches this type of error.

#!/usr/bin/env python
from pylab import *
x = arange(0.0, 20.0, 0.01)
y = x - 10
semilogy(x, y)
ylabel('semilogy')
grid(True)
ylim(ymin=-5)
show()

Running the above script gives:
File "./logtest.py", line 8, in ?
   ylim(ymin=-5)
File "/usr/lib/python2.4/site-packages/matplotlib/pylab.py", line
1131, in ylim
   ret = ax.set_ylim(*args, **kwargs)
File "/usr/lib/python2.4/site-packages/matplotlib/axes.py", line 1314,
in set_ylim
   raise ValueError('Cannot set nonpositive limits with log transform')
ValueError: Cannot set nonpositive limits with log transform

Yes, you are absolutely right.

Somehow I did not really understand my problem at all, but now I do!

The problem was not at all the limits. Some of my sample data contained a negative value, and the error message in my previous posting was simply saying, that you can't apply the logarithmic transformation to this data point. Now, whenever this happens, my error dialog appears, which triggers the expose event for the canvas, which again will raise the ValueError! There it is, my infinity loop.

But of course it is not that easy! You had negative values as well in your example, and nothing happened! This was because you had no legend that was autopositioned. Try this and see how it fails:

#!/usr/bin/env python
from pylab import *
x = arange(0.0, 20.0, 0.01)
y = x - 10
ylabel('semilogy')
grid(True)
ylim(ymin=-5)
semilogy(x, y)
legend(loc='best')
show()

I guess this can be considered a bug, or not?

Best regards,

Niklas.

···

On Wed, 2006-04-19 at 17:47 +0200, N. Volbers wrote: