Gestione eventi e picking #

Matplotlib funziona con una serie di toolkit dell'interfaccia utente (wxpython, tkinter, qt, gtk e macosx) e per supportare funzionalità come la panoramica interattiva e lo zoom delle figure, è utile agli sviluppatori disporre di un'API per interagire con la figura tramite pressioni di tasti e movimenti del mouse che è "GUI neutral", quindi non dobbiamo ripetere molto codice attraverso le diverse interfacce utente. Sebbene l'API di gestione degli eventi sia indipendente dalla GUI, si basa sul modello GTK, che è stata la prima interfaccia utente supportata da Matplotlib. Gli eventi che vengono attivati ​​sono anche un po' più ricchi rispetto a Matplotlib rispetto agli eventi della GUI standard, incluse informazioni come in cui Axessi è verificato l'evento. Gli eventi comprendono anche il sistema di coordinate Matplotlib e riportano le posizioni degli eventi sia in pixel che in coordinate dati.

Connessioni evento #

Per ricevere eventi, devi scrivere una funzione di callback e quindi connettere la tua funzione al gestore eventi, che fa parte del FigureCanvasBase. Ecco un semplice esempio che stampa la posizione del clic del mouse e quale pulsante è stato premuto:

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)

Il FigureCanvasBase.mpl_connectmetodo restituisce un ID connessione (un numero intero), che può essere utilizzato per disconnettere il callback tramite

fig.canvas.mpl_disconnect(cid)

Nota

Il canvas conserva solo riferimenti deboli ai metodi di istanza usati come callback. Pertanto, è necessario mantenere un riferimento alle istanze che possiedono tali metodi. In caso contrario, l'istanza verrà sottoposta a Garbage Collection e il callback svanirà.

Ciò non influisce sulle funzioni libere utilizzate come callback.

Ecco gli eventi a cui puoi connetterti, le istanze di classe che ti vengono inviate quando si verifica l'evento e le descrizioni degli eventi:

Nome dell'evento

Classe

Descrizione

'evento_pressione_pulsante'

MouseEvent

viene premuto il pulsante del mouse

'evento_rilascio_pulsante'

MouseEvent

pulsante del mouse viene rilasciato

'chiudi_evento'

CloseEvent

la figura è chiusa

'draw_evento'

DrawEvent

la tela è stata disegnata (ma il widget dello schermo non è ancora aggiornato)

'key_press_evento'

KeyEvent

tasto viene premuto

'evento_rilascio_chiave'

KeyEvent

la chiave viene rilasciata

'evento_notifica_movimento'

MouseEvent

mosse del mouse

'scegli_evento'

PickEvent

l'artista nella tela è selezionato

'ridimensiona_evento'

ResizeEvent

la tela della figura viene ridimensionata

'scroll_evento'

MouseEvent

la rotella di scorrimento del mouse viene ruotata

'figure_enter_event'

LocationEvent

mouse entra in una nuova figura

'figura_lascia_evento'

LocationEvent

il topo lascia una figura

'axes_enter_event'

LocationEvent

il mouse entra in un nuovo asse

'axes_leave_event'

LocationEvent

mouse lascia un assi

Nota

Quando ti connetti agli eventi 'key_press_event' e 'key_release_event', potresti riscontrare incoerenze tra i diversi toolkit dell'interfaccia utente con cui lavora Matplotlib. Ciò è dovuto a incoerenze/limitazioni del toolkit dell'interfaccia utente. La tabella seguente mostra alcuni esempi di base di ciò che potresti aspettarti di ricevere come tasti (utilizzando un layout di tastiera QWERTY) dai diversi toolkit dell'interfaccia utente, dove una virgola separa i diversi tasti:

Tasto/i premuto/i

WxPython

Qt

WebAgg

Gtk

Tkinter

macosx

Maiusc+2

Maiusc, Maiusc+2

spostare, @

spostare, @

spostare, @

spostare, @

spostare, @

Maiusc+F1

maiusc, maiusc+f1

maiusc, maiusc+f1

maiusc, maiusc+f1

maiusc, maiusc+f1

maiusc, maiusc+f1

maiusc, maiusc+f1

Spostare

spostare

spostare

spostare

spostare

spostare

spostare

Controllo

controllo

controllo

controllo

controllo

controllo

controllo

Alt

alt

alt

alt

alt

alt

alt

Alt Gr

Niente

Niente

alt

iso_level3_shift

iso_level3_shift

Blocco maiuscole

blocco maiuscole

blocco maiuscole

blocco maiuscole

blocco maiuscole

blocco maiuscole

blocco maiuscole

BLOC MAIUSC+a

maiuscolo_lock, a

maiuscolo_lock, a

maiuscolo_lock, A

maiuscolo_lock, A

maiuscolo_lock, A

maiuscolo_lock, a

un

un

un

un

un

un

un

Maiusc+a

spostamento, A

spostamento, A

spostamento, A

spostamento, A

spostamento, A

spostamento, A

BLOC MAIUSC+MAIUSC+a

maiuscolo, maiusc, A

maiuscolo, maiusc, A

maiuscolo, maiuscole, a

maiuscolo, maiuscole, a

maiuscolo, maiuscole, a

maiuscolo, maiusc, A

Ctrl+Maiusc+Alt

controllo, ctrl+maiusc, ctrl+alt

controllo, ctrl+maiusc, ctrl+meta

controllo, ctrl+maiusc, ctrl+meta

controllo, ctrl+maiusc, ctrl+meta

controllo, ctrl+maiusc, ctrl+meta

controllo, ctrl+maiusc, ctrl+alt+maiusc

Ctrl+Maiusc+a

controllo, ctrl+maiusc, ctrl+A

controllo, ctrl+maiusc, ctrl+A

controllo, ctrl+maiusc, ctrl+A

controllo, ctrl+maiusc, ctrl+A

controllo, ctrl+maiusc, ctrl+a

controllo, ctrl+maiusc, ctrl+A

F1

f1

f1

f1

f1

f1

f1

Ctrl+F1

controllo, ctrl+f1

controllo, ctrl+f1

controllo, ctrl+f1

controllo, ctrl+f1

controllo, ctrl+f1

controllo, niente

Matplotlib allega alcune richiamate alla pressione dei tasti per impostazione predefinita per l'interattività; sono documentati nella sezione Scelte rapide da tastiera di navigazione .

Attributi dell'evento #

Tutti gli eventi Matplotlib ereditano dalla classe base matplotlib.backend_bases.Event, che memorizza gli attributi:

name

il nome dell'evento

canvas

l'istanza FigureCanvas che genera l'evento

guiEvent

l'evento GUI che ha attivato l'evento Matplotlib

Gli eventi più comuni che sono il pane quotidiano della gestione degli eventi sono gli eventi di pressione/rilascio dei tasti e gli eventi di pressione/rilascio del mouse ed eventi di movimento. Le classi KeyEvente MouseEventche gestiscono questi eventi derivano entrambe dal LocationEvent, che ha i seguenti attributi

x,y

posizione x e y del mouse in pixel dalla sinistra e dal fondo dell'area di disegno

inaxes

l' Axesistanza su cui si trova il mouse, se presente; altrimenti Nessuno

xdata,ydata

posizione x e y del mouse nelle coordinate dei dati, se il mouse si trova sopra un asse

Diamo un'occhiata a un semplice esempio di tela, in cui viene creato un semplice segmento di linea ogni volta che viene premuto il mouse:

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()

Quello MouseEventche abbiamo appena usato è un LocationEvent, quindi abbiamo accesso ai dati e alle coordinate dei pixel tramite e . Oltre agli attributi, ha anche(event.x, event.y)(event.xdata, event.ydata)LocationEvent

button

il pulsante premuto: Nessuno, MouseButton, 'su' o 'giù' (su e giù sono usati per gli eventi di scorrimento)

key

il tasto premuto: Nessuno, qualsiasi carattere, 'shift', 'win' o 'control'

Esercizio rettangolo trascinabile n.

Scrivi una classe rettangolo trascinabile inizializzata con Rectangleun'istanza ma che sposterà la sua xy posizione quando viene trascinata. Suggerimento: dovrai memorizzare la xyposizione originale del rettangolo che è memorizzato come rect.xy e connetterti agli eventi di pressione, movimento e rilascio del mouse. Quando si preme il mouse, verificare se il clic si verifica sul rettangolo (vedere Rectangle.contains) e, in caso affermativo, memorizzare il rettangolo xy e la posizione del clic del mouse nelle coordinate dei dati. Nel callback dell'evento di movimento, calcola il deltax e il deltay del movimento del mouse e aggiungi quei delta all'origine del rettangolo che hai memorizzato. Il ridisegna la figura. Nell'evento di rilascio del pulsante, è sufficiente reimpostare tutti i dati di pressione del pulsante archiviati come Nessuno.

