Now, the question: Which is the best approach with
> Matplotlib for this kind of interaction? Should I plot
> every point using plot(x1, y1), plot(x2, y2) etc...? Or
> should I use Collections (i have no idea on what to do
> with collections)? Scattered plots? Does anyone have any
> suggestion/pointer for me? Some examples of
> editing/interacting with plots?
Have you seen examples/poly_editor.py? It plots a polygon and at each
vertex plots a marker. You can click on the marker and drag it and
move the vertex around, as well as insert and delete vertices. It
would be easy to modify the callback to change the marker color, size
etc, to indicate that it is selected. The example doesn't do what you
are want precisely, but does provide a lot of code illustrating how to
do these things with mpl event handling.
Note particularly the "i" functionality for inserting a vertex. To
insert a vertex your cursor must be over a line segment connecting two
vertices. From your description of needing to interact with the line
between two points, it should like this lass might be helpful.
class PolygonInteractor:
"""
An polygon editor.
Key-bindings
't' toggle vertex markers on and off. When vertex markers are on,
you can move them, delete them
'd' delete the vertex under point
'i' insert a vertex at point. You must be within epsilon of the
line connecting two existing vertices
You might want to define your own "interactor" classes similar to
PolygonInteractor (eg a LineSegmentInteractor). Then the line segment
will encompass all the intelligence you need and can easily be plugged
into your graph.
If you come up with useful widgets like these, please send them our
way. Also feel free to post partially working code if you get stuck,
On a related note, to make something like this work efficiently it
helps to be able to redraw just part of a figure. The current release
of mpl has support for this with GTKAgg only, but only a few new
backend methods that need to be defined to support this. This makes
things like moving objects around the canvas much more efficiently.
It would be nice to have them for the various *Agg backends
The required methods are:
region = canvas.copy_from_bbox(bbox)
canvas.restore_region(region)
canvas.blit(bbox)
region can be any backend dependent object -- the front end doesn't
use it, only passes it around. bbox is a transforms.BBox instance.
If anyone is interested in animation and knows something about Tk, WX,
Qt, FLTK, you may want to take a crack at implementing these methods.
Below is another interaction/widget which provides a Rectangle widget
that can be resized or moved around (GTKAgg only). When you click on
the edge of the rectangle the edgecolor turns red and when you release
it the color is restored. There are some bugs, but it does illustrate
more interaction code as well as provide a test script for anyone who
wants to understand how to use the three methods shown above.
import matplotlib
matplotlib.use('GTKAgg')
from pylab import *
from matplotlib.transforms import lbwh_to_bbox
from matplotlib.mlab import dist_point_to_segment
class RectangleWidget(Rectangle):
def __init__(self, figure, *args, **kwargs):
Rectangle.__init__(self, *args, **kwargs)
self.figure = figure
self.idle = True
self.oldbox = self.get_window_extent()
self.newbox = self.get_window_extent()
self.region = self.figure.canvas.copy_from_bbox(self.oldbox)
self.figure.canvas.mpl_connect('motion_notify_event', self.move)
self.figure.canvas.mpl_connect('button_press_event', self.press)
self.figure.canvas.mpl_connect('button_release_event', self.release)
self.figure.draw_artist(self)
self._resize = False
self._drag = False
def redraw(self):
canvas = self.figure.canvas
canvas.restore_region(self.region)
self.region = canvas.copy_from_bbox(self.newbox)
self.figure.draw_artist(self)
canvas.blit(self.oldbox)
canvas.blit(self.newbox)
def press(self, event):
if event.button!=1: return
x, y = self.xy
w, h = self.width, self.height
# the four line segments of the rectangle
lb = x, y
lt = x, y+h
rb = x+w, y
rt = x+w, y+h
segments = {
'left' : (lb, lt),
'right' : (rb, rt),
'top' : (lt, rt),
'bottom' : (lb, rb),
}
p = event.x, event.y
near =
for name, segment in segments.items():
s0, s1 = segment
d = dist_point_to_segment(p, s0, s1)
if d<5: near.append(name)
info = event.x, event.y, self.xy[0], self.xy[1], self.width, self.height, near
if len(near):
self._resize = info
self.set_edgecolor('red')
self.set_linewidth(2)
else:
self._resize = False
if self.get_window_extent().contains(event.x, event.y):
self._drag = info
self.redraw()
def release(self, event):
if event.button!=1: return
self._resize = False
self._drag = False
self.idle = True
self.set_edgecolor('black')
self.set_linewidth(0.5)
self.redraw()
def move(self, event):
if not (self.idle and (self._resize or self._drag)): return
self.idle = False
canvas = self.figure.canvas
if self._resize:
exo, eyo, xo, yo, wo, ho, near = self._resize
else:
exo, eyo, xo, yo, wo, ho, near = self._drag
scalex=0; scaley=0; scalew=0; scaleh=0
if 'left' in near:
scalex = 1
scalew = -1
if 'right' in near:
scalew = 1
if 'bottom' in near:
scaley = 1
scaleh = -1
if 'top' in near:
scaleh = 1
if self._drag:
scalex = 1
scaley = 1
dx = event.x - exo
dy = event.y - eyo
if event.button ==1:
self.oldbox = self.get_padded_box()
newx = xo + scalex*dx
if scalew==0 or newx<xo+wo:
self.xy[0] = newx
newy = yo + scaley*dy
if scaleh==0 or newy<yo+ho:
self.xy[1] = newy
neww = wo + scalew*dx
if neww>0: self.width = neww
newh = ho + scaleh*dy
if newh>0: self.height = newh
#self.xy = event.x, event.y
self.newbox = self.get_padded_box()
#print event.x, event.y, self.get_padded_box().get_bounds()
self.redraw()
self.idle = True
def get_padded_box(self, pad=5):
xmin = self.xy[0]-pad
ymin = self.xy[1]-pad
return lbwh_to_bbox(xmin, ymin, self.width+2*pad, self.height+2*pad)
ion()
fig = figure()
def keypress(event):
if event.key=='n':
widget = RectangleWidget( fig, (100,100), 20, 30)
fig.canvas.blit()
fig.canvas.mpl_connect('key_press_event', keypress)
fig.text(0.1, 0.9, "Press n for new rectangle and then resize it or move it")
draw()
show()