Einführung
Dieses Labor ist ein Schritt-für-Schritt-Tutorial zum Erstellen von cross-GUI-Anwendungen mithilfe der Matplotlib-Ereignisbehandlung, um mit Objekten auf der Zeichenfläche zu interagieren. Wir werden einen Polygon-Editor erstellen, mit dem Sie die Eckpunkte verschieben, löschen und einfügen können.
Tipps für die VM
Nachdem der VM-Start abgeschlossen ist, klicken Sie in der oberen linken Ecke, um zur Registerkarte Notebook zu wechseln und Jupyter Notebook für die Übung zu öffnen.
Manchmal müssen Sie einige Sekunden warten, bis Jupyter Notebook vollständig geladen ist. Die Validierung von Vorgängen kann aufgrund von Einschränkungen in Jupyter Notebook nicht automatisiert werden.
Wenn Sie während des Lernens Probleme haben, können Sie Labby gerne fragen. Geben Sie nach der Sitzung Feedback, und wir werden das Problem für Sie prompt beheben.
Importieren der erforderlichen Bibliotheken
Wir müssen die erforderlichen Bibliotheken für den Polygon-Editor importieren.
import numpy as np
from matplotlib.artist import Artist
from matplotlib.lines import Line2D
Erstellen einer Funktion zur Berechnung der Entfernung
Wir müssen eine Funktion erstellen, die die Entfernung zwischen einem Punkt und einem Liniensegment berechnet. Diese Funktion wird später verwendet, um zu bestimmen, ob ein neuer Eckpunkt zum Polygon hinzugefügt werden soll.
def dist_point_to_segment(p, s0, s1):
"""
Berechnet die Entfernung vom Punkt *p* zum Segment (*s0*, *s1*), wobei
*p*, *s0*, *s1* ``[x, y]``-Arrays sind.
"""
s01 = s1 - s0
s0p = p - s0
if (s01 == 0).all():
return np.hypot(*s0p)
## Projektieren auf das Segment, ohne über die Segmentenden hinauszugehen.
p1 = s0 + np.clip((s0p @ s01) / (s01 @ s01), 0, 1) * s01
return np.hypot(*(p - p1))
Erstellen der PolygonInteractor-Klasse
Wir müssen die PolygonInteractor-Klasse erstellen, die die Hauptklasse für den Polygon-Editor ist. Diese Klasse wird alle Interaktionen mit dem Polygon behandeln, wie das Verschieben, Löschen und Einfügen von Eckpunkten.
class PolygonInteractor:
"""
Ein Polygon-Editor.
Tastatureingaben
't' um die Anzeige von Eckpunkten an- und auszuschalten. Wenn die Eckpunkte angezeigt werden,
können Sie sie verschieben und löschen
'd' um den Eckpunkt unterhalb des Mauszeigers zu löschen
'i' um einen Eckpunkt an der aktuellen Position einzufügen. Sie müssen sich innerhalb von
einem bestimmten Abstand (epsilon) von der Linie zwischen zwei vorhandenen Eckpunkten befinden
"""
showverts = True
epsilon = 5 ## maximale Pixelabstand, um als Treffer eines Eckpunkts zu gelten
def __init__(self, ax, poly):
if poly.figure is None:
raise RuntimeError('Sie müssen das Polygon zuerst einer Figur '
'oder einem Canvas hinzufügen, bevor Sie den Interaktor definieren')
self.ax = ax
canvas = poly.figure.canvas
self.poly = poly
x, y = zip(*self.poly.xy)
self.line = Line2D(x, y,
marker='o', markerfacecolor='r',
animated=True)
self.ax.add_line(self.line)
self.cid = self.poly.add_callback(self.poly_changed)
self._ind = None ## der aktive Eckpunkt
canvas.mpl_connect('draw_event', self.on_draw)
canvas.mpl_connect('button_press_event', self.on_button_press)
canvas.mpl_connect('key_press_event', self.on_key_press)
canvas.mpl_connect('button_release_event', self.on_button_release)
canvas.mpl_connect('motion_notify_event', self.on_mouse_move)
self.canvas = canvas
def on_draw(self, event):
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
self.ax.draw_artist(self.poly)
self.ax.draw_artist(self.line)
## Hier ist kein Blit erforderlich, da dies vor dem Aktualisieren des Bildschirms erfolgt
def poly_changed(self, poly):
"""Diese Methode wird aufgerufen, wenn das Pathpatch-Objekt geändert wird."""
## Kopieren Sie nur die Künstler-Eigenschaften auf die Linie (außer Sichtbarkeit)
vis = self.line.get_visible()
Artist.update_from(self.line, poly)
self.line.set_visible(vis) ## Verwenden Sie den Sichtbarkeitsstatus des Polygons nicht
def get_ind_under_point(self, event):
"""
Gibt den Index des Punkts zurück, der am nächsten an der Ereignisposition liegt, oder *None*,
wenn kein Punkt innerhalb von ``self.epsilon`` von der Ereignisposition entfernt ist.
"""
## Anzeige-Koordinaten
xy = np.asarray(self.poly.xy)
xyt = self.poly.get_transform().transform(xy)
xt, yt = xyt[:, 0], xyt[:, 1]
d = np.hypot(xt - event.x, yt - event.y)
indseq, = np.nonzero(d == d.min())
ind = indseq[0]
if d[ind] >= self.epsilon:
ind = None
return ind
def on_button_press(self, event):
"""Callback für Mausklick-Ereignisse."""
if not self.showverts:
return
if event.inaxes is None:
return
if event.button!= 1:
return
self._ind = self.get_ind_under_point(event)
def on_button_release(self, event):
"""Callback für Maus loslassen-Ereignisse."""
if not self.showverts:
return
if event.button!= 1:
return
self._ind = None
def on_key_press(self, event):
"""Callback für Tastatureingaben."""
if not event.inaxes:
return
if event.key == 't':
self.showverts = not self.showverts
self.line.set_visible(self.showverts)
if not self.showverts:
self._ind = None
elif event.key == 'd':
ind = self.get_ind_under_point(event)
if ind is not None:
self.poly.xy = np.delete(self.poly.xy,
ind, axis=0)
self.line.set_data(zip(*self.poly.xy))
elif event.key == 'i':
xys = self.poly.get_transform().transform(self.poly.xy)
p = event.x, event.y ## Anzeige-Koordinaten
for i in range(len(xys) - 1):
s0 = xys[i]
s1 = xys[i + 1]
d = dist_point_to_segment(p, s0, s1)
if d <= self.epsilon:
self.poly.xy = np.insert(
self.poly.xy, i+1,
[event.xdata, event.ydata],
axis=0)
self.line.set_data(zip(*self.poly.xy))
break
if self.line.stale:
self.canvas.draw_idle()
def on_mouse_move(self, event):
"""Callback für Mausbewegungen."""
if not self.showverts:
return
if self._ind is None:
return
if event.inaxes is None:
return
if event.button!= 1:
return
x, y = event.xdata, event.ydata
self.poly.xy[self._ind] = x, y
if self._ind == 0:
self.poly.xy[-1] = x, y
elif self._ind == len(self.poly.xy) - 1:
self.poly.xy[0] = x, y
self.line.set_data(zip(*self.poly.xy))
self.canvas.restore_region(self.background)
self.ax.draw_artist(self.poly)
self.ax.draw_artist(self.line)
self.canvas.blit(self.ax.bbox)
Erstellen des Polygons
Wir müssen das Polygon erstellen, das wir mit der Polygon-Klasse bearbeiten werden.
theta = np.arange(0, 2*np.pi, 0.1)
r = 1.5
xs = r * np.cos(theta)
ys = r * np.sin(theta)
poly = Polygon(np.column_stack([xs, ys]), animated=True)
Erstellen des Plots
Wir müssen den Plot erstellen und das Polygon hinzufügen.
fig, ax = plt.subplots()
ax.add_patch(poly)
p = PolygonInteractor(ax, poly)
ax.set_title('Click and drag a point to move it')
ax.set_xlim((-2, 2))
ax.set_ylim((-2, 2))
plt.show()
Zusammenfassung
In diesem Lab haben wir gelernt, wie man einen Polygon-Editor mit Hilfe der Matplotlib-Ereignisbehandlung erstellt. Wir haben eine Klasse erstellt, um alle Interaktionen mit dem Polygon zu behandeln, und wir haben die Polygon-Klasse verwendet, um das Polygon selbst zu erstellen. Anschließend haben wir den Plot erstellt und das Polygon hinzugefügt. Mit diesen Kenntnissen können Sie Ihre eigenen interaktiven Anwendungen mit Matplotlib erstellen.