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_connectMethode 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'

MouseEvent

Maustaste gedrückt wird

'button_release_event'

MouseEvent

Maustaste losgelassen

'close_event'

CloseEvent

Figur ist geschlossen

'draw_event'

DrawEvent

Leinwand wurde gezeichnet (aber Bildschirm-Widget noch nicht aktualisiert)

'key_press_event'

KeyEvent

Taste gedrückt wird

'key_release_event'

KeyEvent

Taste losgelassen

'motion_notify_event'

MouseEvent

Maus bewegt

'pick_event'

PickEvent

Künstler auf der Leinwand ausgewählt ist

'resize_event'

ResizeEvent

Die Größe der Figurenleinwand wird geändert

'scroll_event'

MouseEvent

Scrollrad der Maus wird gedreht

'figure_enter_event'

LocationEvent

Maus gibt eine neue Figur ein

'figure_leave_event'

LocationEvent

Maus hinterlässt eine Figur

'axes_enter_event'

LocationEvent

Maus gibt eine neue Achse ein

'axes_leave_event'

LocationEvent

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 KeyEventund 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 AxesInstanz, über der sich die Maus befindet, falls vorhanden; sonst keine

xdata,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 #

RectangleSchreiben Sie eine ziehbare Rechteckklasse, die mit einer Instanz initialisiert wird, aber beim Ziehen ihre xy Position verschiebt. Hinweis: Sie müssen die ursprüngliche xyPosition 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 pickerEigenschaft eines Artist(z. B. Line2D, Text, Patch, Polygon, AxesImage, usw.)

Die pickerEigenschaft 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 = TruepropsPickEvent

Die pickradiusEigenschaft 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 PickEventan 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 Artisthat das Pick-Ereignis generiert.

Line2DDarüber hinaus mögen und können bestimmte Künstler PatchCollectionzusä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 pickradiusToleranz 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 Line2Ddie ind-Eigenschaft an, die die Indizes in den Zeilendaten unter dem Auswahlpunkt sind. Siehe Line2D.pickfür Details zu den PickEventEigenschaften 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()