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