Gestion des événements et préparation des commandes #
Matplotlib fonctionne avec un certain nombre de boîtes à outils d'interface utilisateur (wxpython, tkinter, qt, gtk et macosx) et afin de prendre en charge des fonctionnalités telles que le panoramique interactif et le zoom des figures, il est utile pour les développeurs d'avoir une API pour interagir avec la figure via des pressions sur les touches et des mouvements de souris qui sont "neutres à l'interface graphique" afin que nous n'ayons pas à répéter beaucoup de code sur les différentes interfaces utilisateur. Bien que l'API de gestion des événements soit indépendante de l'interface graphique, elle est basée sur le modèle GTK, qui était la première interface utilisateur prise en charge par Matplotlib. Les événements déclenchés sont également un peu plus riches vis-à-vis de Matplotlib que les événements d'interface graphique standard, y compris des informations telles que
Axes
l'événement dans lequel l'événement s'est produit. Les événements comprennent également le système de coordonnées Matplotlib et signalent les emplacements des événements à la fois en pixels et en coordonnées de données.
Connexions événementielles #
Pour recevoir des événements, vous devez écrire une fonction de rappel, puis connecter votre fonction au gestionnaire d'événements, qui fait partie du
FigureCanvasBase
. Voici un exemple simple qui imprime l'emplacement du clic de la souris et le bouton qui a été pressé :
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)
La FigureCanvasBase.mpl_connect
méthode renvoie un identifiant de connexion (un entier), qui peut être utilisé pour déconnecter le rappel via
fig.canvas.mpl_disconnect(cid)
Noter
Le canevas ne conserve que des références faibles aux méthodes d'instance utilisées comme rappels. Par conséquent, vous devez conserver une référence aux instances possédant de telles méthodes. Sinon, l'instance sera récupérée et le rappel disparaîtra.
Cela n'affecte pas les fonctions libres utilisées comme rappels.
Voici les événements auxquels vous pouvez vous connecter, les instances de classe qui vous sont renvoyées lorsque l'événement se produit et les descriptions des événements :
Nom de l'événement |
Classer |
La description |
---|---|---|
'bouton_press_event' |
le bouton de la souris est enfoncé |
|
'bouton_release_event' |
le bouton de la souris est relâché |
|
'fermer_événement' |
la figure est fermée |
|
'draw_event' |
la toile a été dessinée (mais le widget d'écran n'est pas encore mis à jour) |
|
'key_press_event' |
la touche est enfoncée |
|
'key_release_event' |
la clé est libérée |
|
'motion_notify_event' |
mouvements de souris |
|
'choisir_événement' |
l'artiste dans la toile est sélectionné |
|
'resize_event' |
la toile de la figure est redimensionnée |
|
'scroll_event' |
la molette de défilement de la souris tourne |
|
'figure_enter_event' |
la souris entre dans un nouveau chiffre |
|
'figure_leave_event' |
la souris laisse une figure |
|
'axes_enter_event' |
la souris entre dans un nouvel axe |
|
'axes_leave_event' |
la souris laisse un axes |
Noter
Lors de la connexion aux événements 'key_press_event' et 'key_release_event', vous pouvez rencontrer des incohérences entre les différents kits d'outils d'interface utilisateur avec lesquels Matplotlib fonctionne. Cela est dû à des incohérences/limitations de la boîte à outils de l'interface utilisateur. Le tableau suivant montre quelques exemples de base de ce que vous pouvez vous attendre à recevoir comme touche(s) (en utilisant une disposition de clavier QWERTY) des différents kits d'outils d'interface utilisateur, où une virgule sépare les différentes touches :
Touche(s) appuyée(s) |
WxPython |
Qt |
WebAggName |
Gtk |
Tkinter |
Mac OS X |
---|---|---|---|---|---|---|
Maj+2 |
Maj, Maj+2 |
décalage, @ |
décalage, @ |
décalage, @ |
décalage, @ |
décalage, @ |
Maj+F1 |
maj, maj+f1 |
maj, maj+f1 |
maj, maj+f1 |
maj, maj+f1 |
maj, maj+f1 |
maj, maj+f1 |
Décalage |
décalage |
décalage |
décalage |
décalage |
décalage |
décalage |
Contrôler |
contrôler |
contrôler |
contrôler |
contrôler |
contrôler |
contrôler |
Autre |
autre |
autre |
autre |
autre |
autre |
autre |
GrAlt |
Rien |
Rien |
autre |
iso_level3_shift |
iso_level3_shift |
|
Verrouillage des majuscules |
verrouillage des majuscules |
verrouillage des majuscules |
verrouillage des majuscules |
verrouillage des majuscules |
verrouillage des majuscules |
verrouillage des majuscules |
Verr Maj+a |
majuscules, un |
majuscules, un |
majuscules, A |
majuscules, A |
majuscules, A |
majuscules, un |
un |
un |
un |
un |
un |
un |
un |
Maj+a |
décalage, un |
décalage, un |
décalage, un |
décalage, un |
décalage, un |
décalage, un |
Verr Maj+Maj+a |
majuscules, maj, A |
majuscules, maj, A |
majuscules, maj, un |
majuscules, maj, un |
majuscules, maj, un |
majuscules, maj, A |
Ctrl+Maj+Alt |
contrôle, ctrl+maj, ctrl+alt |
control, ctrl+shift, ctrl+meta |
control, ctrl+shift, ctrl+meta |
control, ctrl+shift, ctrl+meta |
control, ctrl+shift, ctrl+meta |
contrôle, ctrl+maj, ctrl+alt+maj |
Ctrl+Maj+a |
contrôle, ctrl+maj, ctrl+A |
contrôle, ctrl+maj, ctrl+A |
contrôle, ctrl+maj, ctrl+A |
contrôle, ctrl+maj, ctrl+A |
contrôle, ctrl+maj, ctrl+a |
contrôle, ctrl+maj, ctrl+A |
F1 |
f1 |
f1 |
f1 |
f1 |
f1 |
f1 |
Ctrl+F1 |
contrôle, ctrl+f1 |
contrôle, ctrl+f1 |
contrôle, ctrl+f1 |
contrôle, ctrl+f1 |
contrôle, ctrl+f1 |
contrôle, Rien |
Matplotlib attache certains rappels de touches par défaut pour l'interactivité ; ils sont documentés dans la section Raccourcis clavier de navigation .
Attributs d'événement #
Tous les événements Matplotlib héritent de la classe de base
matplotlib.backend_bases.Event
, qui stocke les attributs :
name
le nom de l'événement
canvas
l'instance FigureCanvas générant l'événement
guiEvent
l'événement GUI qui a déclenché l'événement Matplotlib
Les événements les plus courants qui sont le pain quotidien de la gestion des événements sont les événements d'appui/relâchement des touches et les événements d'appui/relâchement de la souris et de mouvement. Les classes KeyEvent
et MouseEvent
qui gèrent ces événements sont toutes deux dérivées de LocationEvent, qui a les attributs suivants
x
,y
position x et y de la souris en pixels à partir de la gauche et du bas du canevas
inaxes
l'
Axes
instance sur laquelle se trouve la souris, le cas échéant ; sinon Aucunxdata
,ydata
position x et y de la souris dans les coordonnées des données, si la souris est sur un axe
Regardons un exemple simple de canevas, où un simple segment de ligne est créé chaque fois qu'une souris est pressée :
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()
Le MouseEvent
que nous venons d'utiliser est un LocationEvent
, nous avons donc accès aux données et aux coordonnées des pixels via et . En plus des attributs, il a également(event.x, event.y)
(event.xdata,
event.ydata)
LocationEvent
button
le bouton enfoncé : Aucun,
MouseButton
, 'haut' ou 'bas' (haut et bas sont utilisés pour les événements de défilement)key
la touche enfoncée : Aucun, n'importe quel caractère, 'shift', 'win' ou 'control'
Exercice rectangle déplaçable #
Écrivez une classe de rectangle déplaçable qui est initialisée avec une
Rectangle
instance mais qui déplacera son xy
emplacement lorsqu'elle sera déplacée. Astuce : vous devrez stocker l'
xy
emplacement d'origine du rectangle qui est stocké en tant que rect.xy et vous connecter aux événements d'appui, de mouvement et de relâchement de la souris. Lorsque la souris est enfoncée, vérifiez si le clic se produit sur votre rectangle (voir
Rectangle.contains
) et si c'est le cas, stockez le rectangle xy et l'emplacement du clic de la souris dans les coordonnées de données. Dans le rappel d'événement de mouvement, calculez le deltax et le deltay du mouvement de la souris et ajoutez ces deltas à l'origine du rectangle que vous avez stocké. Le redessiner la figure. Lors de l'événement de relâchement du bouton, réinitialisez simplement toutes les données d'appui sur le bouton que vous avez stockées comme Aucune.
Voici la solution :
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édit supplémentaire : utilisez le blitting pour rendre le dessin animé plus rapide et plus fluide.
Solution de crédit supplémentaire :
# 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()
Souris entrer et sortir #
Si vous souhaitez être averti lorsque la souris entre ou sort d'une figure ou d'axes, vous pouvez vous connecter aux événements d'entrée/sortie de figure/axes. Voici un exemple simple qui change les couleurs des axes et de l'arrière-plan de la figure sur lesquels se trouve la souris :
"""
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()
Cueillette d'objets #
Vous pouvez activer la sélection en définissant la picker
propriété d'un Artist
(comme Line2D
, Text
, Patch
, Polygon
, AxesImage
, etc.)
La picker
propriété peut être définie à l'aide de différents types :
None
Le picking est désactivé pour cet artiste (par défaut).
boolean
Si True, la sélection sera activée et l'artiste déclenchera un événement de sélection si l'événement de la souris est sur l'artiste.
callable
Si le sélecteur est un appelable, c'est une fonction fournie par l'utilisateur qui détermine si l'artiste est touché par l'événement de la souris. La signature est de déterminer le test de réussite. Si l'événement mouse est sur l'artiste, return ; est un dictionnaire de propriétés qui deviennent des attributs supplémentaires sur le .
hit, props = picker(artist, mouseevent)
hit = True
props
PickEvent
La propriété de l'artiste pickradius
peut en outre être définie sur une valeur de tolérance en points (il y a 72 points par pouce) qui détermine la distance à laquelle la souris peut se trouver tout en déclenchant un événement de souris.
Après avoir activé la sélection d'un artiste en définissant la picker
propriété, vous devez connecter un gestionnaire au canevas de la figure pick_event pour obtenir des rappels de sélection sur les événements de pression de la souris. Le gestionnaire ressemble généralement à
def pick_handler(event):
mouseevent = event.mouseevent
artist = event.artist
# now do something with this...
Le PickEvent
passé à votre rappel a toujours les attributs suivants :
mouseevent
Les
MouseEvent
qui génèrent l'événement pick. Voir event-attributes pour une liste d'attributs utiles sur l'événement de souris.artist
Celui
Artist
qui a généré l'événement pick.
De plus, certains artistes aiment Line2D
et PatchCollection
peuvent joindre des métadonnées supplémentaires, comme les indices des données qui répondent aux critères du sélecteur (par exemple, tous les points de la ligne qui se situent dans la pickradius
tolérance spécifiée).
Exemple de cueillette simple #
Dans l'exemple ci-dessous, nous activons la sélection sur la ligne et définissons une tolérance de rayon de sélection en points. La onpick
fonction de rappel sera appelée lorsque l'événement de sélection se situe dans la distance de tolérance de la ligne et a les indices des sommets de données qui se trouvent dans la tolérance de distance de sélection. Notre onpick
fonction de rappel imprime simplement les données qui se trouvent sous l'emplacement de prélèvement. Différents artistes Matplotlib peuvent joindre différentes données au PickEvent. Par exemple, Line2D
attache la propriété ind, qui sont les indices dans les données de ligne sous le point de sélection. Voir
Line2D.pick
pour plus de détails sur les PickEvent
propriétés de la ligne.
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()
Exercice de cueillette #
Créez un ensemble de données de 100 tableaux de 1000 nombres aléatoires gaussiens et calculez la moyenne de l'échantillon et l'écart type de chacun d'eux (indice : les tableaux NumPy ont une méthode moyenne et std) et faites un tracé de marqueur xy des 100 moyennes par rapport aux 100 écarts-types. Connectez la ligne créée par la commande plot à l'événement pick et tracez la série chronologique d'origine des données qui ont généré les points cliqués. Si plusieurs points se trouvent dans la tolérance du point sur lequel vous avez cliqué, vous pouvez utiliser plusieurs sous-parcelles pour tracer les multiples séries temporelles.
Solution d'exercice :
"""
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()