Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2This provides several classes used for blocking interaction with figure 

3windows: 

4 

5`BlockingInput` 

6 Creates a callable object to retrieve events in a blocking way for 

7 interactive sessions. Base class of the other classes listed here. 

8 

9`BlockingKeyMouseInput` 

10 Creates a callable object to retrieve key or mouse clicks in a blocking 

11 way for interactive sessions. Used by `waitforbuttonpress`. 

12 

13`BlockingMouseInput` 

14 Creates a callable object to retrieve mouse clicks in a blocking way for 

15 interactive sessions. Used by `ginput`. 

16 

17`BlockingContourLabeler` 

18 Creates a callable object to retrieve mouse clicks in a blocking way that 

19 will then be used to place labels on a `ContourSet`. Used by `clabel`. 

20""" 

21 

22import logging 

23from numbers import Integral 

24 

25from matplotlib import cbook 

26import matplotlib.lines as mlines 

27 

28_log = logging.getLogger(__name__) 

29 

30 

31class BlockingInput: 

32 """Callable for retrieving events in a blocking way.""" 

33 

34 def __init__(self, fig, eventslist=()): 

35 self.fig = fig 

36 self.eventslist = eventslist 

37 

38 def on_event(self, event): 

39 """ 

40 Event handler; will be passed to the current figure to retrieve events. 

41 """ 

42 # Add a new event to list - using a separate function is overkill for 

43 # the base class, but this is consistent with subclasses. 

44 self.add_event(event) 

45 _log.info("Event %i", len(self.events)) 

46 

47 # This will extract info from events. 

48 self.post_event() 

49 

50 # Check if we have enough events already. 

51 if len(self.events) >= self.n > 0: 

52 self.fig.canvas.stop_event_loop() 

53 

54 def post_event(self): 

55 """For baseclass, do nothing but collect events.""" 

56 

57 def cleanup(self): 

58 """Disconnect all callbacks.""" 

59 for cb in self.callbacks: 

60 self.fig.canvas.mpl_disconnect(cb) 

61 self.callbacks = [] 

62 

63 def add_event(self, event): 

64 """For base class, this just appends an event to events.""" 

65 self.events.append(event) 

66 

67 def pop_event(self, index=-1): 

68 """ 

69 Remove an event from the event list -- by default, the last. 

70 

71 Note that this does not check that there are events, much like the 

72 normal pop method. If no events exist, this will throw an exception. 

73 """ 

74 self.events.pop(index) 

75 

76 pop = pop_event 

77 

78 def __call__(self, n=1, timeout=30): 

79 """Blocking call to retrieve *n* events.""" 

80 cbook._check_isinstance(Integral, n=n) 

81 self.n = n 

82 self.events = [] 

83 

84 if hasattr(self.fig.canvas, "manager"): 

85 # Ensure that the figure is shown, if we are managing it. 

86 self.fig.show() 

87 # Connect the events to the on_event function call. 

88 self.callbacks = [self.fig.canvas.mpl_connect(name, self.on_event) 

89 for name in self.eventslist] 

90 try: 

91 # Start event loop. 

92 self.fig.canvas.start_event_loop(timeout=timeout) 

93 finally: # Run even on exception like ctrl-c. 

94 # Disconnect the callbacks. 

95 self.cleanup() 

96 # Return the events in this case. 

97 return self.events 

98 

99 

100class BlockingMouseInput(BlockingInput): 

101 """ 

102 Callable for retrieving mouse clicks in a blocking way. 

103 

104 This class will also retrieve keypresses and map them to mouse clicks: 

105 delete and backspace are like mouse button 3, enter is like mouse button 2 

106 and all others are like mouse button 1. 

