Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/matplotlib/blocking_input.py : 29%

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:
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.
9`BlockingKeyMouseInput`
10 Creates a callable object to retrieve key or mouse clicks in a blocking
11 way for interactive sessions. Used by `waitforbuttonpress`.
13`BlockingMouseInput`
14 Creates a callable object to retrieve mouse clicks in a blocking way for
15 interactive sessions. Used by `ginput`.
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"""
22import logging
23from numbers import Integral
25from matplotlib import cbook
26import matplotlib.lines as mlines
28_log = logging.getLogger(__name__)
31class BlockingInput:
32 """Callable for retrieving events in a blocking way."""
34 def __init__(self, fig, eventslist=()):
35 self.fig = fig
36 self.eventslist = eventslist
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))
47 # This will extract info from events.
48 self.post_event()
50 # Check if we have enough events already.
51 if len(self.events) >= self.n > 0:
52 self.fig.canvas.stop_event_loop()
54 def post_event(self):
55 """For baseclass, do nothing but collect events."""
57 def cleanup(self):
58 """Disconnect all callbacks."""
59 for cb in self.callbacks:
60 self.fig.canvas.mpl_disconnect(cb)
61 self.callbacks = []
63 def add_event(self, event):
64 """For base class, this just appends an event to events."""
65 self.events.append(event)
67 def pop_event(self, index=-1):
68 """
69 Remove an event from the event list -- by default, the last.
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)
76 pop = pop_event
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 = []
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
100class BlockingMouseInput(BlockingInput):
101 """
102 Callable for retrieving mouse clicks in a blocking way.
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 """
109 button_add = 1
110 button_pop = 3
111 button_stop = 2
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
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()
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)
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)
157 def mouse_event_add(self, event):
158 """
159 Process an button-1 event (add a click if inside axes).
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)
170 def mouse_event_stop(self, event):
171 """
172 Process an button-2 event (end blocking input).
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()
185 def mouse_event_pop(self, event):
186 """
187 Process an button-3 event (remove the last click).
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)
199 def add_click(self, event):
200 """
201 Add the coordinates of an event to the list of clicks.
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()
218 def pop_click(self, event, index=-1):
219 """
220 Remove a click (by default, the last) from the list of clicks.
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()
231 def pop(self, event, index=-1):
232 """
233 Removes a click and the associated event from the list of clicks.
235 Defaults to the last click.
236 """
237 self.pop_click(event, index)
238 BlockingInput.pop(self, index)
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)
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
267class BlockingContourLabeler(BlockingMouseInput):
268 """
269 Callable for retrieving mouse clicks and key presses in a blocking way.
271 Used to place contour labels.
272 """
274 def __init__(self, cs):
275 self.cs = cs
276 BlockingMouseInput.__init__(self, fig=cs.ax.figure)
278 def add_click(self, event):
279 self.button1(event)
281 def pop_click(self, event, index=-1):
282 self.button3(event)
284 def button1(self, event):
285 """
286 Process an button-1 event (add a label to a contour).
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)
301 def button3(self, event):
302 """
303 Process an button-3 event (remove a label if not in inline mode).
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.
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()
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)
326class BlockingKeyMouseInput(BlockingInput):
327 """
328 Callable for retrieving mouse clicks and key presses in a blocking way.
329 """
331 def __init__(self, fig):
332 BlockingInput.__init__(self, fig=fig, eventslist=(
333 'button_press_event', 'key_press_event'))
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.")
342 def __call__(self, timeout=30):
343 """
344 Blocking call to retrieve a single mouse click or key press.
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)
352 return self.keyormouse