Revision: 5782
http://matplotlib.svn.sourceforge.net/matplotlib/?rev=5782&view=rev
Author: dmkaplan
Date: 2008-07-17 19:20:32 +0000 (Thu, 17 Jul 2008)
Log Message:
-----------
Adding more functionality for interacting with figure windows through
mouse clicks and keyboard clicks. This includes adding a
waitforbuttonpress function, as well as manual contour label location
selection for clabel. The underlying Blocking* classes that drive
this interaction have been moved into a separate file
"lib/matplotlib/blocking_input.py". Also added a changelog entry.
Modified Paths:
--------------
trunk/matplotlib/CHANGELOG
trunk/matplotlib/lib/matplotlib/contour.py
trunk/matplotlib/lib/matplotlib/figure.py
trunk/matplotlib/lib/matplotlib/pyplot.py
Added Paths:
-----------
trunk/matplotlib/lib/matplotlib/blocking_input.py
Modified: trunk/matplotlib/CHANGELOG
===================================================================
--- trunk/matplotlib/CHANGELOG 2008-07-17 19:15:58 UTC (rev 5781)
+++ trunk/matplotlib/CHANGELOG 2008-07-17 19:20:32 UTC (rev 5782)
@@ -1,3 +1,6 @@
+2008-07-17 Added ability to manually select contour label locations.
+ Also added a waitforbuttonpress function. - DMK
+
2008-07-17 Fix bug with NaNs at end of path (thanks, Andrew Straw for
the report) - MGD
Added: trunk/matplotlib/lib/matplotlib/blocking_input.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/blocking_input.py
(rev 0)
+++ trunk/matplotlib/lib/matplotlib/blocking_input.py 2008-07-17 19:20:32 UTC
(rev 5782)
@@ -0,0 +1,540 @@
+"""
+This provides several classes used for blocking interaction with figure
windows:
+
+:class:`BlockingInput`
+ creates a callable object to retrieve events in a blocking way for
interactive sessions
+
+:class:`BlockingKeyMouseInput`
+ creates a callable object to retrieve key or mouse clicks in a blocking
way for interactive sessions.
+ Note: Subclass of BlockingInput. Used by waitforbuttonpress
+
+:class:`BlockingMouseInput`
+ creates a callable object to retrieve mouse clicks in a blocking way for
interactive sessions.
+ Note: Subclass of BlockingInput. Used by ginput
+
+:class:`BlockingContourLabeler`
+ creates a callable object to retrieve mouse clicks in a blocking way that
will then be used to place labels on a ContourSet
+ Note: Subclass of BlockingMouseInput. Used by clabel
+"""
+
+import time
+import numpy as np
+import matplotlib.path as path
+
+class BlockingInput(object):
+ """
+ Class that creates a callable object to retrieve events in a
+ blocking way.
+ """
+ def __init__(self, fig, eventslist=()):
+ self.fig = fig
+ assert isinstance(eventslist, tuple), "Requires a tuple of event name
strings"
+ self.eventslist = eventslist
+
+ def on_event(self, event):
+ """
+ Event handler that will be passed to the current figure to
+ retrieve events.
+ """
+ # Add a new event to list - using a separate function is
+ # overkill for the base class, but this is consistent with
+ # subclasses
+ self.add_event(event)
+
+ if self.verbose:
+ print "Event %i" % len(self.events)
+
+ # This will extract info from events
+ self.post_event()
+
+ # Check if we have enough events already
+ if len(self.events) >= self.n and self.n > 0:
+ self.done = True
+
+ def post_event(self):
+ """For baseclass, do nothing but collect events"""
+ pass
+
+ def cleanup(self):
+ """Disconnect all callbacks"""
+ for cb in self.callbacks:
+ self.fig.canvas.mpl_disconnect(cb)
+
+ self.callbacks=[]
+
+ def add_event(self,event):
+ """For base class, this just appends an event to events."""
+ self.events.append(event)
+
+ def pop_event(self,index=-1):
+ """
+ This removes an event from the event list. Defaults to
+ removing last event, but an index can be supplied. Note that
+ this does not check that there are events, much like the
+ normal pop method. If not events exist, this will throw an
+ exception.
+ """
+ self.events.pop(index)
+
+ def pop(self,index=-1):
+ self.pop_event(index)
+ pop.__doc__=pop_event.__doc__
+
+ def __call__(self, n=1, timeout=30, verbose=False ):
+ """
+ Blocking call to retrieve n events
+ """
+
+ assert isinstance(n, int), "Requires an integer argument"
+ self.n = n
+
+ self.events = []
+ self.done = False
+ self.verbose = verbose
+ self.callbacks = []
+
+ # Ensure that the figure is shown
+ self.fig.show()
+
+ # connect the events to the on_event function call
+ for n in self.eventslist:
+ self.callbacks.append( self.fig.canvas.mpl_connect(n,
self.on_event) )
+
+ try:
+ # wait for n clicks
+ counter = 0
+ while not self.done:
+ self.fig.canvas.flush_events()
+ time.sleep(0.01)
+
+ # check for a timeout
+ counter += 1
+ if timeout > 0 and counter > timeout/0.01:
+ print "Timeout reached";
+ break;
+ finally: # Activated on exception like ctrl-c
+ self.cleanup()
+
+ # Disconnect the callbacks
+ self.cleanup()
+
+ # Return the events in this case
+ return self.events
+
+class BlockingMouseInput(BlockingInput):
+ """
+ Class that creates a callable object to retrieve mouse clicks in a
+ blocking way.
+ """
+ def __init__(self, fig):
+ BlockingInput.__init__(self, fig=fig,
+ eventslist=('button_press_event',) )
+
+ def post_event(self):
+ """
+ This will be called to process events
+ """
+ assert len(self.events)>0, "No events yet"
+
+ event = self.events[-1]
+ button = event.button
+
+ # Using additional methods for each button is a bit overkill
+ # for this class, but it allows for easy overloading. Also,
+ # this would make it easy to attach other type of non-mouse
+ # events to these "mouse" actions. For example, the matlab
+ # version of ginput also allows you to add points with
+ # keyboard clicks. This could easily be added to this class
+ # with relatively minor modification to post_event and
+ # __init__.
+ if button == 3:
+ self.button3(event)
+ elif button == 2:
+ self.button2(event)
+ else:
+ self.button1(event)
+
+ def button1( self, event ):
+ """
+ Will be called for any event involving a button other than
+ button 2 or 3. This will add a click if it is inside axes.
+ """
+ if event.inaxes:
+ self.add_click(event)
+ else: # If not a valid click, remove from event list
+ BlockingInput.pop(self)
+
+ def button2( self, event ):
+ """
+ Will be called for any event involving button 2.
+ Button 2 ends blocking input.
+ """
+
+ # Remove last event just for cleanliness
+ BlockingInput.pop(self)
+
+ # This will exit even if not in infinite mode. This is
+ # consistent with matlab and sometimes quite useful, but will
+ # require the user to test how many points were actually
+ # returned before using data.
+ self.done = True
+
+ def button3( self, event ):
+ """
+ Will be called for any event involving button 3.
+ Button 3 removes the last click.
+ """
+ # Remove this last event
+ BlockingInput.pop(self)
+
+ # Now remove any existing clicks if possible
+ if len(self.events)>0:
+ self.pop()
+
+ def add_click(self,event):
+ """
+ This add the coordinates of an event to the list of clicks
+ """
+ self.clicks.append((event.xdata,event.ydata))
+ if self.verbose:
+ print "input %i: %f,%f" % (len(self.clicks),
+ event.xdata, event.ydata)
+
+ # If desired plot up click
+ if self.show_clicks:
+ self.marks.extend(
+ event.inaxes.plot([event.xdata,], [event.ydata,], 'r+') )
+ self.fig.canvas.draw()
+
+ def pop_click(self,index=-1):
+ """
+ This removes a click from the list of clicks. Defaults to
+ removing the last click.
+ """
+ self.clicks.pop(index)
+
+ if self.show_clicks:
+ mark = self.marks.pop(index)
+ mark.remove()
+ self.fig.canvas.draw()
+
+ def pop(self,index=-1):
+ """
+ This removes a click and the associated event from the object.
+ Defaults to removing the last click, but any index can be
+ supplied.
+ """
+ self.pop_click(index)
+ BlockingInput.pop(self,index)
+
+ def cleanup(self):
+ # clean the figure
+ if self.show_clicks:
+ for mark in self.marks:
+ mark.remove()
+ self.marks = []
+ self.fig.canvas.draw()
+
+ # Call base class to remove callbacks
+ BlockingInput.cleanup(self)
+
+ def __call__(self, n=1, timeout=30, verbose=False, show_clicks=True):
+ """
+ Blocking call to retrieve n coordinate pairs through mouse
+ clicks.
+ """
+ self.show_clicks = show_clicks
+ self.clicks = []
+ self.marks = []
+ BlockingInput.__call__(self,n=n,timeout=timeout,verbose=verbose)
+
+ return self.clicks
+
+class BlockingContourLabeler( BlockingMouseInput ):
+ """
+ Class that creates a callable object that uses mouse clicks on a
+ figure window to place contour labels.
+ """
+ def __init__(self,cs):
+ self.cs = cs
+ BlockingMouseInput.__init__(self, fig=cs.ax.figure )
+
+ def button1(self,event):
+ """
+ This will be called if an event involving a button other than
+ 2 or 3 occcurs. This will add a label to a contour.
+ """
+ if event.inaxes == self.cs.ax:
+ conmin,segmin,imin,xmin,ymin = self.cs.find_nearest_contour(
+ event.x, event.y)[:5]
+
+ paths = self.cs.collections[conmin].get_paths()
+ lc = paths[segmin].vertices
+
+ # Figure out label rotation. This is very cludgy.
+ # Ideally, there would be one method in ContourLabeler
+ # that would figure out the best rotation for a label at a
+ # point, but the way automatic label rotation is done is
+ # quite mysterious to me and doesn't seem easy to
+ # generalize to non-automatic label placement. The method
+ # used below is not very robust! It basically looks one
+ # point before and one point after label location on
+ # contour and takes mean of angles of two vectors formed.
+ # This produces "acceptable" results, but not nearly as
+ # nice as automatic method.
+ ll = lc[max(0,imin-1):imin+2] # Get points around point
+ dd = np.diff(ll,axis=0)
+ rotation = np.mean( np.arctan2(dd[:,1], dd[:,0]) ) * 180 / np.pi
+ if rotation > 90:
+ rotation = rotation -180
+ if rotation < -90:
+ rotation = 180 + rotation
+
+ self.cs.add_label(xmin,ymin,rotation,conmin)
+
+ if self.inline:
+ # Get label width for breaking contours
+ lw = self.cs.get_label_width(self.cs.label_levels[conmin],
+ self.cs.fmt,
+ self.cs.fslist[conmin])
+ # Break contour
+ new=self.cs.break_linecontour(lc,rotation,lw,imin)
+ if len(new[0]):
+ paths[segmin] = path.Path(new[0])
+ if len(new[1]):
+ paths.extend([path.Path(new[1])])
+
+ self.fig.canvas.draw()
+ else: # Remove event if not valid
+ BlockingInput.pop(self)
+
+ def button3(self,event):
+ """
+ This will be called if button 3 is clicked. This will remove
+ a label if not in inline mode. Unfortunately, if one is doing
+ inline labels, then there is currently no way to fix the
+ broken contour - once humpty-dumpty is broken, he can't be put
+ back together. In inline mode, this does nothing.
+ """
+ if self.inline:
+ pass
+ else:
+ self.cs.pop_label()
+ self.cs.ax.figure.canvas.draw()
+
+ def __call__(self,inline,n=-1,timeout=-1):
+ self.inline=inline
+ BlockingMouseInput.__call__(self,n=n,timeout=timeout,verbose=False,
+ show_clicks=False)
+
+class BlockingKeyMouseInput(BlockingInput):
+ """
+ Class that creates a callable object to retrieve a single mouse or
+ keyboard click
+ """
+ def __init__(self, fig):
+ BlockingInput.__init__(self, fig=fig,
eventslist=('button_press_event','key_press_event') )
+
+ def post_event(self):
+ """
+ Determines if it is a key event
+ """
+ assert len(self.events)>0, "No events yet"
+
+ self.keyormouse = self.events[-1].name == 'key_press_event'
+
+ def __call__(self, timeout=30, verbose=False):
+ """
+ Blocking call to retrieve a single mouse or key click
+ Returns True if key click, False if mouse, or None if timeout
+ """
+ self.keyormouse = None
+ BlockingInput.__call__(self,n=1,timeout=timeout,verbose=verbose)
+
+ return self.keyormouse
+
+"""
+This provides several classes used for interaction with figure windows:
+
+:class:`BlockingInput`
+ creates a callable object to retrieve events in a blocking way for
interactive sessions
+
+:class:`BlockingKeyMouseInput`
+ creates a callable object to retrieve key or mouse clicks in a blocking
way for interactive sessions.
+ Note: Subclass of BlockingInput. Used by waitforbuttonpress
+
+:class:`BlockingMouseInput`
+ creates a callable object to retrieve mouse clicks in a blocking way for
interactive sessions.
+ Note: Subclass of BlockingInput. Used by ginput
+"""
+
+import time
+
+class BlockingInput(object):
+ """
+ Class that creates a callable object to retrieve events in a
+ blocking way.
+ """
+ def __init__(self, fig, eventslist=()):
+ self.fig = fig
+ assert isinstance(eventslist, tuple), \
+ "Requires a tuple of event name strings"
+ self.eventslist = eventslist
+
+ def on_event(self, event):
+ """
+ Event handler that will be passed to the current figure to
+ retrieve events.
+ """
+ self.events.append(event)
+
+ if self.verbose:
+ print "Event %i" % len(self.events)
+
+ # This will extract info from events
+ self.post_event()
+
+ if len(self.events) >= self.n and self.n > 0:
+ self.done = True
+
+ def post_event(self):
+ """For baseclass, do nothing but collect events"""
+ pass
+
+ def cleanup(self):
+ """Remove callbacks"""
+ for cb in self.callbacks:
+ self.fig.canvas.mpl_disconnect(cb)
+
+ self.callbacks=[]
+
+ def __call__(self, n=1, timeout=30, verbose=False ):
+ """
+ Blocking call to retrieve n events
+ """
+
+ assert isinstance(n, int), "Requires an integer argument"
+ self.n = n
+
+ self.events = []
+ self.done = False
+ self.verbose = verbose
+ self.callbacks = []
+
+ # Ensure that the figure is shown
+ self.fig.show()
+
+ # connect the events to the on_event function call
+ for n in self.eventslist:
+ self.callbacks.append( self.fig.canvas.mpl_connect(n,
self.on_event) )
+
+ try:
+ # wait for n clicks
+ counter = 0
+ while not self.done:
+ self.fig.canvas.flush_events()
+ time.sleep(0.01)
+
+ # check for a timeout
+ counter += 1
+ if timeout > 0 and counter > timeout/0.01:
+ print "Timeout reached";
+ break;
+ finally: # Activated on exception like ctrl-c
+ self.cleanup()
+
+ # Disconnect the callbacks
+ self.cleanup()
+
+ # Return the events in this case
+ return self.events
+
+class BlockingMouseInput(BlockingInput):
+ """
+ Class that creates a callable object to retrieve mouse clicks in a
+ blocking way.
+ """
+ def __init__(self, fig):
+ BlockingInput.__init__(self, fig=fig,
eventslist=('button_press_event',) )
+
+ def post_event(self):
+ """
+ This will be called to process events
+ """
+ assert len(self.events)>0, "No events yet"
+
+ event = self.events[-1]
+
+ if event.button == 3:
+ # If it's a right click, pop the last coordinates.
+ if len(self.clicks) > 0:
+ self.clicks.pop()
+ del self.events[-2:] # Remove button=3 event and previous event
+
+ if self.show_clicks:
+ mark = self.marks.pop()
+ mark.remove()
+ self.fig.canvas.draw()
+ elif event.button == 2 and self.n < 0:
+ # If it's a middle click, and we are in infinite mode, finish
+ self.done = True
+ elif event.inaxes:
+ # If it's a valid click, append the coordinates to the list
+ self.clicks.append((event.xdata, event.ydata))
+ if self.verbose:
+ print "input %i: %f,%f" % (len(self.clicks),
+ event.xdata, event.ydata)
+ if self.show_clicks:
+ self.marks.extend(
+ event.inaxes.plot([event.xdata,], [event.ydata,], 'r+') )
+ self.fig.canvas.draw()
+
+ def cleanup(self):
+ # clean the figure
+ if self.show_clicks:
+ for mark in self.marks:
+ mark.remove()
+ self.marks = []
+ self.fig.canvas.draw()
+
+ # Call base class to remove callbacks
+ BlockingInput.cleanup(self)
+
+ def __call__(self, n=1, timeout=30, verbose=False, show_clicks=True):
+ """
+ Blocking call to retrieve n coordinate pairs through mouse
+ clicks.
+ """
+ self.show_clicks = show_clicks
+ self.clicks = []
+ self.marks = []
+ BlockingInput.__call__(self,n=n,timeout=timeout,verbose=verbose)
+
+ return self.clicks
+
+class BlockingKeyMouseInput(BlockingInput):
+ """
+ Class that creates a callable object to retrieve a single mouse or
+ keyboard click
+ """
+ def __init__(self, fig):
+ BlockingInput.__init__(self, fig=fig,
eventslist=('button_press_event','key_press_event') )
+
+ def post_event(self):
+ """
+ Determines if it is a key event
+ """
+ assert len(self.events)>0, "No events yet"
+
+ self.keyormouse = self.events[-1].name == 'key_press_event'
+
+ def __call__(self, timeout=30, verbose=False):
+ """
+ Blocking call to retrieve a single mouse or key click
+ Returns True if key click, False if mouse, or None if timeout
+ """
+ self.keyormouse = None
+ BlockingInput.__call__(self,n=1,timeout=timeout,verbose=verbose)
+
+ return self.keyormouse
+
Modified: trunk/matplotlib/lib/matplotlib/contour.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/contour.py 2008-07-17 19:15:58 UTC (rev
5781)
+++ trunk/matplotlib/lib/matplotlib/contour.py 2008-07-17 19:20:32 UTC (rev
5782)
@@ -17,6 +17,9 @@
import matplotlib.text as text
import matplotlib.cbook as cbook
+# Import needed for adding manual selection capability to clabel
+from matplotlib.blocking_input import BlockingContourLabeler
+
# We can't use a single line collection for contour because a line
# collection can have only a single line style, and we want to be able to have
# dashed negative contours, for example, and solid positive contours.
@@ -69,6 +72,13 @@
*fmt*:
a format string for the label. Default is '%1.3f'
+ *manual*:
+ if *True*, contour labels will be placed manually using
+ mouse clicks. Click the first button near a contour to
+ add a label, click the second button (or potentially both
+ mouse buttons at once) to finish adding labels. The third
+ button can be used to remove the last label added, but
+ only if labels are not inline.
"""
fontsize = kwargs.get('fontsize', None)
@@ -76,8 +86,9 @@
self.fmt = kwargs.get('fmt', '%1.3f')
_colors = kwargs.get('colors', None)
+ # Detect if manual selection is desired and remove from argument list
+ self.manual_select=kwargs.get('manual',False)
-
if len(args) == 0:
levels = self.levels
indices = range(len(self.levels))
@@ -126,10 +137,16 @@
#self.cl_cvalues = [] # same
self.cl_xy = []
- self.labels(inline)
+ if self.manual_select:
+ print 'Select label locations manually using first mouse button.'
+ print 'End manual selection with second mouse button.'
+ if not inline:
+ print 'Remove last label by clicking third mouse button.'
- for label in self.cl:
- self.ax.add_artist(label)
+ blocking_contour_labeler = BlockingContourLabeler(self)
+ blocking_contour_labeler(inline)
+ else:
+ self.labels(inline)
self.label_list = cbook.silent_list('text.Text', self.cl)
return self.label_list
@@ -335,19 +352,85 @@
return x,y, rotation, dind
+ def add_label(self,x,y,rotation,icon):
+ dx,dy = self.ax.transData.inverted().transform_point((x,y))
+ t = text.Text(dx, dy, rotation = rotation,
+ horizontalalignment='center',
+ verticalalignment='center')
+
+ color = self.label_mappable.to_rgba(self.label_cvalues[icon],
+ alpha=self.alpha)
+
+ _text = self.get_text(self.label_levels[icon],self.fmt)
+ self.set_label_props(t, _text, color)
+ self.cl.append(t)
+ self.cl_cvalues.append(self.label_cvalues[icon])
+
+ # Add label to plot here - useful for manual mode label selection
+ self.ax.add_artist(t)
+
+ def pop_label(self,index=-1):
+ '''Defaults to removing last label, but any index can be supplied'''
+ self.cl_cvalues.pop(index)
+ t = self.cl.pop(index)
+ t.remove()
+
+ def find_nearest_contour( self, x, y, pixel=True ):
+ """
+ Finds contour that is closest to a point. Defaults to
+ measuring distance in pixels (screen space - useful for manual
+ contour labeling), but this can be controlled via a keyword
+ argument.
+
+ Returns a tuple containing the contour, segment, index of
+ segment, x & y of segment point and distance to minimum point.
+ """
+
+ # This function uses a method that is probably quite
+ # inefficient based on converting each contour segment to
+ # pixel coordinates and then comparing the given point to
+ # those coordinates for each contour. This will probably be
+ # quite slow for complex contours, but for normal use it works
+ # sufficiently well that the time is not noticeable.
+ # Nonetheless, improvements could probably be made.
+
+ dmin = 1e10
+ conmin = None
+ segmin = None
+ xmin = None
+ ymin = None
+
+ for icon in self.label_indices:
+ con = self.collections[icon]
+ paths = con.get_paths()
+ for segNum, linepath in enumerate(paths):
+ lc = linepath.vertices
+
+ # transfer all data points to screen coordinates if desired
+ if pixel:
+ lc = self.ax.transData.transform(lc)
+
+ ds = (lc[:,0]-x)**2 + (lc[:,1]-y)**2
+ d = min( ds )
+ if d < dmin:
+ dmin = d
+ conmin = icon
+ segmin = segNum
+ imin = mpl.mlab.find( ds == d )[0]
+ xmin = lc[imin,0]
+ ymin = lc[imin,1]
+
+ return (conmin,segmin,imin,xmin,ymin,dmin)
+
def labels(self, inline):
levels = self.label_levels
fslist = self.fslist
trans = self.ax.transData
- _colors = self.label_mappable.to_rgba(self.label_cvalues,
- alpha=self.alpha)
- fmt = self.fmt
- for icon, lev, color, cvalue, fsize in zip(self.label_indices,
- self.label_levels,
- _colors,
- self.label_cvalues, fslist):
+
+ for icon, lev, fsize in zip(self.label_indices,
+ self.label_levels, fslist):
con = self.collections[icon]
- lw = self.get_label_width(lev, fmt, fsize)
+ lw = self.get_label_width(lev, self.fmt, fsize)
additions = []
paths = con.get_paths()
for segNum, linepath in enumerate(paths):
@@ -362,16 +445,8 @@
slc = trans.transform(linecontour)
if self.print_label(slc,lw):
x,y, rotation, ind = self.locate_label(slc, lw)
- # transfer the location of the label back to
- # data coordinates
- dx,dy = trans.inverted().transform_point((x,y))
- t = text.Text(dx, dy, rotation = rotation,
- horizontalalignment='center',
- verticalalignment='center')
- _text = self.get_text(lev,fmt)
- self.set_label_props(t, _text, color)
- self.cl.append(t)
- self.cl_cvalues.append(cvalue)
+ self.add_label(x,y,rotation,icon)
+
if inline:
new = self.break_linecontour(linecontour, rotation,
lw, ind)
if len(new[0]):
Modified: trunk/matplotlib/lib/matplotlib/figure.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/figure.py 2008-07-17 19:15:58 UTC (rev
5781)
+++ trunk/matplotlib/lib/matplotlib/figure.py 2008-07-17 19:20:32 UTC (rev
5782)
@@ -6,9 +6,6 @@
:class:`SubplotParams`
control the default spacing of the subplots
-:class:`BlockingMouseInput`
- creates a callable object to retrieve mouse clicks in a blocking way for
interactive sessions
-
:class:`Figure`
top level container for all plot elements
@@ -32,6 +29,7 @@
from transforms import Affine2D, Bbox, BboxTransformTo, TransformedBbox
from projections import projection_factory, get_projection_names, \
get_projection_class
+from matplotlib.blocking_input import BlockingMouseInput, BlockingKeyMouseInput
import matplotlib.cbook as cbook
@@ -117,87 +115,6 @@
setattr(self, s, val)
-
-class BlockingMouseInput(object):
- """
- Class that creates a callable object to retrieve mouse clicks in a
- blocking way.
- """
- def __init__(self, fig):
- self.fig = fig
-
-
- def on_click(self, event):
- """
- Event handler that will be passed to the current figure to
- retrieve clicks.
- """
- if event.button == 3:
- # If it's a right click, pop the last coordinates.
- if len(self.clicks) > 0:
- self.clicks.pop()
- if self.show_clicks:
- mark = self.marks.pop()
- mark.remove()
- self.fig.canvas.draw()
- elif event.button == 2 and self.n < 0:
- # If it's a middle click, and we are in infinite mode, finish
- self.done = True
- elif event.inaxes:
- # If it's a valid click, append the coordinates to the list
- self.clicks.append((event.xdata, event.ydata))
- if self.verbose:
- print "input %i: %f,%f" % (len(self.clicks),
- event.xdata, event.ydata)
- if self.show_clicks:
- self.marks.extend(
- event.inaxes.plot([event.xdata,], [event.ydata,], 'r+') )
- self.fig.canvas.draw()
- if self.n > 0 and len(self.clicks) >= self.n:
- self.done = True
-
-
- def __call__(self, n=1, timeout=30, verbose=False, show_clicks=True):
- """
- Blocking call to retrieve n coordinate pairs through mouse
- clicks.
- """
- self.verbose = verbose
- self.done = False
- self.clicks = []
- self.show_clicks = True
- self.marks = []
-
- assert isinstance(n, int), "Requires an integer argument"
- self.n = n
-
- # Ensure that the figure is shown
- self.fig.show()
- # connect the click events to the on_click function call
- self.callback = self.fig.canvas.mpl_connect('button_press_event',
- self.on_click)
- # wait for n clicks
- counter = 0
- while not self.done:
- self.fig.canvas.flush_events()
- time.sleep(0.01)
-
- # check for a timeout
- counter += 1
- if timeout > 0 and counter > timeout/0.01:
- print "ginput timeout";
- break;
-
- # Disconnect the event, clean the figure, and return what we have
- self.fig.canvas.mpl_disconnect(self.callback)
- self.callback = None
- if self.show_clicks:
- for mark in self.marks:
- mark.remove()
- self.fig.canvas.draw()
- return self.clicks
-
-
class Figure(Artist):
"""
@@ -1121,10 +1038,27 @@
blocking_mouse_input = BlockingMouseInput(self)
return blocking_mouse_input(n=n, timeout=timeout,
- verbose=verbose, show_clicks=True)
+ verbose=verbose,
show_clicks=show_clicks)
+ def waitforbuttonpress(self, timeout=-1):
+ """
+ call signature::
+ waitforbuttonpress(self, timeout=-1)
+ Blocking call to interact with the figure.
+
+ This will return True is a key was pressed, False if a mouse
+ button was pressed and None if *timeout* was reached without
+ either being pressed.
+
+ If *timeout* is negative, does not timeout.
+ """
+
+ blocking_input = BlockingKeyMouseInput(self)
+ return blocking_input(timeout=timeout)
+
+
def figaspect(arg):
"""
Create a figure with specified aspect ratio. If *arg* is a number,
Modified: trunk/matplotlib/lib/matplotlib/pyplot.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/pyplot.py 2008-07-17 19:15:58 UTC (rev
5781)
+++ trunk/matplotlib/lib/matplotlib/pyplot.py 2008-07-17 19:20:32 UTC (rev
5782)
@@ -320,7 +320,21 @@
if Figure.ginput.__doc__ is not None:
ginput.__doc__ = dedent(Figure.ginput.__doc__)
+def waitforbuttonpress(*args, **kwargs):
+ """
+ Blocking call to interact with the figure.
+ This will wait for *n* key or mouse clicks from the user and
+ return a list containing True's for keyboard clicks and False's
+ for mouse clicks.
+
+ If *timeout* is negative, does not timeout.
+ """
+ return gcf().waitforbuttonpress(*args, **kwargs)
+if Figure.waitforbuttonpress.__doc__ is not None:
+ waitforbuttonpress.__doc__ = dedent(Figure.waitforbuttonpress.__doc__)
+
+
# Putting things in figures
def figtext(*args, **kwargs):
This was sent by the SourceForge.net collaborative development platform, the
world's largest Open Source development site.
-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
Matplotlib-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins