Figuras interactivas y programación asíncrona #

Matplotlib admite figuras interactivas enriquecidas al incrustar figuras en una ventana GUI. Las interacciones básicas de desplazamiento y zoom en un eje para inspeccionar sus datos están 'integradas' en Matplotlib. Esto es compatible con un sistema completo de manejo de eventos de mouse y teclado que puede usar para crear gráficos interactivos sofisticados.

Esta guía pretende ser una introducción a los detalles de bajo nivel de cómo funciona la integración de Matplotlib con un bucle de eventos de GUI. Para obtener una introducción más práctica a la API de eventos de Matplotlib, consulte el sistema de manejo de eventos , el tutorial interactivo y las aplicaciones interactivas que usan Matplotlib .

Bucles de eventos #

Fundamentalmente, toda la interacción del usuario (y la creación de redes) se implementa como un bucle infinito que espera eventos del usuario (a través del sistema operativo) y luego hace algo al respecto. Por ejemplo, se requiere un bucle mínimo de lectura, evaluación e impresión (REPL).

exec_count = 0
while True:
    inp = input(f"[{exec_count}] > ")        # Read
    ret = eval(inp)                          # Evaluate
    print(ret)                               # Print
    exec_count += 1                          # Loop

A esto le faltan muchas sutilezas (por ejemplo, ¡se cierra en la primera excepción!), pero es representativo de los bucles de eventos que subyacen en todos los terminales, GUI y servidores [ 1 ] . En general, el paso Leer está esperando algún tipo de E/S, ya sea una entrada del usuario o la red, mientras que Evaluar e Imprimir son responsables de interpretar la entrada y luego hacer algo al respecto.

En la práctica, interactuamos con un marco que proporciona un mecanismo para registrar devoluciones de llamadas que se ejecutarán en respuesta a eventos específicos en lugar de implementar directamente el bucle de E/S [ 2 ] . Por ejemplo, "cuando el usuario haga clic en este botón, ejecute esta función" o "cuando el usuario presione la tecla 'z', ejecute esta otra función". Esto permite a los usuarios escribir programas reactivos, controlados por eventos, sin tener que profundizar en los detalles esenciales [ 3 ] de E/S. El bucle de eventos principal a veces se denomina "el bucle principal" y normalmente se inicia, según la biblioteca, mediante métodos con nombres como _exec, runo start.

Todos los marcos de GUI (Qt, Wx, Gtk, tk, OSX o web) tienen algún método para capturar las interacciones del usuario y devolverlas a la aplicación (por ejemplo Signal/ Slotframework en Qt), pero los detalles exactos dependen del kit de herramientas. Matplotlib tiene un back- end para cada kit de herramientas de GUI que admitimos, que utiliza la API del kit de herramientas para unir los eventos de la interfaz de usuario del kit de herramientas con el sistema de manejo de eventos de Matplotlib . Luego puede usar FigureCanvasBase.mpl_connectpara conectar su función al sistema de manejo de eventos de Matplotlib. Esto le permite interactuar directamente con sus datos y escribir interfaces de usuario independientes del kit de herramientas GUI.

Integración del símbolo del sistema #

Hasta aquí todo bien. Tenemos el REPL (como el terminal IPython) que nos permite enviar código de forma interactiva al intérprete y obtener resultados. También tenemos el kit de herramientas GUI que ejecuta un ciclo de eventos en espera de la entrada del usuario y nos permite registrar las funciones que se ejecutarán cuando eso suceda. Sin embargo, si queremos hacer ambas cosas, tenemos un problema: ¡el aviso y el bucle de eventos de la GUI son bucles infinitos y cada uno piensa que está a cargo! Para que tanto el indicador como las ventanas de la GUI respondan, necesitamos un método para permitir que los bucles se 'compartan':

  1. deje que el bucle principal de la GUI bloquee el proceso de python cuando desee ventanas interactivas

  2. deje que el bucle principal de la CLI bloquee el proceso de python y ejecute intermitentemente el bucle de la GUI

  3. incrustar completamente python en la GUI (pero esto es básicamente escribir una aplicación completa)

Bloqueando el aviso #

pyplot.show

Mostrar todas las figuras abiertas.

pyplot.pause

Ejecute el bucle de eventos de la GUI durante segundos de intervalo .

backend_bases.FigureCanvasBase.start_event_loop

Inicie un bucle de eventos de bloqueo.

