Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b2a7521

Browse files
committed
Rewrite blocking_input to something much simpler.
The new implementation is ~200 lines shorter, does not require subclassing (and tracking parent methods when trying to understand the implementation), and keeps all the logic at the place of use instead of it being in blocking_input.py (which is now deprecated).
1 parent b61e3a7 commit b2a7521

File tree

5 files changed

+130
-16
lines changed

5 files changed

+130
-16
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
``matplotlib.blocking_input``
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
This module has been deprecated. Instead, use ``canvas.start_event_loop()``
4+
and ``canvas.stop_event_loop()`` while connecting event callbacks as needed.

lib/matplotlib/_blocking_input.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
def blocking_input_loop(figure, event_names, timeout, handler):
2+
"""
3+
Run *figure*'s event loop while listening to interactive events.
4+
5+
The events listed in *event_names* are passed to *handler*.
6+
7+
This function is used to implement `.Figure.waitforbuttonpress`,
8+
`.Figure.ginput`, and `.Axes.clabel`.
9+
10+
Parameters
11+
----------
12+
figure : `~matplotlib.figure.Figure`
13+
event_names : list of str
14+
The names of the events passed to *handler*.
15+
timeout : float
16+
If positive, the event loop is stopped after *timeout* seconds.
17+
handler : Callable[[Event], Any]
18+
Function called for each event; it can force an early exit of the event
19+
loop by calling ``canvas.stop_event_loop()``.
20+
"""
21+
if hasattr(figure.canvas, "manager"):
22+
figure.show() # Ensure that the figure is shown if we are managing it.
23+
# Connect the events to the on_event function call.
24+
cids = [figure.canvas.mpl_connect(name, handler) for name in event_names]
25+
try:
26+
figure.canvas.start_event_loop(timeout) # Start event loop.
27+
finally: # Run even on exception like ctrl-c.
28+
# Disconnect the callbacks.
29+
for cid in cids:
30+
figure.canvas.mpl_disconnect(cid)

lib/matplotlib/blocking_input.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from matplotlib.backend_bases import MouseButton
2727
import matplotlib.lines as mlines
2828

29+
_api.warn_deprecated("3.5", name=__name__, obj_type="module")
2930
_log = logging.getLogger(__name__)
3031

3132

