Matplotlib Polygon Editor für Cross-GUI-Anwendungen

PythonPythonBeginner
Jetzt üben

This tutorial is from open-source community. Access the source code

💡 Dieser Artikel wurde von AI-Assistenten übersetzt. Um die englische Version anzuzeigen, können Sie hier klicken

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.