backend_bases.FigureCanvasBase.stop_event_loop

Detener el bucle de eventos de bloqueo actual.

La "integración" más simple es iniciar el ciclo de eventos de la GUI en modo de "bloqueo" y hacerse cargo de la CLI. Mientras se ejecuta el ciclo de eventos de la GUI, no puede ingresar nuevos comandos en el indicador (su terminal puede hacer eco de los caracteres ingresados ​​en el terminal, pero no se enviarán al intérprete de Python porque está ocupado ejecutando el ciclo de eventos de la GUI), pero las ventanas de la figura responderán. Una vez que se detenga el ciclo de eventos (dejando que las ventanas de figuras aún abiertas no respondan), podrá volver a utilizar el aviso. Reiniciar el ciclo de eventos hará que cualquier figura abierta responda nuevamente (y procesará cualquier interacción del usuario en cola).

Para iniciar el ciclo de eventos hasta que se cierren todas las cifras abiertas, utilice pyplot.showcomo

pyplot.show(block=True)

Para iniciar el bucle de eventos durante un período de tiempo fijo (en segundos), utilice pyplot.pause.

Si no lo está utilizando pyplot, puede iniciar y detener los bucles de eventos a través de FigureCanvasBase.start_event_loopy FigureCanvasBase.stop_event_loop. Sin embargo, en la mayoría de los contextos en los que no lo usaría pyplot, está incrustando Matplotlib en una aplicación de GUI grande y el bucle de eventos de la GUI ya debería estar ejecutándose para la aplicación.

Lejos del indicador, esta técnica puede ser muy útil si desea escribir un script que se detenga para la interacción del usuario o muestre una figura entre el sondeo de datos adicionales. Consulte Scripts y funciones para obtener más detalles.

Integración de gancho de entrada #

Si bien es útil ejecutar el bucle de eventos de la GUI en un modo de bloqueo o manejar explícitamente los eventos de la IU, ¡podemos hacerlo mejor! Realmente queremos poder tener ventanas de figuras interactivas y de aviso utilizables.

Podemos hacer esto usando la función 'enganche de entrada' del indicador interactivo. El indicador llama a este enlace mientras espera que el usuario escriba (incluso para un mecanógrafo rápido, el indicador está esperando principalmente a que el humano piense y mueva los dedos). Aunque los detalles varían entre las indicaciones, la lógica es más o menos

  1. comenzar a esperar la entrada del teclado

  2. iniciar el bucle de eventos de la GUI

  3. tan pronto como el usuario presione una tecla, salga del bucle de eventos de la GUI y maneje la tecla

  4. repetir

Esto nos da la ilusión de tener simultáneamente ventanas GUI interactivas y un aviso interactivo. La mayor parte del tiempo se ejecuta el bucle de eventos de la GUI, pero tan pronto como el usuario comienza a escribir, el indicador vuelve a tomar el control.

Esta técnica de tiempo compartido solo permite que el bucle de eventos se ejecute mientras Python está inactivo y esperando la entrada del usuario. Si desea que la GUI responda durante la ejecución prolongada del código, es necesario vaciar periódicamente la cola de eventos de la GUI como se describe anteriormente . En este caso, es su código, no el REPL, el que está bloqueando el proceso, por lo que debe manejar el "tiempo compartido" manualmente. Por el contrario, dibujar una figura muy lentamente bloqueará el indicador hasta que termine de dibujarse.

incrustación completa #

También es posible ir en la otra dirección e incrustar completamente figuras (y un intérprete de Python ) en una aplicación nativa enriquecida. Matplotlib proporciona clases para cada conjunto de herramientas que se pueden integrar directamente en las aplicaciones GUI (¡así es como se implementan las ventanas integradas!). Consulte Incorporación de Matplotlib en interfaces gráficas de usuario para obtener más detalles.

Scripts y funciones #

backend_bases.FigureCanvasBase.flush_events

Vacíe los eventos de la GUI para la figura.

backend_bases.FigureCanvasBase.draw_idle

Solicite que se vuelva a dibujar el widget una vez que el control regrese al bucle de eventos de la GUI.

figure.Figure.ginput

Llamada de bloqueo para interactuar con una figura.

pyplot.ginput

Llamada de bloqueo para interactuar con una figura.

pyplot.show

Mostrar todas las figuras abiertas.

pyplot.pause

