Manejo de eventos y recolección #

Matplotlib funciona con una serie de kits de herramientas de interfaz de usuario (wxpython, tkinter, qt, gtk y macosx) y para admitir funciones como la panorámica interactiva y el zoom de figuras, es útil para los desarrolladores tener una API para interactuar con la figura. a través de pulsaciones de teclas y movimientos del mouse que es "GUI neutral" para que no tengamos que repetir una gran cantidad de código en las diferentes interfaces de usuario. Aunque la API de manejo de eventos es GUI neutral, se basa en el modelo GTK, que fue la primera interfaz de usuario admitida por Matplotlib. Los eventos que se activan también son un poco más ricos con respecto a Matplotlib que los eventos estándar de la GUI, incluida información como en qué Axesocurrió el evento. Los eventos también comprenden el sistema de coordenadas de Matplotlib e informan las ubicaciones de los eventos tanto en píxeles como en coordenadas de datos.

Conexiones de eventos #

Para recibir eventos, debe escribir una función de devolución de llamada y luego conectar su función al administrador de eventos, que es parte del FigureCanvasBase. Aquí hay un ejemplo simple que imprime la ubicación del clic del mouse y qué botón se presionó:

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)

El FigureCanvasBase.mpl_connectmétodo devuelve una identificación de conexión (un número entero), que se puede usar para desconectar la devolución de llamada a través de

fig.canvas.mpl_disconnect(cid)

Nota

El lienzo conserva solo referencias débiles a los métodos de instancia utilizados como devoluciones de llamada. Por lo tanto, debe conservar una referencia a las instancias que poseen dichos métodos. De lo contrario, la instancia se recolectará como basura y la devolución de llamada desaparecerá.

Esto no afecta a las funciones gratuitas utilizadas como devolución de llamadas.

Estos son los eventos a los que puede conectarse, las instancias de clase que se le envían cuando ocurre el evento y las descripciones de los eventos:

Nombre del evento

Clase

Descripción

'button_press_event'

MouseEvent

se presiona el botón del ratón

'button_release_event'

MouseEvent

se suelta el botón del ratón

'cerrar_evento'

CloseEvent

la figura esta cerrada

'draw_event'

DrawEvent

se ha dibujado el lienzo (pero el widget de pantalla aún no se ha actualizado)

'key_press_event'

KeyEvent

se presiona la tecla

'key_release_event'

KeyEvent

se suelta la tecla

'movimiento_notificar_evento'

MouseEvent

movimientos del ratón

'pick_event'

PickEvent

se selecciona el artista en el lienzo

'redimensionar_evento'

ResizeEvent

el lienzo de la figura se redimensiona

'scroll_event'

MouseEvent

la rueda de desplazamiento del ratón está girada

'figura_ingresar_evento'

LocationEvent

el mouse ingresa una nueva figura

'figure_leave_event'

LocationEvent

ratón deja una figura

'ejes_enter_event'

LocationEvent

el mouse ingresa un nuevo eje

'ejes_leave_event'

LocationEvent

el ratón deja un hacha

Nota

Al conectarse a los eventos 'key_press_event' y 'key_release_event', es posible que encuentre inconsistencias entre los diferentes kits de herramientas de interfaz de usuario con los que trabaja Matplotlib. Esto se debe a las incoherencias/limitaciones del conjunto de herramientas de la interfaz de usuario. La siguiente tabla muestra algunos ejemplos básicos de lo que puede esperar recibir como tecla(s) (usando un diseño de teclado QWERTY) de los diferentes kits de herramientas de interfaz de usuario, donde una coma separa las diferentes teclas:

Tecla(s) presionadas

WxPython

Qt

WebAgg

Gtk

Tkinter

Mac OS X

Mayús+2

cambio, cambio+2

cambio, @

cambio, @

cambio, @

cambio, @

cambio, @

Mayús+F1

Mayús, Mayús+F1

Mayús, Mayús+F1

Mayús, Mayús+F1

Mayús, Mayús+F1

Mayús, Mayús+F1

Mayús, Mayús+F1

Cambio

cambio

cambio

cambio

cambio

cambio

cambio