107 """ 

108 

109 button_add = 1 

110 button_pop = 3 

111 button_stop = 2 

112 

113 def __init__(self, fig, mouse_add=1, mouse_pop=3, mouse_stop=2): 

114 BlockingInput.__init__(self, fig=fig, 

115 eventslist=('button_press_event', 

116 'key_press_event')) 

117 self.button_add = mouse_add 

118 self.button_pop = mouse_pop 

119 self.button_stop = mouse_stop 

120 

121 def post_event(self): 

122 """Process an event.""" 

123 if len(self.events) == 0: 

124 _log.warning("No events yet") 

125 elif self.events[-1].name == 'key_press_event': 

126 self.key_event() 

127 else: 

128 self.mouse_event() 

129 

130 def mouse_event(self): 

131 """Process a mouse click event.""" 

132 event = self.events[-1] 

133 button = event.button 

134 if button == self.button_pop: 

135 self.mouse_event_pop(event) 

136 elif button == self.button_stop: 

137 self.mouse_event_stop(event) 

138 elif button == self.button_add: 

139 self.mouse_event_add(event) 

140 

141 def key_event(self): 

142 """ 

143 Process a key press event, mapping keys to appropriate mouse clicks. 

144 """ 

145 event = self.events[-1] 

146 if event.key is None: 

147 # At least in OSX gtk backend some keys return None. 

148 return 

149 key = event.key.lower() 

150 if key in ['backspace', 'delete']: 

151 self.mouse_event_pop(event) 

152 elif key in ['escape', 'enter']: 

153 self.mouse_event_stop(event) 

154 else: 

155 self.mouse_event_add(event) 

156 

157 def mouse_event_add(self, event): 

158 """ 

159 Process an button-1 event (add a click if inside axes). 

160 

161 Parameters 

162 ---------- 

163 event : `~.backend_bases.MouseEvent` 

164 """ 

165 if event.inaxes: 

166 self.add_click(event) 

167 else: # If not a valid click, remove from event list. 

168 BlockingInput.pop(self) 

169 

170 def mouse_event_stop(self, event): 

171 """ 

172 Process an button-2 event (end blocking input). 

173 

174 Parameters 

175 ---------- 

176 event : `~.backend_bases.MouseEvent` 

177 """ 

178 # Remove last event just for cleanliness. 

179 BlockingInput.pop(self) 

180 # This will exit even if not in infinite mode. This is consistent with 

181 # MATLAB and sometimes quite useful, but will require the user to test 

182 # how many points were actually returned before using data. 

183 self.fig.canvas.stop_event_loop() 

184 

185 def mouse_event_pop(self, event): 

186 """ 

187 Process an button-3 event (remove the last click). 

188 

189 Parameters 

190 ---------- 

191 event : `~.backend_bases.MouseEvent` 

192 """ 

193 # Remove this last event. 

194 BlockingInput.pop(self) 

195 # Now remove any existing clicks if possible. 

196 if self.events: 

197 self.pop(event) 

198 

199 def add_click(self, event): 

200 """ 

201 Add the coordinates of an event to the list of clicks. 

202 

203 Parameters 

204 ---------- 

205 event : `~.backend_bases.MouseEvent` 

206 """ 

207 self.clicks.append((event.xdata, event.ydata)) 

208 _log.info("input %i: %f, %f", 

209 len(self.clicks), event.xdata, event.ydata) 

210 # If desired, plot up click. 

211 if self.show_clicks: 

212 line = mlines.Line2D([event.xdata], [event.ydata], 

213 marker='+', color='r') 

214 event.inaxes.add_line(line) 

215 self.marks.append(line) 

216 self.fig.canvas.draw() 

217 

218 def pop_click(self, event, index=-1): 

219 """ 

220 Remove a click (by default, the last) from the list of clicks. 

221 

222 Parameters 

223 ---------- 

224 event : `~.backend_bases.MouseEvent` 

225 """ 

226 self.clicks.pop(index) 

227 if self.show_clicks: 

228 self.marks.pop(index).remove() 

229 self.fig.canvas.draw() 

230 

231 def pop(self, event, index=-1): 

232 """ 

233 Removes a click and the associated event from the list of clicks. 