Ejecute el bucle de eventos de la GUI durante segundos de intervalo .

Hay varios casos de uso para usar figuras interactivas en scripts:

  • capturar la entrada del usuario para dirigir el script

  • actualizaciones de progreso a medida que avanza un script de ejecución prolongada

  • transmisión de actualizaciones desde una fuente de datos

Funciones de bloqueo #

Si solo necesita acumular puntos en un eje, puede usar Figure.ginputo, de manera más general, las herramientas de blocking_inputlas herramientas se encargarán de iniciar y detener el ciclo de eventos por usted. Sin embargo, si ha escrito algún manejo de eventos personalizado o está utilizando widgets, deberá ejecutar manualmente el bucle de eventos de la GUI utilizando los métodos descritos anteriormente .

También puede usar los métodos descritos en Bloqueo del indicador para suspender la ejecución del bucle de eventos de la GUI. Una vez que el ciclo sale, su código se reanudará. En general, cualquier lugar que usaría time.sleeplo puede usar en su pyplot.pauselugar con el beneficio adicional de las figuras interactivas.

Por ejemplo, si desea sondear datos, podría usar algo como

fig, ax = plt.subplots()
ln, = ax.plot([], [])

while True:
    x, y = get_new_data()
    ln.set_data(x, y)
    plt.pause(1)

que buscaría nuevos datos y actualizaría la cifra a 1Hz.

Hacer girar explícitamente el evento Loop #

backend_bases.FigureCanvasBase.flush_events

Vacíe los eventos de la GUI para la figura.

backend_bases.FigureCanvasBase.draw_idle

Solicite que se vuelva a dibujar el widget una vez que el control regrese al bucle de eventos de la GUI.

Si tiene ventanas abiertas que tienen eventos de IU pendientes (clics del mouse, pulsaciones de botones o sorteos), puede procesar explícitamente esos eventos llamando a FigureCanvasBase.flush_events. Esto ejecutará el bucle de eventos de la GUI hasta que se hayan procesado todos los eventos de la IU que están esperando actualmente. El comportamiento exacto depende del back-end, pero por lo general se procesan los eventos en todas las figuras y solo se manejarán los eventos que esperan ser procesados ​​(no los agregados durante el procesamiento).

Por ejemplo

import time
import matplotlib.pyplot as plt
import numpy as np
plt.ion()

fig, ax = plt.subplots()
th = np.linspace(0, 2*np.pi, 512)
ax.set_ylim(-1.5, 1.5)

ln, = ax.plot(th, np.sin(th))

def slow_loop(N, ln):
    for j in range(N):
        time.sleep(.1)  # to simulate some work
        ln.figure.canvas.flush_events()

slow_loop(100, ln)

Si bien esto se sentirá un poco lento (ya que solo procesamos la entrada del usuario cada 100 ms, mientras que 20-30 ms es lo que se siente como "receptivo"), responderá.

Si realiza cambios en el gráfico y desea volver a renderizarlo, deberá llamar draw_idlepara solicitar que se vuelva a dibujar el lienzo. Este método se puede pensar en draw_soon en analogía con asyncio.loop.call_soon.

Podemos agregar esto a nuestro ejemplo anterior como

