Editor de polígonos de Matplotlib para aplicaciones cruzadas de GUI

PythonPythonBeginner
Practicar Ahora

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

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

Esta práctica es un tutorial paso a paso sobre cómo construir aplicaciones cruzadas de interfaz gráfica utilizando el manejo de eventos de Matplotlib para interactuar con objetos en el lienzo. Crearemos un editor de polígonos que te permitirá mover, eliminar e insertar vértices.

Consejos sobre la VM

Una vez finalizada la inicialización de la VM, haz clic en la esquina superior izquierda para cambiar a la pestaña Cuaderno y acceder a Jupyter Notebook para practicar.

A veces, es posible que tengas que esperar unos segundos a que Jupyter Notebook termine de cargarse. La validación de operaciones no puede automatizarse debido a las limitaciones de Jupyter Notebook.

Si tienes problemas durante el aprendizaje, no dudes en preguntar a Labby. Proporciona retroalimentación después de la sesión y resolveremos rápidamente el problema para ti.

Importar las bibliotecas necesarias

Necesitamos importar las bibliotecas necesarias para el editor de polígonos.

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

Crear una función para calcular la distancia

Necesitamos crear una función que calcule la distancia entre un punto y un segmento de línea. Esta función se utilizará más adelante para determinar si se debe agregar un nuevo vértice al polígono.

def dist_point_to_segment(p, s0, s1):
    """
    Obtener la distancia desde el punto *p* hasta el segmento (*s0*, *s1*), donde
    *p*, *s0*, *s1* son arrays ``[x, y]``.
    """
    s01 = s1 - s0
    s0p = p - s0
    if (s01 == 0).all():
        return np.hypot(*s0p)
    ## Proyectar sobre el segmento, sin pasar los extremos del segmento.
    p1 = s0 + np.clip((s0p @ s01) / (s01 @ s01), 0, 1) * s01
    return np.hypot(*(p - p1))

Crear la clase PolygonInteractor

Necesitamos crear la clase PolygonInteractor, que es la clase principal para el editor de polígonos. Esta clase manejará todas las interacciones con el polígono, como mover, eliminar e insertar vértices.

class PolygonInteractor:
    """
    Un editor de polígonos.

    Comandos de teclado

      't' alternar la visualización de los marcadores de vértices. Cuando los
          marcadores de vértices están activos, se pueden mover y eliminar

      'd' eliminar el vértice debajo del punto

      'i' insertar un vértice en el punto. Debes estar dentro de una distancia
          epsilon de la línea que conecta dos vértices existentes

    """

    showverts = True
    epsilon = 5  ## distancia máxima en píxeles para considerar que se ha golpeado un vértice

    def __init__(self, ax, poly):
        if poly.figure is None:
            raise RuntimeError('Debes agregar primero el polígono a una figura '
                               'o lienzo antes de definir el interactuador')
        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  ## el vértice activo

        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)
        ## no es necesario hacer un blit aquí, esto se ejecutará antes de que se actualice la pantalla

    def poly_changed(self, poly):
        """Este método se llama cada vez que se llama al objeto pathpatch."""
        ## solo copiar las propiedades del artista a la línea (excepto la visibilidad)
        vis = self.line.get_visible()
        Artist.update_from(self.line, poly)
        self.line.set_visible(vis)  ## no usar el estado de visibilidad del polígono

    def get_ind_under_point(self, event):
        """
        Devuelve el índice del punto más cercano a la posición del evento o *None*
        si ningún punto está a una distancia menor o igual a ``self.epsilon`` de la
        posición del evento.
        """
        ## coordenadas de visualización
        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 para pulsaciones de botón del ratón."""
        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 para liberaciones de botón del ratón."""
        if not self.showverts:
            return
        if event.button!= 1:
            return
        self._ind = None

    def on_key_press(self, event):
        """Callback para pulsaciones de teclas."""
        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  ## coordenadas de visualización
            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 para movimientos del ratón."""
        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)

Crear el polígono

Necesitamos crear el polígono que vamos a editar utilizando la clase 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)

Crear la gráfica

Necesitamos crear la gráfica y agregar el polígono a ella.

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

ax.set_title('Haga clic y arrastre un punto para moverlo')
ax.set_xlim((-2, 2))
ax.set_ylim((-2, 2))
plt.show()

Resumen

En este laboratorio, aprendimos cómo crear un editor de polígonos utilizando el manejo de eventos de Matplotlib. Creamos una clase para manejar todas las interacciones con el polígono, y utilizamos la clase Polygon para crear el polígono en sí mismo. Luego creamos la gráfica y agregamos el polígono a ella. Con este conocimiento, puedes crear tus propias aplicaciones interactivas utilizando Matplotlib.