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é
Axes
ocurrió 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_connect
mé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' |
se presiona el botón del ratón |
|
'button_release_event' |
se suelta el botón del ratón |
|
'cerrar_evento' |
la figura esta cerrada |
|
'draw_event' |
se ha dibujado el lienzo (pero el widget de pantalla aún no se ha actualizado) |
|
'key_press_event' |
se presiona la tecla |
|
'key_release_event' |
se suelta la tecla |
|
'movimiento_notificar_evento' |
movimientos del ratón |
|
'pick_event' |
se selecciona el artista en el lienzo |
|
'redimensionar_evento' |
el lienzo de la figura se redimensiona |
|
'scroll_event' |
la rueda de desplazamiento del ratón está girada |
|
'figura_ingresar_evento' |
el mouse ingresa una nueva figura |
|
'figure_leave_event' |
ratón deja una figura |
|
'ejes_enter_event' |
el mouse ingresa un nuevo eje |
|
'ejes_leave_event' |
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 KeyEvent
y MouseEvent
que 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
Axes
instancia sobre la que se encuentra el mouse, si corresponde; más Ningunoxdata
,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 MouseEvent
que 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
Rectangle
instancia pero moverá su xy
ubicación cuando se arrastre. Sugerencia: deberá almacenar la
xy
ubicació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 picker
propiedad de un Artist
(como Line2D
, Text
, Patch
, Polygon
, AxesImage
, etc.)
La picker
propiedad 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 = True
props
PickEvent
La pickradius
propiedad 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 PickEvent
pasado a su devolución de llamada siempre tiene los siguientes atributos:
mouseevent
MouseEvent
que generan el evento de selección . Consulte atributos de eventos para obtener una lista de atributos útiles en el evento del mouse.artist
El
Artist
que generó el evento de selección.
Además, a ciertos artistas les gusta Line2D
y PatchCollection
pueden 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 pickradius
tolerancia 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, Line2D
adjunta la propiedad ind, que son los índices en los datos de línea debajo del punto de selección. Consulte
Line2D.pick
para obtener detalles sobre las PickEvent
propiedades 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()