Éditeur de polygone Matplotlib pour les applications multi-interface graphique

PythonPythonBeginner
Pratiquer maintenant

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

💡 Ce tutoriel est traduit par l'IA à partir de la version anglaise. Pour voir la version originale, vous pouvez cliquer ici

Introduction

Ce laboratoire est un tutoriel étape par étape sur la manière de construire des applications multi-interface graphique en utilisant la gestion d'événements de Matplotlib pour interagir avec les objets sur le canevas. Nous allons créer un éditeur de polygone qui vous permettra de déplacer, de supprimer et d'insérer des sommets.

Conseils sur la machine virtuelle

Une fois le démarrage de la machine virtuelle terminé, cliquez dans le coin supérieur gauche pour basculer vers l'onglet Carnet de notes pour accéder au carnet Jupyter Notebook pour pratiquer.

Parfois, vous devrez peut-être attendre quelques secondes pour que le carnet Jupyter Notebook ait fini de charger. La validation des opérations ne peut pas être automatisée en raison des limites du carnet Jupyter Notebook.

Si vous rencontrez des problèmes pendant l'apprentissage, n'hésitez pas à demander à Labby. Donnez votre feedback après la session, et nous résoudrons rapidement le problème pour vous.

Importez les bibliothèques requises

Nous devons importer les bibliothèques nécessaires pour l'éditeur de polygone.

import numpy as np
from matplotlib.artist import Artist
from matplotlib.lines import Line2D

Créez une fonction pour calculer la distance

Nous devons créer une fonction qui calcule la distance entre un point et un segment de ligne. Cette fonction sera utilisée plus tard pour déterminer si un nouveau sommet doit être ajouté au polygone.

def dist_point_to_segment(p, s0, s1):
    """
    Obtenez la distance entre le point *p* et le segment (*s0*, *s1*), où
    *p*, *s0*, *s1* sont des tableaux ``[x, y]``.
    """
    s01 = s1 - s0
    s0p = p - s0
    if (s01 == 0).all():
        return np.hypot(*s0p)
    ## Projetez sur le segment, sans dépasser les extrémités du segment.
    p1 = s0 + np.clip((s0p @ s01) / (s01 @ s01), 0, 1) * s01
    return np.hypot(*(p - p1))

Créez la classe PolygonInteractor

Nous devons créer la classe PolygonInteractor, qui est la classe principale pour l'éditeur de polygone. Cette classe gérera toutes les interactions avec le polygone, telles que le déplacement, la suppression et l'insertion de sommets.

class PolygonInteractor:
    """
    Un éditeur de polygone.

    Liaisons de touches

      't' bascule les marqueurs de sommet sur et off. Lorsque les marqueurs de sommet sont activés,
          vous pouvez les déplacer, les supprimer

      'd' supprime le sommet sous le point

      'i' insère un sommet au point. Vous devez être à une distance epsilon de
          la ligne reliant deux sommets existants

    """

    showverts = True
    epsilon = 5  ## distance maximale en pixels pour considérer qu'un sommet est touché

    def __init__(self, ax, poly):
        if poly.figure is None:
            raise RuntimeError('Vous devez d\'abord ajouter le polygone à une figure '
                               'ou un canevas avant de définir l\'interacteur')
        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  ## le sommet actif

        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)
        ## pas besoin de blit ici, cela se produira avant que l'écran ne soit
        ## mis à jour

    def poly_changed(self, poly):
        """Cette méthode est appelée chaque fois que l'objet pathpatch est appelé."""
        ## seulement copiez les propriétés de l'artiste vers la ligne (sauf la visibilité)
        vis = self.line.get_visible()
        Artist.update_from(self.line, poly)
        self.line.set_visible(vis)  ## n'utilisez pas l'état de visibilité du polygone

    def get_ind_under_point(self, event):
        """
        Retourne l'index du point le plus proche de la position de l'événement ou *None*
        si aucun point n'est à une distance inférieure à ``self.epsilon`` de la position de l'événement.
        """
        ## coordonnées d'affichage
        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 pour les pressions de bouton de souris."""
        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 pour les relâchements de bouton de souris."""
        if not self.showverts:
            return
        if event.button!= 1:
            return
        self._ind = None

    def on_key_press(self, event):
        """Callback pour les pressions de touches."""
        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  ## coordonnées d'affichage
            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 pour les déplacements de souris."""
        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)

Créez le polygone

Nous devons créer le polygone que nous allons éditer en utilisant la classe Polygon.

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)

Créez la figure

Nous devons créer la figure et y ajouter le polygone.

fig, ax = plt.subplots()
ax.add_patch(poly)
p = PolygonInteractor(ax, poly)

ax.set_title('Cliquez et faites glisser un point pour le déplacer')
ax.set_xlim((-2, 2))
ax.set_ylim((-2, 2))
plt.show()

Résumé

Dans ce laboratoire, nous avons appris à créer un éditeur de polygone en utilisant la gestion d'événements de Matplotlib. Nous avons créé une classe pour gérer toutes les interactions avec le polygone, et nous avons utilisé la classe Polygon pour créer le polygone lui-même. Nous avons ensuite créé la figure et ajouté le polygone à celle-ci. Avec ces connaissances, vous pouvez créer vos propres applications interactives en utilisant Matplotlib.