lib/matplotlib/contour.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
Classes to support contour plotting and labelling for the Axes class.
33
"""
44

5+
import functools
56
from numbers import Integral
67

78
import numpy as np
89
from numpy import ma
910

1011
import matplotlib as mpl
11-
from matplotlib import _api
12+
from matplotlib import _api, docstring
13+
from matplotlib.backend_bases import MouseButton
1214
import matplotlib.path as mpath
1315
import matplotlib.ticker as ticker
1416
import matplotlib.cm as cm
@@ -20,9 +22,6 @@
2022
import matplotlib.patches as mpatches
2123
import matplotlib.transforms as mtransforms
2224

23-
# Import needed for adding manual selection capability to clabel
24-
from matplotlib.blocking_input import BlockingContourLabeler
25-
from matplotlib import docstring
2625

2726
# We can't use a single line collection for contour because a line
2827
# collection can have only a single line style, and we want to be able to have
@@ -45,6 +44,35 @@ def get_rotation(self):
4544
return new_angle
4645

4746

47+
def _contour_labeler_event_handler(cs, inline, inline_spacing, event):
48+
canvas = cs.axes.figure.canvas
49+
is_button = event.name == "button_press_event"
50+
is_key = event.name == "key_press_event"
51+
# Quit (even if not in infinite mode; this is consistent with
52+
# MATLAB and sometimes quite useful, but will require the user to
53+
# test how many points were actually returned before using data).
54+
if (is_button and event.button == MouseButton.MIDDLE
55+
or is_key and event.key in ["escape", "enter"]):
56+
canvas.stop_event_loop()
57+
# Pop last click.
58+
elif (is_button and event.button == MouseButton.RIGHT
59+
or is_key and event.key in ["backspace", "delete"]):
60+
# Unfortunately, if one is doing inline labels, then there is currently
61+
# no way to fix the broken contour - once humpty-dumpty is broken, he
62+
# can't be put back together. In inline mode, this does nothing.
63+
if not inline:
64+
cs.pop_label()
65+
canvas.draw()
66+
# Add new click.
67+
elif (is_button and event.button == MouseButton.LEFT
68+
# On macOS/gtk, some keys return None.
69+
or is_key and event.key is not None):
70+
if event.inaxes == cs.ax:
71+
cs.add_label_near(event.x, event.y, transform=False,
72+
inline=inline, inline_spacing=inline_spacing)
73+
canvas.draw()
74+
75+
4876
class ContourLabeler:
4977
"""Mixin to provide labelling capability to `.ContourSet`."""
5078

@@ -198,8 +226,11 @@ def clabel(self, levels=None, *,
198226
print('End manual selection with second mouse button.')
199227
if not inline:
200228
print('Remove last label by clicking third mouse button.')
201-
blocking_contour_labeler = BlockingContourLabeler(self)
202-
blocking_contour_labeler(inline, inline_spacing)
229+
mpl._blocking_input.blocking_input_loop(
230+
self.axes.figure, ["button_press_event", "key_press_event"],
231+
timeout=-1, handler=functools.partial(
232+
_contour_labeler_event_handler,
233+
self, inline, inline_spacing))
203234
else:
204235
self.labels(inline, inline_spacing)
205236

lib/matplotlib/figure.py

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import numpy as np
2323

2424
import matplotlib as mpl
25-
from matplotlib import docstring, projections
25+
from matplotlib import _blocking_input, docstring, projections
2626
from matplotlib.artist import (
2727
Artist, allow_rasterization, _finalize_rasterization)
2828
from matplotlib.backend_bases import (
@@ -33,7 +33,6 @@
3333
import matplotlib.image as mimage
3434

3535
from matplotlib.axes import Axes, SubplotBase, subplot_class_factory
36-
from matplotlib.blocking_input import BlockingMouseInput, BlockingKeyMouseInput
3736
from matplotlib.gridspec import GridSpec
3837
import matplotlib.legend as mlegend
3938
from matplotlib.patches import Rectangle
@@ -3013,12 +3012,52 @@ def ginput(self, n=1, timeout=30, show_clicks=True,
30133012
terminates input and any other key (not already used by the window
30143013
manager) selects a point.
30153014
"""
3016-
blocking_mouse_input = BlockingMouseInput(self,
3017-
mouse_add=mouse_add,
3018-
mouse_pop=mouse_pop,
3019-
mouse_stop=mouse_stop)
3020-
return blocking_mouse_input(n=n, timeout=timeout,
3021-
show_clicks=show_clicks)
3015+
clicks = []
3016+
marks = []
3017+
3018+
def handler(event):
3019+
is_button = event.name == "button_press_event"
3020+
is_key = event.name == "key_press_event"
3021+
# Quit (even if not in infinite mode; this is consistent with
3022+
# MATLAB and sometimes quite useful, but will require the user to
3023+
# test how many points were actually returned before using data).
3024+
if (is_button and event.button == mouse_stop
3025+
or is_key and event.key in ["escape", "enter"]):
3026+
self.canvas.stop_event_loop()
3027+
# Pop last click.
3028+
elif (is_button and event.button == mouse_pop
3029+
or is_key and event.key in ["backspace", "delete"]):
3030+
if clicks:
3031+
clicks.pop()
3032+
if show_clicks:
3033+
marks.pop().remove()
3034+
self.canvas.draw()
3035+
# Add new click.
3036+
elif (is_button and event.button == mouse_add
3037+
# On macOS/gtk, some keys return None.
3038+
or is_key and event.key is not None):
3039+
if event.inaxes:
3040+
clicks.append((event.xdata, event.ydata))
3041+
_log.info("input %i: %f, %f",
3042+
len(clicks), event.xdata, event.ydata)
3043+
if show_clicks:
3044+
line = mpl.lines.Line2D([event.xdata], [event.ydata],
3045+
marker="+", color="r")
3046+
event.inaxes.add_line(line)
3047+
marks.append(line)
3048+
self.canvas.draw()
3049+
if len(clicks) == n and n > 0:
3050+
self.canvas.stop_event_loop()
3051+
3052+
_blocking_input.blocking_input_loop(
3053+
self, ["button_press_event", "key_press_event"], timeout, handler)
3054+
3055+
# Cleanup.
3056+
for mark in marks:
3057+
mark.remove()
3058+
self.canvas.draw()
3059+
3060+
return clicks
30223061

30233062
def waitforbuttonpress(self, timeout=-1):
30243063
"""
@@ -3028,8 +3067,17 @@ def waitforbuttonpress(self, timeout=-1):
30283067
mouse button was pressed and None if no input was given within
30293068
*timeout* seconds. Negative values deactivate *timeout*.
30303069
"""
3031-
blocking_input = BlockingKeyMouseInput(self)
3032-
return blocking_input(timeout=timeout)
3070+
event = None
3071+
3072+
def handler(ev):
3073+
nonlocal event
3074+
event = ev
3075+
self.canvas.stop_event_loop()
3076+
3077+
_blocking_input.blocking_input_loop(
3078+
self, ["button_press_event", "key_press_event"], timeout, handler)
3079+
3080+
return None if event is None else event.name == "key_press_event"
30333081

30343082
def init_layoutgrid(self):
30353083
"""Initialize the layoutgrid for use in constrained_layout."""

0 commit comments

Comments
 (0)