Ecco la soluzione:

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()

Credito extra : usa il blitting per rendere il disegno animato più veloce e fluido.

Soluzione di credito extra:

# 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()

Mouse entra ed esci #

Se vuoi essere avvisato quando il mouse entra o esce da una figura o da un asse, puoi collegarti agli eventi di entrata/uscita della figura/assi. Ecco un semplice esempio che cambia i colori degli assi e dello sfondo della figura su cui si trova il mouse:

"""
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()

Raccolta oggetti #

Puoi abilitare il prelievo impostando la pickerproprietà di un Artist(come Line2D, Text, Patch, Polygon, AxesImage, ecc.)

La pickerproprietà può essere impostata utilizzando vari tipi:

None

La selezione è disabilitata per questo artista (impostazione predefinita).

boolean

Se True, la selezione sarà abilitata e l'artista attiverà un evento di selezione se l'evento del mouse si trova sopra l'artista.

callable

Se picker è un callable, è una funzione fornita dall'utente che determina se l'artista viene colpito dall'evento del mouse. La firma serve a determinare l'hit test. Se l'evento del mouse è sopra l'artista, return ; è un dizionario di proprietà che diventano attributi aggiuntivi su .hit, props = picker(artist, mouseevent)hit = TruepropsPickEvent

La pickradiusproprietà dell'artista può inoltre essere impostata su un valore di tolleranza in punti (ci sono 72 punti per pollice) che determina quanto lontano può essere il mouse e attivare comunque un evento del mouse.

Dopo aver abilitato un artista per la selezione impostando la picker proprietà, è necessario connettere un gestore alla figura canvas pick_event per ottenere i callback di selezione sugli eventi di pressione del mouse. Il gestore in genere assomiglia a

def pick_handler(event):
    mouseevent = event.mouseevent
    artist = event.artist
    # now do something with this...

Il PickEventpassato al tuo callback ha sempre i seguenti attributi:

mouseevent

Il MouseEventche genera l'evento pick. Vedere event-attributes per un elenco di attributi utili sull'evento del mouse.

artist

L' Artistelemento che ha generato l'evento pick.

Inoltre, ad alcuni artisti piacciono Line2De PatchCollectionpossono allegare metadati aggiuntivi, come gli indici dei dati che soddisfano i criteri di selezione (ad esempio, tutti i punti nella linea che rientrano nella pickradiustolleranza specificata).

Esempio di selezione semplice #

Nell'esempio seguente, abilitiamo il prelievo sulla linea e impostiamo una tolleranza del raggio di prelievo in punti. La onpick funzione di callback verrà chiamata quando l'evento pick si trova all'interno della distanza di tolleranza dalla linea e ha gli indici dei vertici dei dati che si trovano all'interno della tolleranza della distanza di selezione. La nostra onpick funzione di richiamata stampa semplicemente i dati che si trovano sotto la posizione di prelievo. Artisti Matplotlib diversi possono allegare dati diversi a PickEvent. Ad esempio, Line2Dallega la proprietà ind, che sono gli indici nei dati della riga sotto il punto di selezione. Vedere Line2D.pickper i dettagli sulle PickEventproprietà della linea.

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()

Esercizio di raccolta #

Crea un set di dati di 100 matrici di 1000 numeri casuali gaussiani e calcola la media campionaria e la deviazione standard di ciascuno di essi (suggerimento: le matrici NumPy hanno un metodo medio e std) e crea un grafico indicatore xy delle 100 medie rispetto ai 100 deviazioni standard. Collega la linea creata dal comando plot all'evento pick e traccia la serie temporale originale dei dati che hanno generato i punti cliccati. Se più di un punto rientra nella tolleranza del punto cliccato, è possibile utilizzare più sottotrame per tracciare le serie temporali multiple.

Soluzione dell'esercizio:

"""
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()