Hi,
Attached is a new version of the patch that includes ginput,
waitforbuttonpress and clabel changes. It is already quite functional,
but there are a couple of issues that need improving that I would like
to solicit comments on. I explain below after detailing what I have
done.
I decided to use a slightly different approach for adding manual label
placement ability to clabel. I created a class BlockingContourLabeler
that inherits from BlockingMouseInput. This code is in
blocking_input.py. This class is called by clabel when manual mode is
selected and does the labeling by modifying the passed ContourSet object
using ContourSet methods. This avoids clouding the ContourSet
namespace with BlockingMouseInput variables and methods. Along the way,
I generalized BlockingMouseInput so that it makes it very easy to
overload methods to create the class for clabel.
In general, I think this approach is quite robust and efficient. Label
placement and coloring work perfectly. Label rotation and inlining are
usable, but not nearly as robust as the automatic versions. The main
reason for this is that I can't get my head around how the automatic
code (locate_labels and get_label_coords) works and this is preventing
me from abstracting that code into something that I can use for
non-automatic labeling. It looks to me like it breaks each contour into
n chunks, where n is the width of the label in pixels, and then places
the label on one of those pieces using the starting and ending points of
the chunk to define the label orientation. But this doesn't make much
sense to me, so I am hesitant to edit it. Somehow it works and
generally looks quite good. Perhaps someone who understands that code
can enlighten me.
In an ideal world, there would be a general ContourLabeler method that
would take the label location and spit out the appropriate rotation, no
matter where that label is. If I were to do it, I would transform the
contour to pixel space, calculate the pixel distance between points,
then use cumsum to find points around the label location that are just
greater than labelwidth/2 away along the contour path. These two
indices would define the label rotation and would be useful for breaking
contours. I can implement this, but I would like to have a better
understanding of how the current code works before modifying it.
I also made a slight modification to BlockingMouseInput that affects
ginput functioning. You can now exit out of ginput at any time using
the second mouse button, even if not in infinite mode. This agrees with
matlab functionality and can be quite useful if you want to select no
more than a certain number of points.
Cheers,
David
On Thu, 2008-07-17 at 09:46 +0200, David M. Kaplan wrote:
> Hi all,
>
> Thanks for the comments. My sourceforge ID is dmkaplan. Please add me
> as a developer. I will commit to the trunk and try to not break things,
> but I am VERY new to python and it is a possibility. If things don't
> work out, we can always fall back to creating a branch, though I admit
> that branching and merging in subversion is a pain. And please do
> notify me about stylistic issues, etc.
>
> My contributions are likely to be a bit sporadic and selfish in the
> sense that I am just adding functionality that I use all the time in
> matlab. But if everyone did that, it wouldn't be half bad....
>
> I don't think the blocking code will be that hard to maintain. It
> basically just depends on events, callback functions and time.sleep. If
> those are cross-platform, then it shouldn't be a problem. But only time
> will tell. My ability and desire to test on multiple platforms is
> limited - I use ubuntu/gnome-gtk/linux 100%.
>
> I plan on spending today sprucing up the patch I sent. Unless anyone is
> against, I will probably commit and notify in stages so that each piece
> of the puzzle can be considered separately.
>
> I have noticed that the current contour labeling code has its
> idiosyncrasies, including dealing poorly with label resizing and not
> being very friendly about unbreaking contours (i.e., it is currently not
> possible to have a true remove method for ContourSet). I don't plan on
> fixing these (at least until I have a burning desire to resize labels),
> but think a contribution that allowed people to resize labels and
> break-unbreak contours would be useful. I plan on compartmentalizing a
> bit more ContourLabeler so that each bit of functionality is a separate
> method - this should make integrating a new LabeledLine class a bit
> easier as we would just need to attach the right method of one to the
> other.
>
> Cheers,
> David
>
> On Wed, 2008-07-16 at 10:10 -1000, Eric Firing wrote:
> > John Hunter wrote:
> > > On Wed, Jul 16, 2008 at 7:20 AM, David M. Kaplan <[EMAIL PROTECTED]>
> > > wrote:
> > >
> > >> The patch isn't done - manually selected labels won't be rotated or
> > >> inline. There is also a need for general cleaning up and documentation.
> > >> I just want to see what people think about the approach before investing
> > >> more time. I added this functionality by adding a class
> > >> ContourLabelerWithManual that inherits from ContourLabeler and
> > >> BlockingMouseInput (the class used by ginput to interactively select
> > >> points). ContourSet then inherits from ContourLabelerWithManual instead
> > >> of ContourLabeler. If manual is selected, then it enters interactive
> > >> mode, if not, then results should be as before.
> > >
> > > Hi David,
> > >
> > > I think this looks good. I would like to see it finished before
> > > inclusion (eg rotating the labels inline) but this functionality looks
> > > very useful. In general, I think it would be nice to have better
> > > support for easy interaction with figures, eg for annotations. My
> > > main question is whether blocking input is the best choice.
> > > Admitedly, most users find it more intuitive to set up blocking input
> > > rather than using non-blocking input that is terminated by a custom
> > > button press, key stroke, or command, but I am worried about how
> > > robust this is across user interfaces and environments, with all the
> > > attendant problems of GUI threads, mainloops, etc. Gael has done a
> > > nice job with ginput across backends, but this code is relatively new
> > > and lightly tested. Basically, someone (probably you and Gael) will
> > > need to be up to the task of supporting blocking input across user
> > > interfaces, which probably isn't trivial but maybe I'm overly
> > > cautious. Anyway, something to think about.
> > >
> > >> I also had to move the classes Blocking*Input from figure.py to a
> > >> separate file blocking_input.py to avoid circular imports.
> > >
> > > A minor nit here: when you import blocking_input, you should use
> > >
> > > import matplotlib.blocking_input as blocking_input
> > >
> > > rather than simply
> > >
> > > import blocking_input
> > >
> > > as discussed in the coding guide:
> > > http://matplotlib.sourceforge.net/doc/html/devel/coding_guide.html
> > >
> > > On the subject of the docs, we are in the midst of a push to get beter
> > > documentation for mpl, so anything you can add while working in terms
> > > of tutorial or faq or whatever on the new code would be welcome. The
> > > docs are in the doc subdir of the svn trunk.
> > >
> > >> Please let me know what you think. Also, I am wondering if the powers
> > >> that be would be willing to give me commit access to my own branch of
> > >> the matplotlib svn. I don't want to modify the trunk, but for my own
> > >> sanity, it would be nice to be able to keep track of my changes
> > >> somewhere. If not, I would like to here what other non-commit
> > >> developers do to best organize changes.
> > >
> > > If you send me your sf id, I will add you to the developer list. I
> > > don't mind you committing to the trunk unless you are afraid your
> > > changes will break code. I am not a huge fan of having a lot of
> > > branches.
> > >
> > > Thanks for the submission -- I await more informed commentary from
> > > those who actually use contouring....
> >
> > I agree with adding the functionality provided it can be done in a
> > robust and maintainable way; I have never looked into the arcane aspects
> > of gui interaction across backends.
> >
> > Unless it got fixed as part of the transforms re-write, there are
> > problems even with the existing contour labeling, in that it does not
> > (or did not) handle resizes well. Among the many things I have thought
> > of but not gotten to is the idea that there should be a LabeledLine
> > class to handle this outside of contour.py. The idea is that in
> > addition to the ordinary line path, it would include a list of labels
> > and positions (not sure what the right way to specify the positions is),
> > and the class would handle breaking the path and inserting the correctly
> > rotated text.
> >
> > Eric
> >
> >
> > >
> > > JDH
> > >
> > > -------------------------------------------------------------------------
> > > 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-devel mailing list
> > > Matplotlib-devel@lists.sourceforge.net
> > > https://lists.sourceforge.net/lists/listinfo/matplotlib-devel
> >
--
**********************************
David M. Kaplan
Charge de Recherche 1
Institut de Recherche pour le Developpement
Centre de Recherche Halieutique Mediterraneenne et Tropicale
av. Jean Monnet
B.P. 171
34203 Sete cedex
France
Phone: +33 (0)4 99 57 32 27
Fax: +33 (0)4 99 57 32 95
http://www.ur097.ird.fr/team/dkaplan/index.html
**********************************
--
**********************************
David M. Kaplan
Charge de Recherche 1
Institut de Recherche pour le Developpement
Centre de Recherche Halieutique Mediterraneenne et Tropicale
av. Jean Monnet
B.P. 171
34203 Sete cedex
France
Phone: +33 (0)4 99 57 32 27
Fax: +33 (0)4 99 57 32 95
http://www.ur097.ird.fr/team/dkaplan/index.html
**********************************
Index: lib/matplotlib/pyplot.py
===================================================================
--- lib/matplotlib/pyplot.py (revision 5770)
+++ lib/matplotlib/pyplot.py (working copy)
@@ -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):
Index: lib/matplotlib/contour.py
===================================================================
--- lib/matplotlib/contour.py (revision 5770)
+++ lib/matplotlib/contour.py (working copy)
@@ -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.
@@ -44,6 +47,15 @@
only labels contours listed in *v*.
+ clabel(cs,[ v,] 'manual', **kwargs )
+
+ places contour labels 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 (see
+ below).
+
Optional keyword arguments:
*fontsize*:
@@ -76,8 +88,12 @@
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=False
+ if len(args)>0 and args[-1]=='manual':
+ self.manual_select=True
+ args = args[:-1]
-
if len(args) == 0:
levels = self.levels
indices = range(len(self.levels))
@@ -126,10 +142,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 +357,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 +450,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]):
Index: lib/matplotlib/blocking_input.py
===================================================================
--- lib/matplotlib/blocking_input.py (revision 0)
+++ lib/matplotlib/blocking_input.py (revision 0)
@@ -0,0 +1,355 @@
+"""
+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
+
Index: lib/matplotlib/figure.py
===================================================================
--- lib/matplotlib/figure.py (revision 5770)
+++ lib/matplotlib/figure.py (working copy)
@@ -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):
"""
@@ -1117,10 +1034,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,
-------------------------------------------------------------------------
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-devel mailing list
Matplotlib-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel