Index: guitools/BaseInteractor.py
===================================================================
--- guitools/BaseInteractor.py	(revision 4972de2b5011084cbeaf6d68d05d0d2406b76a68)
+++ guitools/BaseInteractor.py	(revision 4972de2b5011084cbeaf6d68d05d0d2406b76a68)
@@ -0,0 +1,156 @@
+interface_color = 'black'
+disable_color = 'gray'
+active_color = 'red'
+rho_color = 'black'
+mu_color = 'green'
+P_color = 'blue'
+theta_color = 'orange'
+profile_colors = [rho_color, mu_color, P_color, theta_color]
+
+class _BaseInteractor:
+    """
+    Share some functions between the interface interactor and various layer
+    interactors.
+    
+    Individual interactors need the following functions:
+    
+        save(ev)  - save the current state for later restore
+        restore() - restore the old state
+        move(x,y,ev) - move the interactor to position x,y
+        moveend(ev) - end the drag event
+        update() - draw the interactors
+        
+    The following are provided by the base class:
+    
+        connect_markers(markers) - register callbacks for all markers
+        clear_markers() - remove all items in self.markers
+        onHilite(ev) - enter/leave event processing
+        onLeave(ev) - enter/leave event processing
+        onClick(ev) - mouse click: calls save()
+        onRelease(ev) - mouse click ends: calls moveend()
+        onDrag(ev) - mouse move: calls move() or restore()
+        onKey(ev) - keyboard move: calls move() or restore()
+        
+    Interactor attributes:
+    
+        base  - model we are operating on
+        axes  - axes holding the interactor
+        color - color of the interactor in non-active state
+        markers - list of handles for the interactor
+    """
+    def __init__(self,base,axes,color='black'):
+        self.base = base
+        self.axes = axes
+        self.color = color
+        
+    def clear_markers(self):
+        '''
+        Clear old markers and interfaces.
+        '''
+        for h in self.markers: h.remove()
+        if self.markers: self.base.connect.clear(*self.markers)
+        self.markers = []
+
+    def save(self, ev):
+        pass
+    def restore(self, ev):
+        pass
+    def move(self, x, y, ev):
+        pass
+    def moveend(self, ev):
+        pass
+
+    def connect_markers(self,markers):
+        """
+        Connect markers to callbacks
+        """
+        for h in markers:
+            connect = self.base.connect
+            connect('enter', h, self.onHilite)
+            connect('leave', h, self.onLeave)
+            connect('click', h, self.onClick)
+            connect('release', h, self.onRelease)
+            connect('drag', h, self.onDrag)
+            connect('key', h, self.onKey)
+
+    def onHilite(self, ev):
+        """
+        Hilite the artist reporting the event, indicating that it is
+        ready to receive a click.
+        """
+        ev.artist.set_color(active_color)
+        self.base.draw()
+        return True
+
+    def onLeave(self, ev):
+        """
+        Restore the artist to the original colour when the cursor leaves.
+        """
+        ev.artist.set_color(self.color)
+        self.base.draw()
+        return True
+            
+    def onClick(self, ev):
+        """
+        Prepare to move the artist.  Calls save() to preserve the state for
+        later restore().
+        """
+        self.clickx,self.clicky = ev.xdata,ev.ydata
+        self.save(ev)
+        return True
+
+    def onRelease(self, ev):
+        self.moveend(ev)
+        return True
+
+    def onDrag(self, ev):
+        """
+        Move the artist.  Calls move() to update the state, or restore() if
+        the mouse leaves the window.
+        """
+        inside,prop = self.axes.contains(ev)
+        if inside:
+            self.clickx,self.clicky = ev.xdata,ev.ydata
+            self.move(ev.xdata,ev.ydata,ev)
+        else:
+            self.restore()
+        self.base.update()
+        return True
+    
+    def onKey(self, ev):
+        '''
+        Respond to keyboard events.  Arrow keys move the widget.  Escape
+        restores it to the position before the last click.
+        
+        Calls move() to update the state.  Calls restore() on escape.
+        '''
+        if ev.key == 'escape':
+            self.restore()
+        elif ev.key in ['up', 'down', 'right', 'left']:
+            dx,dy = self.dpixel(self.clickx,self.clicky,nudge=ev.control)
+            if ev.key == 'up': self.clicky += dy
+            elif ev.key == 'down': self.clicky -= dy
+            elif ev.key == 'right': self.clickx += dx
+            else: self.clickx -= dx
+            self.move(self.clickx,self.clicky,ev)
+        else:
+            return False
+        self.base.update()
+        return True
+
+    def dpixel(self,x,y,nudge=False):
+        '''
+        Return the step size in data coordinates for a small
+        step in screen coordinates.  If nudge is False (default)
+        the step size is one pixel.  If nudge is True, the step
+        size is 0.2 pixels.
+        '''
+        ax = self.axes
+        px,py = ax.transData.inverse_xy_tup((x,y))
+        if nudge:
+            nx,ny = ax.transData.xy_tup((px+0.2,py+0.2))
+        else:
+            nx,ny = ax.transData.xy_tup((px+1.,py+1.))
+        dx,dy = nx-x,ny-y
+        return dx,dy
+
Index: guitools/PlotPanel.py
===================================================================
--- guitools/PlotPanel.py	(revision ad8bcd6945b6f5a975704dfa5d1fb0894ccc99b2)
+++ guitools/PlotPanel.py	(revision 4972de2b5011084cbeaf6d68d05d0d2406b76a68)
@@ -14,7 +14,7 @@
 from plottables import Theory1D
 #TODO: make the plottables interactive
+from binder import BindArtist
 
 DEBUG = False
-print "hello"
 
 from plottables import Graph
@@ -149,4 +149,9 @@
         
         self.axes = [self.subplot]
+        
+        # Interactor
+        self.connect = BindArtist(self.subplot.figure)
+        #self.selected_plottable = None
+
         # new data for the fit 
         self.fit_result = Theory1D(x=[], y=[], dy=None)
@@ -560,4 +565,35 @@
     #    if trigger == 'xlim': self._connect_to_xlim(callback)
 
+    def interactive_points(self,x,y,dx=None,dy=None,name='', color=0,symbol=0,label=None):
+        """Draw markers with error bars"""
+        self.subplot.set_yscale('linear')
+        self.subplot.set_xscale('linear')
+        
+        from plottable_interactor import PointInteractor
+        p = PointInteractor(self, self.subplot, zorder=3, id=name)
+        p.points(x, y, dx, dy, color, symbol, label)
+        
+        self.subplot.set_yscale(self.yscale)
+        self.subplot.set_xscale(self.xscale)
+
+    def interactive_curve(self,x,y,dy=None,name='',color=0,symbol=0,label=None):
+        """Draw markers with error bars"""
+        self.subplot.set_yscale('linear')
+        self.subplot.set_xscale('linear')
+        
+        from plottable_interactor import PointInteractor
+        p = PointInteractor(self, self.subplot, zorder=4, id=name)
+        p.curve(x, y, dy, color, symbol, label)
+        
+        self.subplot.set_yscale(self.yscale)
+        self.subplot.set_xscale(self.xscale)
+
+    def plottable_selected(self, id):
+        """
+            Called to register a plottable as selected
+        """
+        #TODO: check that it really is in the list of plottables
+        self.graph.selected_plottable = id
+
     def points(self,x,y,dx=None,dy=None,color=0,symbol=0,label=None):
         """Draw markers with error bars"""