234 

235 Defaults to the last click. 

236 """ 

237 self.pop_click(event, index) 

238 BlockingInput.pop(self, index) 

239 

240 def cleanup(self, event=None): 

241 """ 

242 Parameters 

243 ---------- 

244 event : `~.backend_bases.MouseEvent`, optional 

245 Not used 

246 """ 

247 # Clean the figure. 

248 if self.show_clicks: 

249 for mark in self.marks: 

250 mark.remove() 

251 self.marks = [] 

252 self.fig.canvas.draw() 

253 # Call base class to remove callbacks. 

254 BlockingInput.cleanup(self) 

255 

256 def __call__(self, n=1, timeout=30, show_clicks=True): 

257 """ 

258 Blocking call to retrieve *n* coordinate pairs through mouse clicks. 

259 """ 

260 self.show_clicks = show_clicks 

261 self.clicks = [] 

262 self.marks = [] 

263 BlockingInput.__call__(self, n=n, timeout=timeout) 

264 return self.clicks 

265 

266 

267class BlockingContourLabeler(BlockingMouseInput): 

268 """ 

269 Callable for retrieving mouse clicks and key presses in a blocking way. 

270 

271 Used to place contour labels. 

272 """ 

273 

274 def __init__(self, cs): 

275 self.cs = cs 

276 BlockingMouseInput.__init__(self, fig=cs.ax.figure) 

277 

278 def add_click(self, event): 

279 self.button1(event) 

280 

281 def pop_click(self, event, index=-1): 

282 self.button3(event) 

283 

284 def button1(self, event): 

285 """ 

286 Process an button-1 event (add a label to a contour). 

287 

288 Parameters 

289 ---------- 

290 event : `~.backend_bases.MouseEvent` 

291 """ 

292 # Shorthand 

293 if event.inaxes == self.cs.ax: 

294 self.cs.add_label_near(event.x, event.y, self.inline, 

295 inline_spacing=self.inline_spacing, 

296 transform=False) 

297 self.fig.canvas.draw() 

298 else: # Remove event if not valid 

299 BlockingInput.pop(self) 

300 

301 def button3(self, event): 

302 """ 

303 Process an button-3 event (remove a label if not in inline mode). 

304 

305 Unfortunately, if one is doing inline labels, then there is currently 

306 no way to fix the broken contour - once humpty-dumpty is broken, he 

307 can't be put back together. In inline mode, this does nothing. 

308 

309 Parameters 

310 ---------- 

311 event : `~.backend_bases.MouseEvent` 

312 """ 

313 if self.inline: 

314 pass 

315 else: 

316 self.cs.pop_label() 

317 self.cs.ax.figure.canvas.draw() 

318 

319 def __call__(self, inline, inline_spacing=5, n=-1, timeout=-1): 

320 self.inline = inline 

321 self.inline_spacing = inline_spacing 

322 BlockingMouseInput.__call__(self, n=n, timeout=timeout, 

323 show_clicks=False) 

324 

325 

326class BlockingKeyMouseInput(BlockingInput): 

327 """ 

328 Callable for retrieving mouse clicks and key presses in a blocking way. 

329 """ 

330 

331 def __init__(self, fig): 

332 BlockingInput.__init__(self, fig=fig, eventslist=( 

333 'button_press_event', 'key_press_event')) 

334 

335 def post_event(self): 

336 """Determine if it is a key event.""" 

337 if self.events: 

338 self.keyormouse = self.events[-1].name == 'key_press_event' 

339 else: 

340 _log.warning("No events yet.") 

341 

342 def __call__(self, timeout=30): 

343 """ 

344 Blocking call to retrieve a single mouse click or key press. 

345 

346 Returns ``True`` if key press, ``False`` if mouse click, or ``None`` if 

347 timed out. 

348 """ 

349 self.keyormouse = None 

350 BlockingInput.__call__(self, n=1, timeout=timeout) 

351 

352 return self.keyormouse