def slow_loop(N, ln):
    for j in range(N):
        time.sleep(.1)  # to simulate some work
        if j % 10:
            ln.set_ydata(np.sin(((j // 10) % 5 * th)))
            ln.figure.canvas.draw_idle()

        ln.figure.canvas.flush_events()

slow_loop(100, ln)

Cuanto más frecuentemente llame, FigureCanvasBase.flush_eventsmás sensible se sentirá su figura, pero a costa de gastar más recursos en la visualización y menos en su cálculo.

Artistas obsoletos #

Los artistas (a partir de Matplotlib 1.5) tienen un atributo obsoletoTrue que indica si el estado interno del artista ha cambiado desde la última vez que se representó. De forma predeterminada, el estado obsoleto se propaga hasta los padres Artistas en el árbol de dibujo, por ejemplo, si Line2D se cambia el color de una instancia, el Axesy Figureque lo contienen también se marcarán como "obsoletos". Así, fig.staleinformará si algún artista de la figura ha sido modificado y no está sincronizado con lo que se muestra en pantalla. Esto está destinado a ser utilizado para determinar si draw_idlese debe llamar para programar una nueva representación de la figura.

Cada artista tiene un Artist.stale_callbackatributo que contiene una devolución de llamada con la firma.

def callback(self: Artist, val: bool) -> None:
   ...

que por defecto está configurado en una función que reenvía el estado obsoleto al padre del artista. Si desea evitar que un artista determinado se propague, establezca este atributo en Ninguno.

Figurelas instancias no tienen un artista contenedor y su devolución de llamada predeterminada es None. Si llama pyplot.iony no está IPython, instalaremos una devolución de llamada para invocar draw_idlecada vez que Figurese vuelva obsoleto. Usamos IPythonel 'post_execute'gancho para invocar draw_idlecualquier figura obsoleta después de haber ejecutado la entrada del usuario, pero antes de devolver el aviso al usuario. Si no está usando pyplot, puede usar el Figure.stale_callbackatributo de devolución de llamada para recibir una notificación cuando una cifra se haya vuelto obsoleta.

Dibujo inactivo #

backend_bases.FigureCanvasBase.draw

Renderizar el Figure.

backend_bases.FigureCanvasBase.draw_idle

Solicite que se vuelva a dibujar el widget una vez que el control regrese al bucle de eventos de la GUI.

backend_bases.FigureCanvasBase.flush_events

Vacíe los eventos de la GUI para la figura.

En casi todos los casos, recomendamos usar backend_bases.FigureCanvasBase.draw_idleover backend_bases.FigureCanvasBase.draw. drawfuerza una representación de la figura, mientras que draw_idleprograma una representación la próxima vez que la ventana GUI vuelva a pintar la pantalla. Esto mejora el rendimiento al renderizar solo los píxeles que se mostrarán en la pantalla. Si desea asegurarse de que la pantalla se actualice lo antes posible,

fig.canvas.draw_idle()
fig.canvas.flush_events()

Roscado #

La mayoría de los marcos de GUI requieren que todas las actualizaciones de la pantalla y, por lo tanto, su ciclo de eventos principal, se ejecuten en el subproceso principal. Esto hace imposible enviar actualizaciones periódicas de una trama a un subproceso de fondo. Aunque parezca al revés, normalmente es más fácil llevar los cálculos a un subproceso de fondo y actualizar periódicamente la figura en el subproceso principal.

En general, Matplotlib no es seguro para subprocesos. Si va a actualizar Artistobjetos en un subproceso y dibujar desde otro, debe asegurarse de bloquear las secciones críticas.

Mecanismo de integración de bucle de eventos #

CPython / línea de lectura #

La API de Python C proporciona un enlace, PyOS_InputHook, para registrar una función que se ejecutará ("Se llamará a la función cuando el indicador del intérprete de Python esté a punto de quedar inactivo y espere la entrada del usuario desde la terminal"). Este enlace se puede usar para integrar un segundo bucle de eventos (el bucle de eventos de la GUI) con el bucle de solicitud de entrada de Python. Las funciones de enlace generalmente agotan todos los eventos pendientes en la cola de eventos de la GUI, ejecutan el bucle principal durante un período de tiempo fijo corto o ejecutan el bucle de eventos hasta que se presiona una tecla en stdin.

Matplotlib actualmente no realiza ninguna gestión PyOS_InputHookdebido a la amplia gama de formas en que se utiliza Matplotlib. Esta gestión se deja a las bibliotecas posteriores, ya sea el código de usuario o el shell. Es posible que las figuras interactivas, incluso con Matplotlib en 'modo interactivo', no funcionen en la respuesta de vanilla python si PyOS_InputHookno se registra una adecuada.

Los enlaces de entrada y los ayudantes para instalarlos generalmente se incluyen con los enlaces de Python para los kits de herramientas de GUI y se pueden registrar en la importación. IPython también incluye funciones de enlace de entrada para todos los marcos de GUI compatibles con Matplotlib que se pueden instalar a través de %matplotlib. Este es el método recomendado para integrar Matplotlib y un aviso.

IPython/prompt_toolkit #

Con IPython >= 5.0, IPython ha pasado de usar el aviso basado en línea de lectura de CPython a un prompt_toolkitaviso basado. prompt_toolkit tiene el mismo gancho de entrada conceptual, que se alimenta a prompt_toolkittravés del IPython.terminal.interactiveshell.TerminalInteractiveShell.inputhook() método. La fuente de los prompt_toolkitganchos de entrada se encuentra en IPython.terminal.pt_inputhooks.

notas al pie