Index: guitools/binder.py
===================================================================
--- guitools/binder.py	(revision 4972de2b5011084cbeaf6d68d05d0d2406b76a68)
+++ guitools/binder.py	(revision 4972de2b5011084cbeaf6d68d05d0d2406b76a68)
@@ -0,0 +1,384 @@
+"""
+Extension to MPL to support the binding of artists to key/mouse events.
+"""
+
+class Selection:
+    """
+    Store and compare selections.
+    """
+    # TODO: We need some way to check in prop matches, preferably
+    # TODO: without imposing structure on prop.
+        
+    artist=None
+    prop={}
+    def __init__(self,artist=None,prop={}):
+        self.artist,self.prop = artist,self.prop
+    def __eq__(self,other):
+        return self.artist is other.artist
+    def __ne__(self,other):
+        return self.artist is not other.artist
+    def __nonzero__(self):
+        return self.artist is not None
+
+class BindArtist:
+
+    # Track keyboard modifiers for events.
+    # TODO: Move keyboard modifier support into the backend.  We cannot
+    # TODO: properly support it from outside the windowing system since there
+    # TODO: is no way to recognized whether shift is held down when the mouse
+    # TODO: first clicks on the the application window.
+    control,shift,alt,meta=False,False,False,False
+
+    # Track doubleclick
+    dclick_threshhold = 0.25
+    _last_button, _last_time = None, 0
+    
+    # Mouse/keyboard events we can bind to
+    events= ['enter','leave','motion','click','dclick','drag','release',
+             'scroll','key','keyup']
+    # TODO: Need our own event structure
+    
+    def __init__(self,figure):
+        canvas = figure.canvas
+
+        # Link to keyboard/mouse
+        try:
+            self._connections = [
+                canvas.mpl_connect('motion_notify_event',self._onMotion),
+                canvas.mpl_connect('button_press_event',self._onClick),
+                canvas.mpl_connect('button_release_event',self._onRelease),
+                canvas.mpl_connect('key_press_event',self._onKey),
+                canvas.mpl_connect('key_release_event',self._onKeyRelease),
+                canvas.mpl_connect('scroll_event',self._onScroll)
+            ]
+        except:
+            print "bypassing scroll_event: wrong matplotlib version"
+            self._connections = [
+                canvas.mpl_connect('motion_notify_event',self._onMotion),
+                canvas.mpl_connect('button_press_event',self._onClick),
+                canvas.mpl_connect('button_release_event',self._onRelease),
+                canvas.mpl_connect('key_press_event',self._onKey),
+                canvas.mpl_connect('key_release_event',self._onKeyRelease),
+            ]
+            
+
+        # Turn off picker if it hasn't already been done
+        try:
+            canvas.mpl_disconnect(canvas.button_pick_id)
+            canvas.mpl_disconnect(canvas.scroll_pick_id)
+        except: 
+            pass
+        
+        self.canvas = canvas
+        self.figure = figure
+        self.clearall()
+        
+    def clear(self, *artists):
+        """
+        self.clear(h1,h2,...)
+            Remove connections for artists h1, h2, ...
+            
+        Use clearall() to reset all connections.
+        """
+
+        for h in artists:
+            for a in self.events:
+                if h in self._actions[a]: del self._actions[a][h]
+            if h in self._artists: self._artists.remove(h)
+        if self._current.artist in artists: self._current = Selection()
+        if self._hasclick.artist in artists: self._hasclick = Selection()
+        if self._haskey.artist in artists: self._haskey = Selection()
+        
+    def clearall(self):
+        """
+        Clear connections to all artists.
+        
+        Use clear(h1,h2,...) to reset specific artists.
+        """
+        # Don't monitor any actions
+        self._actions = {}
+        for action in self.events:
+            self._actions[action] = {}
+
+        # Need activity state
+        self._artists = []
+        self._current = Selection()
+        self._hasclick = Selection()
+        self._haskey = Selection()
+
+    def disconnect(self):
+        """
+        In case we need to disconnect from the canvas...
+        """
+        try: 
+            for cid in self._connections: self.canvas.mpl_disconnect(cid)
+        except: 
+            pass
+        self._connections = []
+
+    def __del__(self):
+        self.disconnect()
+
+    def __call__(self,trigger,artist,action):
+        """Register a callback for an artist to a particular trigger event.
+        
+        usage:
+            self.connect(eventname,artist,action)
+    
+        where:
+            eventname is a string
+            artist is the particular graph object to respond to the event
+            action(event,**kw) is called when the event is triggered
+
+        The action callback is associated with particular artists.
+        Different artists will have different kwargs.  See documentation
+        on the contains() method for each artist.  One common properties
+        are ind for the index of the item under the cursor, which is
+        returned by Line2D and by collections.
+
+        The following events are supported:
+            enter: mouse cursor moves into the artist or to a new index
+            leave: mouse cursor leaves the artist
+            click: mouse button pressed on the artist
+            drag: mouse button pressed on the artist and cursor moves
+            release: mouse button released for the artist
+            key: key pressed when mouse is on the artist
+            keyrelease: key released for the artist
+    
+        The event received by action has a number of attributes:
+            name is the event name which was triggered
+            artist is the object which triggered the event
+            x,y are the screen coordinates of the mouse
+            xdata,ydata are the graph coordinates of the mouse
+            button is the mouse button being pressed/released
+            key is the key being pressed/released
+            shift,control,alt,meta are flags which are true if the
+                corresponding key is pressed at the time of the event.
+            details is a dictionary of artist specific details, such as the 
+                id(s) of the point that were clicked.
+                
+        When receiving an event, first check the modifier state to be
+        sure it applies.  E.g., the callback for 'press' might be:
+            if event.button == 1 and event.shift: process Shift-click
+
+        TODO: Only receive events with the correct modifiers (e.g., S-click,
+        TODO:   or *-click for any modifiers).
+        TODO: Only receive button events for the correct button (e.g., click1
+        TODO:   release3, or dclick* for any button)
+        TODO: Support virtual artist, so that and artist can be flagged as
+        TODO:   having a tag list and receive the correct events
+        TODO: Support virtual events for binding to button-3 vs shift button-1
+        TODO:   without changing callback code
+        TODO: Attach multiple callbacks to the same event?
+        TODO: Clean up interaction with toolbar modes
+        TODO: push/pushclear/pop context so that binding changes for the duration
+        TODO:   e.g., to support ? context sensitive help
+        """
+        # Check that the trigger is valid
+        if trigger not in self._actions:
+            raise ValueError,"%s invalid --- valid triggers are %s"\
+                %(trigger,", ".join(self.events))
+
+        # Register the trigger callback
+        self._actions[trigger][artist]=action
+        #print "==> added",artist,[artist],"to",trigger,":",self._actions[trigger].keys()
+
+        # Maintain a list of all artists
+        if artist not in self._artists: 
+            self._artists.append(artist)
+
+    def trigger(self,actor,action,ev):
+        """
+        Trigger a particular event for the artist.  Fallback to axes,
+        to figure, and to 'all' if the event is not processed.
+        """
+        if action not in self.events:
+            raise ValueError, "Trigger expects "+", ".join(self.events)
+        
+        # Tag the event with modifiers
+        for mod in ('alt','control','shift','meta'):
+            setattr(ev,mod,getattr(self,mod))
+        setattr(ev,'artist',None)
+        setattr(ev,'action',action)
+        setattr(ev,'prop',{})
+        
+        # Fallback scheme.  If the event does not return false, pass to parent.
+        processed = False
+        artist,prop = actor.artist,actor.prop
+        if artist in self._actions[action]:
+            ev.artist,ev.prop = artist,prop
+            processed = self._actions[action][artist](ev)
+        if not processed and ev.inaxes in self._actions[action]:
+            ev.artist,ev.prop = ev.inaxes,{}
+            processed = self._actions[action][ev.inaxes](ev)
+        if not processed and self.figure in self._actions[action]:
+            ev.artist,ev.prop = self.figure,{}
+            processed = self._actions[action][self.figure](ev)
+        if not processed and 'all' in self._actions[action]:
+            ev.artist,ev.prop = None,{}
+            processed = self._actions[action]['all'](ev)
+        return processed
+
+    def _find_current(self, event):
+        """
+        Find the artist who will receive the event.  Only search
+        registered artists.  All others are invisible to the mouse.
+        """
+        # TODO: sort by zorder of axes then by zorder within axes
+        self._artists.sort(cmp=lambda x,y: cmp(y.zorder,x.zorder))
+        # print "search"," ".join([str(h) for h in self._artists])
+        found = Selection()
+        #print "searching in",self._artists
+        for artist in self._artists:
+            # TODO: should contains() return false if invisible?
+            if not artist.get_visible(): 
+                continue
+            # TODO: optimization - exclude artists not inaxes
+            try:
+                inside,prop = artist.contains(event)
+            except:
+                # Probably an old version of matplotlib
+                inside = False
+            if inside:
+                found.artist,found.prop = artist,prop
+                break
+        #print "found",found.artist
+        
+        # TODO: how to check if prop is equal?
+        if found != self._current:
+            self.trigger(self._current,'leave',event)
+            self.trigger(found,'enter',event)
+        self._current = found
+
+        return found
+        
+    def _onMotion(self,event):
+        """
+        Track enter/leave/motion through registered artists; all
+        other artists are invisible.
+        """
+        ## Can't kill double-click on motion since Windows produces
+        ## spurious motion events.
+        #self._last_button = None
+        
+        # Dibs on the motion event for the clicked artist
+        if self._hasclick:
+            # Make sure the x,y data use the coordinate system of the
+            # artist rather than the default axes coordinates.
+            
+            transform = self._hasclick.artist.get_transform()
+            #x,y = event.xdata,event.ydata
+            x,y = event.x,event.y
+            x,y = transform.inverse_xy_tup((x,y))
+            event.xdata,event.ydata = x,y
+            self.trigger(self._hasclick,'drag',event)
+        else:
+            found = self._find_current(event)
+            #print "found",found.artist
+            self.trigger(found,'motion',event)
+
+    def _onClick(self,event):
+        """
+        Process button click
+        """
+        import time
+        
+        # Check for double-click
+        event_time = time.time()
+        #print event_time,self._last_time,self.dclick_threshhold
+        #print (event_time > self._last_time + self.dclick_threshhold)
+        #print event.button,self._last_button
+        if (event.button != self._last_button) or \
+                (event_time > self._last_time + self.dclick_threshhold):
+            action = 'click'
+        else:
+            action = 'dclick'
+        self._last_button = event.button
+        self._last_time = event_time
+        
+        # If an artist is already dragging, feed any additional button
+        # presses to that artist.
+        # TODO: do we want to force a single button model on the user?
+        # TODO: that is, once a button is pressed, no other buttons
+        # TODO: can come through?  I think this belongs in canvas, not here.
+        if self._hasclick:
+            found = self._hasclick
+        else:
+            found = self._find_current(event)
+        #print "button %d pressed"%event.button
+        # Note: it seems like if "click" returns False then hasclick should
+        # not be set.  The problem is that there are two reasons it can
+        # return false: because there is no click action for this artist
+        # or because the click action returned false.  A related problem
+        # is that click actions will go to the canvas if there is no click
+        # action for the artist, even if the artist has a drag. I'll leave
+        # it to future maintainers to sort out this problem.  For now the
+        # recommendation is that users should define click if they have
+        # drag or release on the artist.
+        self.trigger(found,action,event)
+        self._hasclick = found
+
+    def _onDClick(self,event):
+        """
+        Process button double click
+        """
+        # If an artist is already dragging, feed any additional button
+        # presses to that artist.
+        # TODO: do we want to force a single button model on the user?
+        # TODO: that is, once a button is pressed, no other buttons
+        # TODO: can come through?  I think this belongs in canvas, not here.
+        if self._hasclick:
+            found = self._hasclick
+        else:
+            found = self._find_current(event)
+        self.trigger(found,'dclick',event)
+        self._hasclick = found
+
+    def _onRelease(self,event):
+        """
+        Process release release
+        """
+        self.trigger(self._hasclick,'release',event)
+        self._hasclick = Selection()
+            
+    def _onKey(self,event):
+        """
+        Process key click
+        """
+        # TODO: Do we really want keyboard focus separate from mouse focus?
+        # TODO: Do we need an explicit focus command for keyboard?
+        # TODO: Can we tab between items?
+        # TODO: How do unhandled events get propogated to axes, figure and
+        # TODO: finally to application?  Do we need to implement a full tags 
+        # TODO: architecture a la Tk?
+        # TODO: Do modifiers cause a grab?  Does the artist see the modifiers?
+        if event.key in ('alt','meta','control','shift'):
+            setattr(self,event.key,True)
+            return
+
+        if self._haskey:
+            found = self._haskey
+        else:
+            found = self._find_current(event)
+        self.trigger(found,'key',event)
+        self._haskey = found
+    
+    def _onKeyRelease(self,event):
+        """
+        Process key release
+        """
+        if event.key in ('alt','meta','control','shift'):
+            setattr(self,event.key,False)
+            return
+        
+        if self._haskey:
+            self.trigger(self._haskey,'keyup',event)
+        self._haskey = Selection()
+
+    def _onScroll(self,event):
+        """
+        Process scroll event
+        """
+        found = self._find_current(event)
+        self.trigger(found,'scroll',event)
+
Index: guitools/plottable_interactor.py
===================================================================
--- guitools/plottable_interactor.py	(revision 4972de2b5011084cbeaf6d68d05d0d2406b76a68)
+++ guitools/plottable_interactor.py	(revision 4972de2b5011084cbeaf6d68d05d0d2406b76a68)
@@ -0,0 +1,122 @@
+import plottables
+from BaseInteractor import _BaseInteractor
+
+class PointInteractor(_BaseInteractor):
+    
+    def __init__(self,base,axes,color='black', zorder=3, id=''):
+        _BaseInteractor.__init__(self, base, axes, color=color)
+        self.zorder = zorder
+        self.id = id
+        self.colorlist = ['b','g','r','c','m','y']
+        self.symbollist = ['o','x','^','v','<','>','+','s','d','D','h','H','p']
+        self.marker = None
+        self.marker2 = None
+        self._button_down = False
+        self._context_menu = False
+        self._dragged = False
+        
+    def _color(self,c):
+        """Return a particular colour"""
+        return self.colorlist[c%len(self.colorlist)]
+    
+    def _symbol(self,s):
+        """Return a particular symbol"""
+        return self.symbollist[s%len(self.symbollist)]
+
+
+    def points(self,x,y,dx=None,dy=None,color=0,symbol=0,label=None):
+        
+        if not self.marker==None:
+            self.base.connect.clear([self.marker])
+        
+        self.color = self._color(color)
+        
+        # Convert tuple (lo,hi) to array [(x-lo),(hi-x)]
+        if dx != None and type(dx) == type(()):
+            dx = nx.vstack((x-dx[0],dx[1]-x)).transpose()
+        if dy != None and type(dy) == type(()):
+            dy = nx.vstack((y-dy[0],dy[1]-y)).transpose()
+        
+        if dx==None and dy==None:
+            self.marker = self.axes.plot(x,y,color=self.color,
+                                   marker=self._symbol(symbol),linestyle='',label=label,
+                                   zorder=self.zorder)[0]
+        else:
+            self.marker = self.axes.errorbar(x, y, yerr=dy, xerr=None,
+             ecolor=self.color, 
+             color=self.color,
+             capsize=2,linestyle='', barsabove=False,
+             #mec=self.color, mfc=self.color,
+             marker=self._symbol(symbol),
+             lolims=False, uplims=False,
+             xlolims=False, xuplims=False,label=label,
+             zorder=self.zorder)[0]
+            
+        self.connect_markers([self.marker])
+        self.update()
+        
+    def curve(self, x, y, dy=None, color=0, symbol=0, label=None):
+        
+        if not self.marker==None:
+            self.base.connect.clear([self.marker])
+        
+        self.color = self._color(color)
+        
+        self.marker = self.axes.plot(x,y,color=self.color,marker='',linestyle='-',label=label)[0]
+            
+        self.connect_markers([self.marker])
+        self.update()
+        
+    def connect_markers(self,markers):
+        """
+        Connect markers to callbacks
+        """
+        for h in markers:
+            connect = self.base.connect
+            connect('enter', h, self._on_enter)
+            connect('leave', h, self._on_leave)
+            connect('click', h, self._on_click)
+            connect('release', h, self._on_release)
+            #connect('drag', h, self._on_drag)
+            connect('key', h, self.onKey)
+
+    def clear(self):
+        print "plottable_interactor.clear()"
+        
+    def _on_click(self, evt):
+        self._context_menu = False
+        
+    def _on_release(self, evt): 
+        # Check to see whether we are about to pop 
+        # the context menu up
+        if evt.button==3:
+            self._context_menu = True
+        
+    def _on_enter(self, evt):
+        self.base.plottable_selected(self.id)
+        evt.artist.set_color('y')
+        if hasattr(evt.artist, "set_facecolor"):
+            evt.artist.set_facecolor('y')
+        if hasattr(evt.artist, "set_edgecolor"):
+            evt.artist.set_edgecolor('y')
+        self.axes.figure.canvas.draw_idle()
+        
+    def _on_leave(self, evt):
+        if self._context_menu==False:
+            self.base.plottable_selected(None)
+            evt.artist.set_color(self.color)
+            if hasattr(evt.artist, "set_facecolor"):
+                evt.artist.set_facecolor(self.color)
+            if hasattr(evt.artist, "set_edgecolor"):            
+                evt.artist.set_edgecolor(self.color)
+            self.axes.figure.canvas.draw_idle()
+            
+        self._context_menu = False
+    
+    def update(self):
+        """
+            Update
+        """
+        print "update"
+     
+     
Index: guitools/plottables.py
===================================================================
--- guitools/plottables.py	(revision 052a66bcbb3220025ecb5afe3a9b2afadcc6f3c2)
+++ guitools/plottables.py	(revision 4972de2b5011084cbeaf6d68d05d0d2406b76a68)
@@ -236,4 +236,14 @@
         return labels
     
