I wanted something more precise (and easier on the carpal tunnel) than
the included Lasso widget. Inspired by the existing Lasso, I forked
out an alternate approach that creates a polygon with mouse clicks and
returns a list of vertices to a callback function.
See the attached for a demo. It's been tested with WxAgg running off
recent svn. I'm using idle_event, which isn't mentioned in the docs
for the released version.
If there's interest, I'll put together a patch to add this to widgets.py.
Thanks,
Eric B
from matplotlib.lines import Line2D
from matplotlib.widgets import Widget
class PolyLasso(Widget):
"""
A lasso widget that allows the user to define a lasso region by
clicking to form a polygon.
"""
def __init__(self, ax, callback=None,
line_to_next=True,
useblit=True):
"""
Create a new lasso.
*ax* is the axis on which to draw
*callback* is a function that will be called with arguments (*ax*, *line*, *verts*).
*verts* is a list of (x,y) pairs that define the polygon, with the first and
last entries equal.
*line_to_next* controls whether or not a line is drawn to the current
cursor position as the polygon is created
*useblit* = *True* is the only thorougly tested option.
"""
self.axes = ax
self.figure = ax.figure
self.canvas = self.figure.canvas
self.useblit = useblit
self.line_to_next = line_to_next
if useblit:
self.background = self.canvas.copy_from_bbox(self.axes.bbox)
self.verts = []
self.line = None
self.callback = callback
self.cids = []
self.cids.append(self.canvas.mpl_connect('button_release_event', self.onrelease))
self.cids.append(self.canvas.mpl_connect('motion_notify_event', self.onmove))
self.cids.append(self.canvas.mpl_connect('draw_event', self.ondraw))
def ondraw(self, event):
""" draw_event callback, to take care of some blit artifacts """
self.background = self.canvas.copy_from_bbox(self.axes.bbox)
if self.line:
self.axes.draw_artist(self.line)
self.canvas.blit(self.axes.bbox)
def do_callback(self, event):
""" idle_event callback after polygon is finalized. """
# Clear out callbacks.
for cid in self.cids:
self.canvas.mpl_disconnect(cid)
self.callback(self.axes, self.line, self.verts)
self.cleanup()
def cleanup(self):
""" Remove the lasso line. """
# Clear out callbacks
for cid in self.cids:
self.canvas.mpl_disconnect(cid)
self.axes.lines.remove(self.line)
self.canvas.draw()
def finalize(self):
""" Called when user makes the final right click """
# all done with callbacks pertaining to adding verts to the polygon
for cid in self.cids:
self.canvas.mpl_disconnect(cid)
self.cids = []
# Greater than three verts, since a triangle
# has a duplicate point to close the poly.
if len(self.verts)>3:
# Matplotlib will not draw the closed polygon until we step completely
# out of this routine. It is desirable to see the closed polygon on screen
# in the event that *callback* takes a long time. So, delay callback until
# we get an idle event, which will signal that the closed polygon has also
# been drawn.
self.cids.append(self.canvas.mpl_connect('idle_event', self.do_callback))
else:
print 'Need at least three vertices to make a polygon'
self.cleanup()
def draw_update(self):
""" Adjust the polygon line, and blit it to the screen """
self.line.set_data(zip(*self.verts))
if self.useblit:
self.canvas.restore_region(self.background)
self.axes.draw_artist(self.line)
self.canvas.blit(self.axes.bbox)
else:
self.canvas.draw_idle()
def onmove(self, event):
""" Update the next vertex position """
if self.line == None: return
self.verts[-1] = ((event.xdata, event.ydata))
if self.line_to_next:
self.draw_update()
def onrelease(self, event):
""" User clicked the mouse. Add a vertex or finalize depending on mouse button. """
if self.verts is None: return
if event.inaxes != self.axes: return
if event.button == 3:
# Right click - close the polygon
# Set the dummy point to the first point
self.verts[-1] = self.verts[0]
self.draw_update()
self.finalize()
return
# The rest pertains to left click only
if event.button != 1: return
if self.line == None:
# Deal with the first click
self.line=Line2D([event.xdata], [event.ydata],
linestyle='-', marker='s', color='black', lw=1, ms=4, animated=True)
self.axes.add_line(self.line)
self.verts.append((event.xdata, event.ydata))
# finalize vertex at this click, set up a new on that changes as mouse moves
self.verts[-1] = (event.xdata, event.ydata)
self.verts.append((event.xdata, event.ydata))
self.draw_update()
class manager(object):
def __init__(self):
self.x = numpy.arange(10.0)
self.y = self.x**2.0
self.charge = numpy.zeros_like(self.x)
self.f = figure()
ax = self.f.add_subplot(111)
self.sc = ax.scatter(self.x, self.y, c=self.charge, vmin=-1, vmax=1, edgecolor='none')
# self.sc.set_clim(-1, 1)
self.lasso = PolyLasso(ax, self.callback)
self.f.canvas.widgetlock(self.lasso)
def callback(self, ax, lasso_line, verts):
print verts
mask = points_inside_poly(zip(self.x, self.y), verts) == 1
print self.x[mask]
print self.y[mask]
self.charge[mask] = -1
print self.charge
self.lasso_line = lasso_line
# not actually necessary ... scatter stores a ref to charge array
# self.sc.set_array(self.charge)
self.f.canvas.widgetlock.release(self.lasso)
if __name__ == '__main__':
import numpy
from matplotlib.nxutils import points_inside_poly
from matplotlib.pyplot import figure, show
m = manager()
show()
-------------------------------------------------------------------------
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