3
3
matplotlib event handling to interact with objects on the canvas
4
4
5
5
"""
6
+ from matplotlib .artist import Artist
6
7
from matplotlib .patches import Polygon
7
8
from matplotlib .numerix import sqrt , nonzero , equal , asarray , dot , Float
8
9
from matplotlib .numerix .mlab import amin
11
12
12
13
13
14
14
- class EditablePolygon ( Polygon ) :
15
+ class PolygonInteractor :
15
16
"""
16
- An editable polygon.
17
+ An polygon editor .
17
18
18
19
Key-bindings
19
20
@@ -29,105 +30,120 @@ class EditablePolygon(Polygon):
29
30
30
31
showverts = True
31
32
epsilon = 5 # max pixel distance to count as a vertex hit
32
- def __init__ (self , * args , ** kwargs ):
33
- Polygon .__init__ (self , * args , ** kwargs )
34
- self .line = Line2D ([],[],marker = 'o' , markerfacecolor = 'r' )
35
- self ._ind = None # the active vert
36
33
37
- self .xy = list (self .xy ) # make sure it is editable
38
-
39
- def set_figure (self , fig ):
40
- Polygon .set_figure (self , fig )
41
- self .line .set_figure (fig )
42
- self .figure .canvas .mpl_connect ('button_press_event' , self .button_press_callback )
43
- self .figure .canvas .mpl_connect ('key_press_event' , self .key_press_callback )
44
- self .figure .canvas .mpl_connect ('button_release_event' , self .button_release_callback )
45
- self .figure .canvas .mpl_connect ('motion_notify_event' , self .motion_notify_callback )
46
-
34
+ def __init__ (self , poly ):
35
+ if poly .figure is None :
36
+ raise RuntimeError ('You must first add the polygon to a figure or canvas before defining the interactor' )
37
+ canvas = poly .figure .canvas
38
+ self .poly = poly
39
+ self .poly .verts = list (self .poly .verts )
40
+ x , y = zip (* self .poly .verts )
41
+ self .line = Line2D (x ,y ,marker = 'o' , markerfacecolor = 'r' )
42
+ #self._update_line(poly)
47
43
48
- def set_transform (self , trans ):
49
- Polygon .set_transform (self , trans )
50
- self .line .set_transform (trans )
51
-
52
- def set_clip_on (self , b ):
53
- Polygon .set_clip_on (self , b )
54
- self .line .set_clip_on (b )
55
-
56
- def set_clip_box (self , b ):
57
- Polygon .set_clip_box (self , b )
58
- self .line .set_clip_box (b )
44
+ cid = self .poly .add_callback (self .poly_changed )
45
+ self ._ind = None # the active vert
59
46
47
+ canvas .mpl_connect ('button_press_event' , self .button_press_callback )
48
+ canvas .mpl_connect ('key_press_event' , self .key_press_callback )
49
+ canvas .mpl_connect ('button_release_event' , self .button_release_callback )
50
+ canvas .mpl_connect ('motion_notify_event' , self .motion_notify_callback )
51
+ self .canvas = canvas
52
+
60
53
61
- def draw (self , renderer ):
62
- if not self ._visible : return
63
- Polygon .draw (self , renderer )
64
- if self .showverts :
65
- self .line .set_data (zip (* self .xy ))
66
- self .line .draw (renderer )
54
+ def poly_changed (self , poly ):
55
+ 'this method is called whenever the polygon object is called'
56
+ # only copy the artist props to the line (except visibility)
57
+ vis = self .line .get_visible ()
58
+ Artist .update_from (self .line , poly )
59
+ self .line .set_visible (vis ) # don't use the poly visibility state
60
+
67
61
68
62
def get_ind_under_point (self , event ):
69
- x , y = zip (* self .xy )
63
+ 'get the index of the vertex under point if within epsilon tolerance'
64
+ x , y = zip (* self .poly .verts )
65
+
70
66
# display coords
71
- xt , yt = self ._transform .numerix_x_y (x , y )
67
+ xt , yt = self .poly . get_transform () .numerix_x_y (x , y )
72
68
d = sqrt ((xt - event .x )** 2 + (yt - event .y )** 2 )
73
69
indseq = nonzero (equal (d , amin (d )))
74
70
ind = indseq [0 ]
71
+
75
72
if d [ind ]>= self .epsilon :
76
73
ind = None
77
74
78
75
return ind
79
76
80
77
def button_press_callback (self , event ):
78
+ 'whenever a mouse button is pressed'
81
79
if not self .showverts : return
82
80
if event .inaxes == None : return
81
+ if event .button != 1 : return
83
82
self ._ind = self .get_ind_under_point (event )
84
-
85
83
86
84
def button_release_callback (self , event ):
87
- if not self .showverts : return
85
+ 'whenever a mouse button is released'
86
+ if not self .showverts : return
87
+ if event .button != 1 : return
88
88
self ._ind = None
89
89
90
90
def key_press_callback (self , event ):
91
+ 'whenever a key is pressed'
91
92
if not event .inaxes : return
92
93
if event .key == 't' :
93
94
self .showverts = not self .showverts
95
+ self .line .set_visible (self .showverts )
94
96
if not self .showverts : self ._ind = None
95
97
elif event .key == 'd' :
96
98
ind = self .get_ind_under_point (event )
97
99
if ind is not None :
98
- self .xy = [tup for i ,tup in enumerate (self .xy ) if i != ind ]
100
+ self .poly .verts = [tup for i ,tup in enumerate (self .poly .verts ) if i != ind ]
101
+ self .line .set_data (zip (* self .poly .verts ))
99
102
elif event .key == 'i' :
100
- xys = self ._transform . seq_xy_tups (self .xy )
103
+ xys = self .poly . get_transform (). seq_xy_tups (self .poly . verts )
101
104
p = event .x , event .y # display coords
102
105
for i in range (len (xys )- 1 ):
103
106
s0 = xys [i ]
104
107
s1 = xys [i + 1 ]
105
108
d = dist_point_to_segment (p , s0 , s1 )
106
109
if d <= self .epsilon :
107
- self .xy .insert (i + 1 , (event .xdata , event .ydata ))
110
+ self .poly .verts .insert (i + 1 , (event .xdata , event .ydata ))
111
+ self .line .set_data (zip (* self .poly .verts ))
108
112
break
109
113
110
-
111
-
112
-
113
- self .figure .canvas .draw ()
114
+
115
+ self .canvas .draw ()
114
116
115
117
def motion_notify_callback (self , event ):
118
+ 'on mouse movement'
116
119
if not self .showverts : return
117
120
if self ._ind is None : return
118
121
if event .inaxes is None : return
122
+ if event .button != 1 : return
119
123
x ,y = event .xdata , event .ydata
120
- self .xy [self ._ind ] = x ,y
121
- self .figure .canvas .draw_idle ()
124
+ self .poly .verts [self ._ind ] = x ,y
125
+ self .line .set_data (zip (* self .poly .verts ))
126
+ self .canvas .draw_idle ()
122
127
123
128
124
129
from pylab import *
125
- verts = Circle ((.5 ,.5 ),.5 ).get_verts ()
126
- p = EditablePolygon (verts )
130
+
131
+
132
+
133
+
127
134
128
135
fig = figure ()
136
+ circ = Circle ((.5 ,.5 ),.5 )
137
+
138
+
139
+
140
+
129
141
ax = subplot (111 )
130
- ax .add_patch (p )
131
- title ('Click and drag a point to move it' )
132
- axis ([0 ,1 ,0 ,1 ])
142
+ ax .add_patch (circ )
143
+ p = PolygonInteractor ( circ )
144
+
145
+ ax .add_line (p .line )
146
+ ax .set_title ('Click and drag a point to move it' )
147
+ ax .set_xlim ((0 ,1 ))
148
+ ax .set_ylim ((0 ,1 ))
133
149
show ()
0 commit comments