Event-Handling und Kommissionierung #
Matplotlib funktioniert mit einer Reihe von Benutzeroberflächen-Toolkits (wxpython, tkinter, qt, gtk und macosx) und um Funktionen wie interaktives Schwenken und Zoomen von Figuren zu unterstützen, ist es für die Entwickler hilfreich, eine API für die Interaktion mit der Figur zu haben über Tastendrücke und Mausbewegungen, die "GUI-neutral" ist, sodass wir nicht viel Code über die verschiedenen Benutzeroberflächen hinweg wiederholen müssen. Obwohl die Ereignisbehandlungs-API GUI-neutral ist, basiert sie auf dem GTK-Modell, das die erste von Matplotlib unterstützte Benutzeroberfläche war. Die ausgelösten Ereignisse sind im Vergleich zu Matplotlib auch etwas umfangreicher als Standard-GUI-Ereignisse, einschließlich Informationen darüber, in welchem
Axes
Ereignis das Ereignis aufgetreten ist. Die Ereignisse verstehen auch das Matplotlib-Koordinatensystem und melden Ereignisorte sowohl in Pixel- als auch in Datenkoordinaten.
Ereignisverbindungen #
Um Ereignisse zu empfangen, müssen Sie eine Rückruffunktion schreiben und Ihre Funktion dann mit dem Ereignismanager verbinden, der Teil der
FigureCanvasBase
. Hier ist ein einfaches Beispiel, das die Position des Mausklicks und die gedrückte Taste druckt:
fig, ax = plt.subplots()
ax.plot(np.random.rand(10))
def onclick(event):
print('%s click: button=%d, x=%d, y=%d, xdata=%f, ydata=%f' %
('double' if event.dblclick else 'single', event.button,
event.x, event.y, event.xdata, event.ydata))
cid = fig.canvas.mpl_connect('button_press_event', onclick)
Die FigureCanvasBase.mpl_connect
Methode gibt eine Verbindungs-ID (eine Ganzzahl) zurück, die zum Trennen des Rückrufs über verwendet werden kann
fig.canvas.mpl_disconnect(cid)
Notiz
Der Canvas behält nur schwache Verweise auf Instanzmethoden, die als Callbacks verwendet werden. Daher müssen Sie einen Verweis auf Instanzen beibehalten, die solche Methoden besitzen. Andernfalls wird die Instanz bereinigt und der Callback verschwindet.
Dies wirkt sich nicht auf freie Funktionen aus, die als Callbacks verwendet werden.
Hier sind die Ereignisse, zu denen Sie eine Verbindung herstellen können, die Klasseninstanzen, die an Sie zurückgesendet werden, wenn das Ereignis eintritt, und die Ereignisbeschreibungen:
Veranstaltungsname |
Klasse |
Beschreibung |
---|---|---|
'button_press_event' |
Maustaste gedrückt wird |
|
'button_release_event' |
Maustaste losgelassen |
|
'close_event' |
Figur ist geschlossen |
|
'draw_event' |
Leinwand wurde gezeichnet (aber Bildschirm-Widget noch nicht aktualisiert) |
|
'key_press_event' |
Taste gedrückt wird |
|
'key_release_event' |
Taste losgelassen |
|
'motion_notify_event' |
Maus bewegt |
|
'pick_event' |
Künstler auf der Leinwand ausgewählt ist |
|
'resize_event' |
Die Größe der Figurenleinwand wird geändert |
|
'scroll_event' |
Scrollrad der Maus wird gedreht |
|
'figure_enter_event' |
Maus gibt eine neue Figur ein |
|
'figure_leave_event' |
Maus hinterlässt eine Figur |
|
'axes_enter_event' |
Maus gibt eine neue Achse ein |
|
'axes_leave_event' |
Maus hinterlässt eine Achse |
Notiz
Wenn Sie sich mit den Ereignissen „key_press_event“ und „key_release_event“ verbinden, können Sie auf Inkonsistenzen zwischen den verschiedenen Benutzeroberflächen-Toolkits stoßen, mit denen Matplotlib arbeitet. Dies liegt an Inkonsistenzen/Einschränkungen des Benutzeroberflächen-Toolkits. Die folgende Tabelle zeigt einige grundlegende Beispiele dafür, was Sie als Taste(n) (mit einem QWERTZ-Tastaturlayout) von den verschiedenen Benutzeroberflächen-Toolkits erwarten können, wobei ein Komma verschiedene Tasten trennt:
Taste(n) gedrückt |
WxPython |
Qt |
WebAgg |
Gtk |
Tkinter |
Mac OS X |
---|---|---|---|---|---|---|
Umschalt+2 |
Shift, Shift+2 |
Wechsel, @ |
Wechsel, @ |
Wechsel, @ |
Wechsel, @ |
Wechsel, @ |
Umschalt+F1 |
Shift, Shift+F1 |
Shift, Shift+F1 |
Shift, Shift+F1 |
Shift, Shift+F1 |
Shift, Shift+F1 |
Shift, Shift+F1 |
Wechsel |
Wechsel |
Wechsel |
Wechsel |
Wechsel |
Wechsel |
Wechsel |
Kontrolle |
Kontrolle |
Kontrolle |
Kontrolle |
Kontrolle |
Kontrolle |
Kontrolle |
Alt |
alt |
alt |
alt |
alt |
alt |
alt |
Alt Gr |
Nichts |
Nichts |
alt |
iso_level3_shift |
iso_level3_shift |
|
Caps Lock |
Caps Lock |
Caps Lock |
Caps Lock |
Caps Lock |
Caps Lock |
Caps Lock |
Feststelltaste+a |
caps_lock, a |
caps_lock, a |
caps_lock, A |
caps_lock, A |
caps_lock, A |
caps_lock, a |
a |
a |
a |
a |
a |
a |
a |
Umschalt+a |
Verschiebung, a |
Verschiebung, a |
Verschiebung, a |
Verschiebung, a |
Verschiebung, a |
Verschiebung, a |
Feststelltaste+Umschalt+a |
Feststelltaste, Umschalttaste, A |
Feststelltaste, Umschalttaste, A |
caps_lock, Shift, a |
caps_lock, Shift, a |
caps_lock, Shift, a |
Feststelltaste, Umschalttaste, A |
Strg+Umschalt+Alt |
Steuerung, Strg+Shift, Strg+Alt |
Steuerung, Strg+Shift, Strg+Meta |
Steuerung, Strg+Shift, Strg+Meta |
Steuerung, Strg+Shift, Strg+Meta |
Steuerung, Strg+Shift, Strg+Meta |
Steuerung, Strg+Umschalt, Strg+Alt+Umschalt |
Strg+Umschalt+a |
Steuerung, Strg+Shift, Strg+A |
Steuerung, Strg+Shift, Strg+A |
Steuerung, Strg+Shift, Strg+A |
Steuerung, Strg+Shift, Strg+A |
Steuerung, Strg+Shift, Strg+A |
Steuerung, Strg+Shift, Strg+A |
F1 |
f1 |
f1 |
f1 |
f1 |
f1 |
f1 |
Strg+F1 |
Steuerung, Strg+F1 |
Steuerung, Strg+F1 |
Steuerung, Strg+F1 |
Steuerung, Strg+F1 |
Steuerung, Strg+F1 |
Kontrolle, nichts |
Matplotlib hängt standardmäßig einige Tastendruck-Callbacks für Interaktivität an; Sie sind im Abschnitt Tastenkombinationen für die Navigation dokumentiert.
Ereignisattribute #
Alle Matplotlib-Ereignisse erben von der Basisklasse
matplotlib.backend_bases.Event
, die die Attribute speichert:
name
der Veranstaltungsname
canvas
die FigureCanvas-Instanz, die das Ereignis generiert
guiEvent
das GUI-Ereignis, das das Matplotlib-Ereignis ausgelöst hat
Die häufigsten Ereignisse, die das Brot und die Butter der Ereignisbehandlung sind, sind Tastendruck-/loslassen-Ereignisse und Mausdrücken/loslassen- und Bewegungsereignisse. Die Klassen KeyEvent
und MouseEvent
, die diese Ereignisse behandeln, sind beide von LocationEvent abgeleitet, das die folgenden Attribute hat
x
,y
x- und y-Position der Maus in Pixel vom linken und unteren Rand der Leinwand
inaxes
die
Axes
Instanz, über der sich die Maus befindet, falls vorhanden; sonst keinexdata
,ydata
x- und y-Position der Maus in Datenkoordinaten, wenn sich die Maus über einer Achse befindet
Schauen wir uns ein einfaches Beispiel einer Leinwand an, bei der jedes Mal, wenn eine Maus gedrückt wird, ein einfaches Liniensegment erstellt wird:
from matplotlib import pyplot as plt
class LineBuilder:
def __init__(self, line):
self.line = line
self.xs = list(line.get_xdata())
self.ys = list(line.get_ydata())
self.cid = line.figure.canvas.mpl_connect('button_press_event', self)
def __call__(self, event):
print('click', event)
if event.inaxes!=self.line.axes: return
self.xs.append(event.xdata)
self.ys.append(event.ydata)
self.line.set_data(self.xs, self.ys)
self.line.figure.canvas.draw()
fig, ax = plt.subplots()
ax.set_title('click to build line segments')
line, = ax.plot([0], [0]) # empty line
linebuilder = LineBuilder(line)
plt.show()
Das MouseEvent
, was wir gerade verwendet haben, ist a LocationEvent
, also haben wir Zugriff auf die Daten und Pixelkoordinaten über und . Zusätzlich zu den Attributen hat es auch(event.x, event.y)
(event.xdata,
event.ydata)
LocationEvent
button
die gedrückte Taste: None,
MouseButton
, 'up' oder 'down' (oben und unten werden für Scroll-Ereignisse verwendet)key
die gedrückte Taste: Keine, beliebiges Zeichen, 'shift', 'win' oder 'control'
Übung zum ziehbaren Rechteck #
Rectangle
Schreiben Sie eine ziehbare Rechteckklasse, die mit einer Instanz initialisiert
wird, aber beim Ziehen ihre xy
Position verschiebt. Hinweis: Sie müssen die ursprüngliche
xy
Position des Rechtecks speichern, die als rect.xy gespeichert ist, und eine Verbindung zu den Mausereignissen „Press“, „Motion“ und „Loslassen“ herstellen. Wenn die Maus gedrückt wird, überprüfen Sie, ob der Klick über Ihrem Rechteck erfolgt (siehe
Rectangle.contains
), und wenn ja, speichern Sie das Rechteck xy und die Position des Mausklicks in Datenkoordinaten. Berechnen Sie im Rückruf des Bewegungsereignisses Deltax und Deltay der Mausbewegung und addieren Sie diese Deltas zum Ursprung des gespeicherten Rechtecks. Die Figur neu zeichnen. Setzen Sie beim Tastenfreigabeereignis einfach alle Tastendruckdaten zurück, die Sie als Keine gespeichert haben.
Hier ist die Lösung:
import numpy as np
import matplotlib.pyplot as plt
class DraggableRectangle:
def __init__(self, rect):
self.rect = rect
self.press = None
def connect(self):
"""Connect to all the events we need."""
self.cidpress = self.rect.figure.canvas.mpl_connect(
'button_press_event', self.on_press)
self.cidrelease = self.rect.figure.canvas.mpl_connect(
'button_release_event', self.on_release)
self.cidmotion = self.rect.figure.canvas.mpl_connect(
'motion_notify_event', self.on_motion)
def on_press(self, event):
"""Check whether mouse is over us; if so, store some data."""
if event.inaxes != self.rect.axes:
return
contains, attrd = self.rect.contains(event)
if not contains:
return
print('event contains', self.rect.xy)
self.press = self.rect.xy, (event.xdata, event.ydata)
def on_motion(self, event):
"""Move the rectangle if the mouse is over us."""
if self.press is None or event.inaxes != self.rect.axes:
return
(x0, y0), (xpress, ypress) = self.press
dx = event.xdata - xpress
dy = event.ydata - ypress
# print(f'x0={x0}, xpress={xpress}, event.xdata={event.xdata}, '
# f'dx={dx}, x0+dx={x0+dx}')
self.rect.set_x(x0+dx)
self.rect.set_y(y0+dy)
self.rect.figure.canvas.draw()
def on_release(self, event):
"""Clear button press information."""
self.press = None
self.rect.figure.canvas.draw()
def disconnect(self):
"""Disconnect all callbacks."""
self.rect.figure.canvas.mpl_disconnect(self.cidpress)
self.rect.figure.canvas.mpl_disconnect(self.cidrelease)
self.rect.figure.canvas.mpl_disconnect(self.cidmotion)
fig, ax = plt.subplots()
rects = ax.bar(range(10), 20*np.random.rand(10))
drs = []
for rect in rects:
dr = DraggableRectangle(rect)
dr.connect()
drs.append(dr)
plt.show()
Extra -Punkt : Verwenden Sie Blitting, um die animierte Zeichnung schneller und flüssiger zu machen.
Zusatzkreditlösung:
# Draggable rectangle with blitting.
import numpy as np
import matplotlib.pyplot as plt
class DraggableRectangle:
lock = None # only one can be animated at a time
def __init__(self, rect):
self.rect = rect
self.press = None
self.background = None
def connect(self):
"""Connect to all the events we need."""
self.cidpress = self.rect.figure.canvas.mpl_connect(
'button_press_event', self.on_press)
self.cidrelease = self.rect.figure.canvas.mpl_connect(
'button_release_event', self.on_release)
self.cidmotion = self.rect.figure.canvas.mpl_connect(
'motion_notify_event', self.on_motion)
def on_press(self, event):
"""Check whether mouse is over us; if so, store some data."""
if (event.inaxes != self.rect.axes
or DraggableRectangle.lock is not None):
return
contains, attrd = self.rect.contains(event)
if not contains:
return
print('event contains', self.rect.xy)
self.press = self.rect.xy, (event.xdata, event.ydata)
DraggableRectangle.lock = self
# draw everything but the selected rectangle and store the pixel buffer
canvas = self.rect.figure.canvas
axes = self.rect.axes
self.rect.set_animated(True)
canvas.draw()
self.background = canvas.copy_from_bbox(self.rect.axes.bbox)
# now redraw just the rectangle
axes.draw_artist(self.rect)
# and blit just the redrawn area
canvas.blit(axes.bbox)
def on_motion(self, event):
"""Move the rectangle if the mouse is over us."""
if (event.inaxes != self.rect.axes
or DraggableRectangle.lock is not self):
return
(x0, y0), (xpress, ypress) = self.press
dx = event.xdata - xpress
dy = event.ydata - ypress
self.rect.set_x(x0+dx)
self.rect.set_y(y0+dy)
canvas = self.rect.figure.canvas
axes = self.rect.axes
# restore the background region
canvas.restore_region(self.background)
# redraw just the current rectangle
axes.draw_artist(self.rect)
# blit just the redrawn area
canvas.blit(axes.bbox)
def on_release(self, event):
"""Clear button press information."""
if DraggableRectangle.lock is not self:
return
self.press = None
DraggableRectangle.lock = None
# turn off the rect animation property and reset the background
self.rect.set_animated(False)
self.background = None
# redraw the full figure
self.rect.figure.canvas.draw()
def disconnect(self):
"""Disconnect all callbacks."""
self.rect.figure.canvas.mpl_disconnect(self.cidpress)
self.rect.figure.canvas.mpl_disconnect(self.cidrelease)
self.rect.figure.canvas.mpl_disconnect(self.cidmotion)
fig, ax = plt.subplots()
rects = ax.bar(range(10), 20*np.random.rand(10))
drs = []
for rect in rects:
dr = DraggableRectangle(rect)
dr.connect()
drs.append(dr)
plt.show()
Maus eingeben und verlassen #
Wenn Sie benachrichtigt werden möchten, wenn die Maus eine Figur oder eine Achse betritt oder verlässt, können Sie eine Verbindung zu den Figuren/Achsen betreten/verlassen-Ereignissen herstellen. Hier ist ein einfaches Beispiel, das die Farben der Achsen und des Figurenhintergrunds ändert, über denen sich die Maus befindet:
"""
Illustrate the figure and axes enter and leave events by changing the
frame colors on enter and leave
"""
import matplotlib.pyplot as plt
def enter_axes(event):
print('enter_axes', event.inaxes)
event.inaxes.patch.set_facecolor('yellow')
event.canvas.draw()
def leave_axes(event):
print('leave_axes', event.inaxes)
event.inaxes.patch.set_facecolor('white')
event.canvas.draw()
def enter_figure(event):
print('enter_figure', event.canvas.figure)
event.canvas.figure.patch.set_facecolor('red')
event.canvas.draw()
def leave_figure(event):
print('leave_figure', event.canvas.figure)
event.canvas.figure.patch.set_facecolor('grey')
event.canvas.draw()
fig1, axs = plt.subplots(2)
fig1.suptitle('mouse hover over figure or axes to trigger events')
fig1.canvas.mpl_connect('figure_enter_event', enter_figure)
fig1.canvas.mpl_connect('figure_leave_event', leave_figure)
fig1.canvas.mpl_connect('axes_enter_event', enter_axes)
fig1.canvas.mpl_connect('axes_leave_event', leave_axes)
fig2, axs = plt.subplots(2)
fig2.suptitle('mouse hover over figure or axes to trigger events')
fig2.canvas.mpl_connect('figure_enter_event', enter_figure)
fig2.canvas.mpl_connect('figure_leave_event', leave_figure)
fig2.canvas.mpl_connect('axes_enter_event', enter_axes)
fig2.canvas.mpl_connect('axes_leave_event', leave_axes)
plt.show()
Objektauswahl #
Sie können die Auswahl aktivieren, indem Sie die picker
Eigenschaft eines Artist
(z. B. Line2D
, Text
, Patch
, Polygon
, AxesImage
, usw.)
Die picker
Eigenschaft kann mit verschiedenen Typen gesetzt werden:
None
Die Auswahl ist für diesen Künstler deaktiviert (Standard).
boolean
Bei True wird die Auswahl aktiviert und der Künstler löst ein Auswahlereignis aus, wenn sich das Mausereignis über dem Künstler befindet.
callable
Wenn picker aufrufbar ist, handelt es sich um eine vom Benutzer bereitgestellte Funktion, die bestimmt, ob der Künstler von dem Mausereignis getroffen wird. Die Signatur dient zur Ermittlung des Treffertests. Wenn sich das Mausereignis über dem Künstler befindet, geben Sie ; ist ein Wörterbuch von Eigenschaften, die zu zusätzlichen Attributen auf der .
hit, props = picker(artist, mouseevent)
hit = True
props
PickEvent
Die pickradius
Eigenschaft des Künstlers kann zusätzlich auf einen Toleranzwert in Punkten eingestellt werden (es gibt 72 Punkte pro Zoll), der bestimmt, wie weit die Maus sein darf und trotzdem ein Mausereignis auslösen kann.
Nachdem Sie einen Künstler für die Auswahl aktiviert haben, indem Sie die picker
Eigenschaft festgelegt haben, müssen Sie einen Handler mit der Abbildungsleinwand pick_event verbinden, um Auswahlrückrufe bei Mausklickereignissen zu erhalten. Der Handler sieht normalerweise so aus
def pick_handler(event):
mouseevent = event.mouseevent
artist = event.artist
# now do something with this...
Die PickEvent
an Ihren Callback übergebene hat immer die folgenden Attribute:
mouseevent
Die
MouseEvent
, die das Pick-Ereignis generieren. Unter event -attributes finden Sie eine Liste nützlicher Attribute für das Mausereignis.artist
Das
Artist
hat das Pick-Ereignis generiert.
Line2D
Darüber hinaus mögen und können bestimmte Künstler PatchCollection
zusätzliche Metadaten anhängen, wie die Indizes der Daten, die die Auswahlkriterien erfüllen (z. B. alle Punkte in der Linie, die innerhalb der angegebenen pickradius
Toleranz liegen).
Einfaches Auswahlbeispiel #
Im folgenden Beispiel aktivieren wir das Picken auf der Linie und legen eine Pickradiustoleranz in Punkten fest. Die onpick
Callback-Funktion wird aufgerufen, wenn das Auswahlereignis innerhalb der Toleranzentfernung von der Linie liegt, und hat die Indizes der Dateneckpunkte, die innerhalb der Auswahlentfernungstoleranz liegen. Unsere onpick
Callback-Funktion druckt einfach die Daten aus, die unter dem Pick-Standort stehen. Verschiedene Matplotlib-Künstler können unterschiedliche Daten an das PickEvent anhängen. Fügt beispielsweise Line2D
die ind-Eigenschaft an, die die Indizes in den Zeilendaten unter dem Auswahlpunkt sind. Siehe
Line2D.pick
für Details zu den PickEvent
Eigenschaften der Linie.
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.set_title('click on points')
line, = ax.plot(np.random.rand(100), 'o',
picker=True, pickradius=5) # 5 points tolerance
def onpick(event):
thisline = event.artist
xdata = thisline.get_xdata()
ydata = thisline.get_ydata()
ind = event.ind
points = tuple(zip(xdata[ind], ydata[ind]))
print('onpick points:', points)
fig.canvas.mpl_connect('pick_event', onpick)
plt.show()
Pflückübung #
Erstellen Sie einen Datensatz von 100 Arrays mit 1000 Gaußschen Zufallszahlen und berechnen Sie den Stichprobenmittelwert und die Standardabweichung von jeder von ihnen (Hinweis: NumPy-Arrays haben eine Mittelwert- und Standardmethode) und erstellen Sie ein xy-Markierungsdiagramm der 100 Mittelwerte gegenüber den 100 Standardabweichungen. Verbinden Sie die durch den plot-Befehl erstellte Linie mit dem pick-Ereignis und plotten Sie die ursprüngliche Zeitreihe der Daten, die die angeklickten Punkte erzeugt haben. Wenn mehr als ein Punkt innerhalb der Toleranz des angeklickten Punkts liegt, können Sie mehrere Subplots verwenden, um die mehreren Zeitreihen zu plotten.
Übungslösung:
"""
Compute the mean and stddev of 100 data sets and plot mean vs. stddev.
When you click on one of the (mean, stddev) points, plot the raw dataset
that generated that point.
"""
import numpy as np
import matplotlib.pyplot as plt
X = np.random.rand(100, 1000)
xs = np.mean(X, axis=1)
ys = np.std(X, axis=1)
fig, ax = plt.subplots()
ax.set_title('click on point to plot time series')
line, = ax.plot(xs, ys, 'o', picker=True, pickradius=5) # 5 points tolerance
def onpick(event):
if event.artist != line:
return
n = len(event.ind)
if not n:
return
fig, axs = plt.subplots(n, squeeze=False)
for dataind, ax in zip(event.ind, axs.flat):
ax.plot(X[dataind])
ax.text(0.05, 0.9,
f"$\\mu$={xs[dataind]:1.3f}\n$\\sigma$={ys[dataind]:1.3f}",
transform=ax.transAxes, verticalalignment='top')
ax.set_ylim(-0.5, 1.5)
fig.show()
return True
fig.canvas.mpl_connect('pick_event', onpick)
plt.show()