+    def get_plottable(self, name):
+        """
+            Return the plottable with the given
+            name if it exists. Otherwise return None
+        """
+        for item in self.plottables:
+            if item.name==name:
+                return item
+        return None
+    
     def returnPlottable(self):
         """
@@ -246,4 +256,6 @@
     def render(self,plot):
         """Redraw the graph"""
+        plot.connect.clearall()
+        
         plot.clear()
         plot.properties(self.prop)
@@ -258,4 +270,7 @@
         self.reset()
         self.set(**kw)
+        
+        # Name of selected plottable, if any
+        self.selected_plottable = None
 
 
@@ -352,4 +367,7 @@
     short_name = None
     
+    # Fancy name
+    name = None
+    
     # Data
     x  = None
@@ -360,4 +378,7 @@
     # Parameter to allow a plot to be part of the list without being displayed
     hidden = False
+    
+    # Flag to set whether a plottable has an interactor or not
+    interactive = True
 
     def __setattr__(self, name, value):
@@ -801,6 +822,13 @@
         
     def render(self,plot,**kw):
-        plot.points(self.view.x,self.view.y,dx=self.view.dx,dy=self.view.dy,**kw)
-     
+        """
+            Renders the plottable on the graph
+        """
+        if self.interactive==True:
+            plot.interactive_points(self.view.x,self.view.y,
+                                    dx=self.view.dx,dy=self.view.dy,
+                                    name=self.name, **kw)            
+        else:
+            plot.points(self.view.x,self.view.y,dx=self.view.dx,dy=self.view.dy,**kw)
    
     def changed(self):
@@ -835,5 +863,11 @@
         
     def render(self,plot,**kw):
-        plot.curve(self.view.x,self.view.y,dy=self.view.dy,**kw)
+        if self.interactive==True:
+            plot.interactive_curve(self.view.x,self.view.y,
+                                   dy=self.view.dy,
+                                   name=self.name,**kw)
+        else:
+            plot.curve(self.view.x,self.view.y,dy=self.view.dy,**kw)
+            
 
     def changed(self):
