Live slider updating (gtk help)

  I just committed code which adds a new kwarg to the

    > slider to allow for live updates while dragging. It is
    > off by default since the gtk backend seems to queue
    > events. Wx and tk work in a natural way (they don't
    > generate mouse events while busy). Who is the resident
    > gtk expert, and do you think you know how to fix this?
    > It would be nice to have the live slider be on by
    > default.

The backend_bases.FigureCanvas defines a base method draw_idle which
calls draw by default

    def draw_idle(self, *args, **kwargs):
        """
        draw only if idle; defaults to draw but backends can overrride
        """
        self.draw(*args, **kwargs)

backend_gtk subclasses this method to do proper idle drawing. So call
idle_draw from Slider.

Note that there are two updates to consider, the update of the slider
and the update of the figure it is manipulating. There is no reason
not to continuously update the slider once you have this idle business
sorted out. But you probably do want to continuously update the
figure it is modifying in every case, because the figure could be very
complicated to draw. Instead, you might want to expose this live
update property as a checkbox in the slider, since if the figure is
expensive (see examples/widgets/check_buttons.py and note you will
have to update from CVS because I just committed a patch to make check
buttons work for single buttons)

I've been working on how to draw individual artists w/o updating the
entire figure. This is mostly working with a few bugs in gtkagg.
Basically you have to manage the background region as a memory chunk
and then reblit the background before redrawing the artist. This was
designed to make animations faster and to support better GUI widgets.

The first example below shows how to draw just a rectangle and move it
around the screen w/o updating the entire figure. The second example
is more complex, and makes a rectangle widget which you can move
around the canvas or resize using a standard click on edge drag
operation. In this one, you create new rectangles by pressing "n"
(only works on gtkagg). Only two methods are required to support this
on other backends

  region = canvas.copy_from_bbox(bbox)
  canvas.restore_region(region)
  canvas.blit(bbox)

This is all highly experimental and subject to change, but it maybe
something you want to incorporate into the slider for better realtime
updating. Note that you can easily add copy_from_bbox and
restore_region to cocoaagg by simply forwarding the calls onto the agg
renderer, as gtkagg does. For the blit method, you have to figure out
how to make cocoa blit a region -- shouldn't be too hard.

JDH

######## Example 1 #############

from pylab import *
ion()
ax = subplot(111)
plot(rand(100), rand(100), 'o')

r = Rectangle((.5, .5), width=.1, height=.1)
ax.add_patch(r)
r.region = ax.figure.canvas.copy_from_bbox(r.get_window_extent())
draw()
def move(event):
    if not move.idle or event.inaxes != ax: return
    move.idle = 0
    ax.figure.canvas.restore_region(r.region)
    r.xy = event.xdata, event.ydata
    r.region = ax.figure.canvas.copy_from_bbox(r.get_window_extent())
    ax.draw_artist(r)
    ax.figure.canvas.blit(ax.bbox)
    move.idle = 1
move.idle = 1
connect('motion_notify_event', move)
show()

######## Example 2 #############
from pylab import *
from matplotlib.transforms import lbwh_to_bbox
from matplotlib.mlab import dist_point_to_segment

class RectangleWidget(Rectangle):
    def __init__(self, figure, *args, **kwargs):
        Rectangle.__init__(self, *args, **kwargs)
        self.figure = figure
        self.idle = True
        self.oldbox = self.get_window_extent()
        self.newbox = self.get_window_extent()
        
        self.region = self.figure.canvas.copy_from_bbox(self.oldbox)
        self.figure.canvas.mpl_connect('motion_notify_event', self.move)
        self.figure.canvas.mpl_connect('button_press_event', self.press)
        self.figure.canvas.mpl_connect('button_release_event', self.release)
        
        self.figure.draw_artist(self)
        self._resize = False
        self._drag = False

    def redraw(self):
        canvas = self.figure.canvas
        canvas.restore_region(self.region)
        self.region = canvas.copy_from_bbox(self.newbox)
        self.figure.draw_artist(self)
        canvas.blit(self.oldbox)
        canvas.blit(self.newbox)
        
    def press(self, event):
        if event.button!=1: return
        x, y = self.xy
        w, h = self.width, self.height
        # the four line segments of the rectangle
        lb = x, y
        lt = x, y+h
        rb = x+w, y
        rt = x+w, y+h
        segments = {
            'left' : (lb, lt),
            'right' : (rb, rt),
            'top' : (lt, rt),
            'bottom' : (lb, rb),
            }
        p = event.x, event.y
        near = []
        for name, segment in segments.items():
            s0, s1 = segment
            d = dist_point_to_segment(p, s0, s1)
            if d<5: near.append(name)

        info = event.x, event.y, self.xy[0], self.xy[1], self.width, self.height, near
        if len(near):
            self._resize = info
            self.set_edgecolor('red')
            self.set_linewidth(2)
        else:
            self._resize = False
            if self.get_window_extent().contains(event.x, event.y):
                self._drag = info
        self.redraw()

    def release(self, event):
        if event.button!=1: return
        self._resize = False
        self._drag = False
        self.idle = True
        self.set_edgecolor('black')
        self.set_linewidth(0.5)
        self.redraw()
        
    def move(self, event):
        if not (self.idle and (self._resize or self._drag)): return

        self.idle = False
        canvas = self.figure.canvas
        if self._resize:
            exo, eyo, xo, yo, wo, ho, near = self._resize
        else:
            exo, eyo, xo, yo, wo, ho, near = self._drag

        scalex=0; scaley=0; scalew=0; scaleh=0
        if 'left' in near:
            scalex = 1
            scalew = -1
        if 'right' in near:
            scalew = 1
        if 'bottom' in near:
            scaley = 1
            scaleh = -1
        if 'top' in near:
            scaleh = 1

        if self._drag:
            scalex = 1
            scaley = 1
        
        dx = event.x - exo
        dy = event.y - eyo
        if event.button ==1:
            self.oldbox = self.get_padded_box()

            newx = xo + scalex*dx
            if scalew==0 or newx<xo+wo:
                self.xy[0] = newx
            newy = yo + scaley*dy
            if scaleh==0 or newy<yo+ho:
                self.xy[1] = newy

            neww = wo + scalew*dx
            if neww>0: self.width = neww
            
            newh = ho + scaleh*dy
            if newh>0: self.height = newh
            
            #self.xy = event.x, event.y
            self.newbox = self.get_padded_box()
            #print event.x, event.y, self.get_padded_box().get_bounds()
            self.redraw()
        self.idle = True

    def get_padded_box(self, pad=5):
        xmin = self.xy[0]-pad
        ymin = self.xy[1]-pad
        return lbwh_to_bbox(xmin, ymin, self.width+2*pad, self.height+2*pad)
        
ion()
fig = figure()
#ax = subplot(111)

def keypress(event):
    if event.key=='n':
        widget = RectangleWidget( fig, (100,100), 20, 30)
        fig.canvas.blit()

fig.canvas.mpl_connect('key_press_event', keypress)

show()

draw_idle still seems to suffer from the "trying to catch up" problem.
I added the gdk.POINTER_MOTION_HINT_MASK to enable passive mouse motion
event handling. The gtk interface does not lag behind with this
enabled, even with complex figures. Does this break functionality you
are expecting somewhere else? Now the gtk backend acts the same as wx
and tk with respect to mouse motion.
  Here is the reference I found for addressing this problem:
http://www.pygtk.org/pygtk2tutorial/sec-EventHandling.html#id3096656

- Charlie

John Hunter wrote:

···

"Charles" == Charles Moad <cmoad@...209...> writes:

    > I just committed code which adds a new kwarg to the
    > slider to allow for live updates while dragging. It is
    > off by default since the gtk backend seems to queue
    > events. Wx and tk work in a natural way (they don't
    > generate mouse events while busy). Who is the resident
    > gtk expert, and do you think you know how to fix this?
    > It would be nice to have the live slider be on by
    > default.

The backend_bases.FigureCanvas defines a base method draw_idle which
calls draw by default

    def draw_idle(self, *args, **kwargs):
        """
        draw only if idle; defaults to draw but backends can overrride
        """
        self.draw(*args, **kwargs)

backend_gtk subclasses this method to do proper idle drawing. So call
idle_draw from Slider.

Note that there are two updates to consider, the update of the slider
and the update of the figure it is manipulating. There is no reason
not to continuously update the slider once you have this idle business
sorted out. But you probably do want to continuously update the
figure it is modifying in every case, because the figure could be very
complicated to draw. Instead, you might want to expose this live
update property as a checkbox in the slider, since if the figure is
expensive (see examples/widgets/check_buttons.py and note you will
have to update from CVS because I just committed a patch to make check
buttons work for single buttons)

I've been working on how to draw individual artists w/o updating the
entire figure. This is mostly working with a few bugs in gtkagg.
Basically you have to manage the background region as a memory chunk
and then reblit the background before redrawing the artist. This was
designed to make animations faster and to support better GUI widgets.

The first example below shows how to draw just a rectangle and move it
around the screen w/o updating the entire figure. The second example
is more complex, and makes a rectangle widget which you can move
around the canvas or resize using a standard click on edge drag
operation. In this one, you create new rectangles by pressing "n"
(only works on gtkagg). Only two methods are required to support this
on other backends

  region = canvas.copy_from_bbox(bbox)
  canvas.restore_region(region)
  canvas.blit(bbox)

This is all highly experimental and subject to change, but it maybe
something you want to incorporate into the slider for better realtime
updating. Note that you can easily add copy_from_bbox and
restore_region to cocoaagg by simply forwarding the calls onto the agg
renderer, as gtkagg does. For the blit method, you have to figure out
how to make cocoa blit a region -- shouldn't be too hard.