Control

control

control

control

control

control

control

alternativa

alternativa

alternativa

alternativa

alternativa

alternativa

alternativa

Alt Gr

Nada

Nada

alternativa

iso_level3_shift

iso_level3_shift

Bloq Mayús

Bloq Mayús

Bloq Mayús

Bloq Mayús

Bloq Mayús

Bloq Mayús

Bloq Mayús

Bloq Mayús+a

bloqueo de mayúsculas, un

bloqueo de mayúsculas, un

Bloq Mayús, A

Bloq Mayús, A

Bloq Mayús, A

bloqueo de mayúsculas, un

a

a

a

a

a

a

a

Mayús+a

cambio, A

cambio, A

cambio, A

cambio, A

cambio, A

cambio, A

Bloq Mayús+Mayús+a

bloqueo de mayúsculas, mayúsculas, A

bloqueo de mayúsculas, mayúsculas, A

bloqueo de mayúsculas, mayúsculas, un

bloqueo de mayúsculas, mayúsculas, un

bloqueo de mayúsculas, mayúsculas, un

bloqueo de mayúsculas, mayúsculas, A

Ctrl+Mayús+Alt

controlar, ctrl+shift, ctrl+alt

controlar, ctrl+shift, ctrl+meta

controlar, ctrl+shift, ctrl+meta

controlar, ctrl+shift, ctrl+meta

controlar, ctrl+shift, ctrl+meta

control, ctrl+shift, ctrl+alt+shift

Ctrl+Mayús+a

controlar, ctrl+shift, ctrl+A

controlar, ctrl+shift, ctrl+A

controlar, ctrl+shift, ctrl+A

controlar, ctrl+shift, ctrl+A

controlar, ctrl+shift, ctrl+a

controlar, ctrl+shift, ctrl+A

F1

f1

f1

f1

f1

f1

f1

Ctrl+F1

controlar, ctrl+f1

controlar, ctrl+f1

controlar, ctrl+f1

controlar, ctrl+f1

controlar, ctrl+f1

controlar, nada

Matplotlib adjunta algunas devoluciones de llamada de pulsación de teclas de forma predeterminada para la interactividad; están documentados en la sección de métodos abreviados de teclado de navegación .

Atributos de evento #

Todos los eventos de Matplotlib se heredan de la clase base matplotlib.backend_bases.Event, que almacena los atributos:

name

el nombre del evento

canvas

la instancia de FigureCanvas que genera el evento

guiEvent

el evento GUI que desencadenó el evento Matplotlib

Los eventos más comunes que son el pan y la mantequilla del manejo de eventos son los eventos de presionar/soltar teclas y los eventos de presionar/soltar del mouse y de movimiento. Las clases KeyEventy MouseEventque manejan estos eventos se derivan de LocationEvent, que tiene los siguientes atributos

x,y

posición x e y del mouse en píxeles desde la izquierda y la parte inferior del lienzo

inaxes

la Axesinstancia sobre la que se encuentra el mouse, si corresponde; más Ninguno

xdata,ydata

posición x e y del ratón en coordenadas de datos, si el ratón está sobre un eje

Veamos un ejemplo simple de un lienzo, donde se crea un segmento de línea simple cada vez que se presiona el 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()

El MouseEventque acabamos de usar es un LocationEvent, por lo que tenemos acceso a los datos y las coordenadas de píxeles a través de y . Además de los atributos, también tiene(event.x, event.y)(event.xdata, event.ydata)LocationEvent

button

el botón presionado: Ninguno, MouseButton, 'arriba' o 'abajo' (arriba y abajo se usan para eventos de desplazamiento)

key

la tecla presionada: Ninguno, cualquier carácter, 'shift', 'win' o 'control'

Ejercicio de rectángulo arrastrable #

Escriba una clase de rectángulo arrastrable que se inicialice con una Rectangleinstancia pero moverá su xy ubicación cuando se arrastre. Sugerencia: deberá almacenar la xyubicación original del rectángulo que se almacena como rect.xy y conectarse a los eventos de presionar, mover y soltar el mouse. Cuando se presiona el mouse, verifique si el clic ocurre sobre su rectángulo (vea Rectangle.contains) y, si es así, almacene el rectángulo xy y la ubicación del clic del mouse en las coordenadas de datos. En la devolución de llamada del evento de movimiento, calcule el deltax y el deltay del movimiento del mouse y agregue esos deltas al origen del rectángulo que almacenó. El redibujar la figura. En el evento de liberación del botón, simplemente restablezca todos los datos de presión del botón que almacenó como Ninguno.

Aquí está la solución:

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

Crédito adicional : use blitting para hacer que el dibujo animado sea más rápido y suave.

Solución de crédito 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()

Ratón entrar y salir #

Si desea recibir una notificación cuando el mouse ingresa o sale de una figura o ejes, puede conectarse a los eventos de entrada/salida de figuras/ejes. Aquí hay un ejemplo simple que cambia los colores de los ejes y el fondo de la figura sobre el que se encuentra el 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()

Selección de objetos #

Puede habilitar la selección configurando la pickerpropiedad de un Artist(como Line2D, Text, Patch, Polygon, AxesImage, etc.)

La pickerpropiedad se puede configurar usando varios tipos:

None

La selección está deshabilitada para este artista (predeterminado).

boolean

Si es Verdadero, se habilitará la selección y el artista activará un evento de selección si el evento del mouse está sobre el artista.

callable

Si el selector es invocable, es una función proporcionada por el usuario que determina si el evento del mouse golpea al artista. La firma es para determinar la prueba de acierto. Si el evento del mouse está sobre el artista, devuelve ; es un diccionario de propiedades que se convierten en atributos adicionales en el .hit, props = picker(artist, mouseevent)hit = TruepropsPickEvent

La pickradiuspropiedad del artista también se puede establecer en un valor de tolerancia en puntos (hay 72 puntos por pulgada) que determina qué tan lejos puede estar el mouse y aun así desencadenar un evento de mouse.

Una vez que haya habilitado a un artista para elegir mediante la configuración de la picker propiedad, debe conectar un controlador al lienzo de la figura pick_event para obtener devoluciones de llamada de selección en eventos de presión del mouse. El controlador normalmente se ve como

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

El PickEventpasado a su devolución de llamada siempre tiene los siguientes atributos:

mouseevent

MouseEventque generan el evento de selección . Consulte atributos de eventos para obtener una lista de atributos útiles en el evento del mouse.

artist

El Artistque generó el evento de selección.

Además, a ciertos artistas les gusta Line2Dy PatchCollectionpueden adjuntar metadatos adicionales, como los índices de los datos que cumplen con los criterios del selector (por ejemplo, todos los puntos en la línea que están dentro de la pickradiustolerancia especificada).

Ejemplo de selección simple #

En el siguiente ejemplo, habilitamos la selección en la línea y establecemos una tolerancia de radio de selección en puntos. Se onpick llamará a la función de devolución de llamada cuando el evento de selección esté dentro de la distancia de tolerancia desde la línea y tenga los índices de los vértices de datos que están dentro de la tolerancia de distancia de selección. Nuestra onpick función de devolución de llamada simplemente imprime los datos que se encuentran debajo de la ubicación de selección. Diferentes artistas de Matplotlib pueden adjuntar diferentes datos al PickEvent. Por ejemplo, Line2Dadjunta la propiedad ind, que son los índices en los datos de línea debajo del punto de selección. Consulte Line2D.pickpara obtener detalles sobre las PickEventpropiedades de la línea.

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

Ejercicio de selección #

Cree un conjunto de datos de 100 matrices de 1000 números aleatorios gaussianos y calcule la media de la muestra y la desviación estándar de cada uno de ellos (pista: las matrices NumPy tienen un método medio y estándar) y haga un gráfico de marcador xy de las 100 medias frente a las 100 desviaciones estandar. Conecte la línea creada por el comando de trazado al evento de selección y trace la serie de tiempo original de los datos que generaron los puntos en los que se hizo clic. Si hay más de un punto dentro de la tolerancia del punto en el que se hizo clic, puede usar múltiples subgráficos para trazar varias series de tiempo.

Solución del ejercicio:

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