JDH

######## Example 1 #############

from pylab import *
ion()
ax = subplot(111)
plot(rand(100), rand(100), 'o')

r = Rectangle((.5, .5), width=.1, height=.1)
ax.add_patch(r)
r.region = ax.figure.canvas.copy_from_bbox(r.get_window_extent())
draw()
def move(event):
    if not move.idle or event.inaxes != ax: return
    move.idle = 0
    ax.figure.canvas.restore_region(r.region)
    r.xy = event.xdata, event.ydata
    r.region = ax.figure.canvas.copy_from_bbox(r.get_window_extent())
    ax.draw_artist(r)
    ax.figure.canvas.blit(ax.bbox)
    move.idle = 1
move.idle = 1
connect('motion_notify_event', move)
show()

######## Example 2 #############
from pylab import *
from matplotlib.transforms import lbwh_to_bbox
from matplotlib.mlab import dist_point_to_segment

class RectangleWidget(Rectangle):
    def __init__(self, figure, *args, **kwargs):
        Rectangle.__init__(self, *args, **kwargs)
        self.figure = figure
        self.idle = True
        self.oldbox = self.get_window_extent()
        self.newbox = self.get_window_extent()
        
        self.region = self.figure.canvas.copy_from_bbox(self.oldbox)
        self.figure.canvas.mpl_connect('motion_notify_event', self.move)
        self.figure.canvas.mpl_connect('button_press_event', self.press)
        self.figure.canvas.mpl_connect('button_release_event', self.release)
        
        self.figure.draw_artist(self)
        self._resize = False
        self._drag = False

    def redraw(self):
        canvas = self.figure.canvas
        canvas.restore_region(self.region)
        self.region = canvas.copy_from_bbox(self.newbox)
        self.figure.draw_artist(self)
        canvas.blit(self.oldbox)
        canvas.blit(self.newbox)
        
    def press(self, event):
        if event.button!=1: return
        x, y = self.xy
        w, h = self.width, self.height
        # the four line segments of the rectangle
        lb = x, y
        lt = x, y+h
        rb = x+w, y
        rt = x+w, y+h
        segments = {
            'left' : (lb, lt),
            'right' : (rb, rt),
            'top' : (lt, rt),
            'bottom' : (lb, rb),
            }
        p = event.x, event.y
        near = []
        for name, segment in segments.items():
            s0, s1 = segment
            d = dist_point_to_segment(p, s0, s1)
            if d<5: near.append(name)

        info = event.x, event.y, self.xy[0], self.xy[1], self.width, self.height, near
        if len(near):
            self._resize = info
            self.set_edgecolor('red')
            self.set_linewidth(2)
        else:
            self._resize = False
            if self.get_window_extent().contains(event.x, event.y):
                self._drag = info
        self.redraw()

    def release(self, event):
        if event.button!=1: return
        self._resize = False
        self._drag = False
        self.idle = True
        self.set_edgecolor('black')
        self.set_linewidth(0.5)
        self.redraw()
        
    def move(self, event):
        if not (self.idle and (self._resize or self._drag)): return

        self.idle = False
        canvas = self.figure.canvas
        if self._resize:
            exo, eyo, xo, yo, wo, ho, near = self._resize
        else:
            exo, eyo, xo, yo, wo, ho, near = self._drag

        scalex=0; scaley=0; scalew=0; scaleh=0
        if 'left' in near:
            scalex = 1
            scalew = -1
        if 'right' in near:
            scalew = 1
        if 'bottom' in near:
            scaley = 1
            scaleh = -1
        if 'top' in near:
            scaleh = 1

        if self._drag:
            scalex = 1
            scaley = 1
        
        dx = event.x - exo
        dy = event.y - eyo
        if event.button ==1:
            self.oldbox = self.get_padded_box()

            newx = xo + scalex*dx
            if scalew==0 or newx<xo+wo:
                self.xy[0] = newx
            newy = yo + scaley*dy
            if scaleh==0 or newy<yo+ho:
                self.xy[1] = newy

            neww = wo + scalew*dx
            if neww>0: self.width = neww
            
            newh = ho + scaleh*dy
            if newh>0: self.height = newh
            
            #self.xy = event.x, event.y
            self.newbox = self.get_padded_box()
            #print event.x, event.y, self.get_padded_box().get_bounds()
            self.redraw()
        self.idle = True

    def get_padded_box(self, pad=5):
        xmin = self.xy[0]-pad
        ymin = self.xy[1]-pad
        return lbwh_to_bbox(xmin, ymin, self.width+2*pad, self.height+2*pad)
        
ion()
fig = figure()
#ax = subplot(111)

def keypress(event):
    if event.key=='n':
        widget = RectangleWidget( fig, (100,100), 20, 30)
        fig.canvas.blit()

fig.canvas.mpl_connect('key_press_event', keypress)

show()