Figuras interativas e programação assíncrona #
Matplotlib suporta figuras interativas ricas incorporando figuras em uma janela GUI. As interações básicas de panorâmica e zoom em um Axes para inspecionar seus dados são 'incorporadas' ao Matplotlib. Isso é suportado por um sistema completo de manipulação de eventos de mouse e teclado que você pode usar para construir gráficos interativos sofisticados.
Este guia pretende ser uma introdução aos detalhes de baixo nível de como funciona a integração do Matplotlib com um loop de eventos da GUI. Para obter uma introdução mais prática à API de eventos do Matplotlib, consulte sistema de manipulação de eventos , Tutorial interativo e Aplicativos interativos usando o Matplotlib .
Loops de eventos #
Fundamentalmente, toda interação do usuário (e rede) é implementada como um loop infinito esperando por eventos do usuário (através do sistema operacional) e fazendo algo a respeito. Por exemplo, um mínimo Read Evaluate Print Loop (REPL) é
exec_count = 0
while True:
inp = input(f"[{exec_count}] > ") # Read
ret = eval(inp) # Evaluate
print(ret) # Print
exec_count += 1 # Loop
Faltam muitos detalhes (por exemplo, ele sai na primeira exceção!), mas é representativo dos loops de eventos subjacentes a todos os terminais, GUIs e servidores [ 1 ] . Em geral, a etapa Read está aguardando algum tipo de E/S -- seja entrada do usuário ou da rede -- enquanto Evaluate e Print são responsáveis por interpretar a entrada e fazer algo a respeito.
Na prática, interagimos com um framework que fornece um mecanismo para registrar callbacks a serem executados em resposta a eventos específicos, em vez de implementar diretamente o loop de I/O [ 2 ] . Por exemplo, "quando o usuário clicar neste botão, execute esta função" ou "quando o usuário pressionar a tecla 'z', execute esta outra função". Isso permite que os usuários escrevam programas reativos, orientados a eventos, sem ter que se aprofundar nos detalhes minuciosos [ 3 ] de E/S. O loop de eventos principal às vezes é referido como "o loop principal" e normalmente é iniciado, dependendo da biblioteca, por métodos com nomes como _exec
,
run
ou start
.
Todos os frameworks GUI (Qt, Wx, Gtk, tk, OSX ou web) possuem algum método para capturar as interações do usuário e passá-las de volta para o aplicativo (por exemplo Signal
/ Slot
framework no Qt), mas os detalhes exatos dependem do kit de ferramentas. O Matplotlib tem um back- end para cada kit de ferramentas GUI que suportamos, que usa a API do kit de ferramentas para conectar os eventos da IU do kit de ferramentas ao sistema de manipulação de eventos do Matplotlib . Você pode usar
FigureCanvasBase.mpl_connect
para conectar sua função ao sistema de manipulação de eventos do Matplotlib. Isso permite que você interaja diretamente com seus dados e escreva interfaces de usuário agnósticas do kit de ferramentas da GUI.
Integração do prompt de comando #
Até agora tudo bem. Temos o REPL (como o terminal IPython) que nos permite enviar código de forma interativa para o interpretador e obter os resultados de volta. Também temos o kit de ferramentas da GUI que executa um loop de eventos aguardando a entrada do usuário e nos permite registrar as funções a serem executadas quando isso acontecer. No entanto, se quisermos fazer as duas coisas, temos um problema: o prompt e o loop de eventos da GUI são loops infinitos que cada um pensa que está no comando! Para que o prompt e as janelas da GUI sejam responsivos, precisamos de um método para permitir que os loops 'compartilhem o tempo':
deixe o loop principal da GUI bloquear o processo python quando quiser janelas interativas
deixe o loop principal da CLI bloquear o processo python e executar intermitentemente o loop da GUI
incorpore totalmente o python na GUI (mas isso é basicamente escrever um aplicativo completo)
Bloqueando o prompt #
Exibir todas as figuras abertas. |
|
Execute o loop de eventos da GUI para intervalos de segundos. |
|
Inicie um loop de evento de bloqueio. |
|
Pare o loop de evento de bloqueio atual. |
A "integração" mais simples é iniciar o loop de eventos da GUI no modo 'blocking' e assumir a CLI. Enquanto o loop de eventos da GUI está em execução, você não pode inserir novos comandos no prompt (seu terminal pode ecoar os caracteres digitados no terminal, mas eles não serão enviados ao interpretador Python porque ele está ocupado executando o loop de eventos da GUI), mas as janelas da figura serão responsivas. Depois que o loop de eventos for interrompido (deixando qualquer janela de figura ainda aberta sem resposta), você poderá usar o prompt novamente. Reiniciar o loop de eventos tornará qualquer figura aberta responsiva novamente (e processará qualquer interação do usuário na fila).
Para iniciar o loop de eventos até que todas as figuras abertas sejam fechadas, use
pyplot.show
como
pyplot.show(block=True)
Para iniciar o loop de eventos por um período fixo de tempo (em segundos), use
pyplot.pause
.
Se você não estiver usando pyplot
, pode iniciar e parar os loops de eventos via FigureCanvasBase.start_event_loop
e
FigureCanvasBase.stop_event_loop
. No entanto, na maioria dos contextos em que você não usaria, pyplot
você está incorporando o Matplotlib em um aplicativo GUI grande e o loop de eventos da GUI já deve estar em execução para o aplicativo.
Longe do prompt, essa técnica pode ser muito útil se você quiser escrever um script que faça uma pausa para a interação do usuário ou exiba uma figura entre a pesquisa de dados adicionais. Consulte Scripts e funções para obter mais detalhes.
Integração do gancho de entrada #
Embora seja útil executar o loop de eventos da GUI em um modo de bloqueio ou lidar explicitamente com eventos da interface do usuário, podemos fazer melhor! Nós realmente queremos ter um prompt utilizável e janelas de figuras interativas.
Podemos fazer isso usando o recurso 'gancho de entrada' do prompt interativo. Esse gancho é chamado pelo prompt enquanto espera que o usuário digite (mesmo para um digitador rápido, o prompt espera principalmente que o humano pense e mova os dedos). Embora os detalhes variem entre os prompts, a lógica é aproximadamente
começar a esperar pela entrada do teclado
iniciar o loop de eventos da GUI
assim que o usuário pressiona uma tecla, saia do loop de eventos da GUI e manipule a tecla
repetir
Isso nos dá a ilusão de ter simultaneamente janelas GUI interativas e um prompt interativo. Na maioria das vezes, o loop de eventos da GUI está em execução, mas assim que o usuário começa a digitar, o prompt assume novamente.
Essa técnica de compartilhamento de tempo permite apenas que o loop de eventos seja executado enquanto o python estiver ocioso e aguardando a entrada do usuário. Se você deseja que a GUI seja responsiva durante código de execução longa, é necessário liberar periodicamente a fila de eventos da GUI conforme descrito acima . Neste caso, é o seu código, não o REPL, que está bloqueando o processo, então você precisa lidar com o "time-share" manualmente. Por outro lado, um desenho de figura muito lento bloqueará o prompt até que ele termine de desenhar.
Incorporação completa #
Também é possível ir na outra direção e incorporar figuras totalmente (e um interpretador Python ) em um aplicativo nativo rico. O Matplotlib fornece classes para cada kit de ferramentas que podem ser incorporadas diretamente em aplicativos GUI (é assim que as janelas integradas são implementadas!). Consulte Incorporando Matplotlib em interfaces gráficas do usuário para obter mais detalhes.
Scripts e funções #
Descarregue os eventos da GUI para a figura. |
|
Solicite um redesenho do widget assim que o controle retornar ao loop de eventos da GUI. |
|
Chamada de bloqueio para interagir com uma figura. |
|
Chamada de bloqueio para interagir com uma figura. |
|
Exibir todas as figuras abertas. |
|
Execute o loop de eventos da GUI para intervalos de segundos. |
Existem vários casos de uso para usar figuras interativas em scripts:
capturar a entrada do usuário para orientar o script
atualizações de progresso à medida que um script de execução longa progride
streaming de atualizações de uma fonte de dados
Funções de bloqueio #
Se você só precisa coletar pontos em um Axes, você pode usar
Figure.ginput
ou, mais geralmente, as ferramentas das
blocking_input
ferramentas cuidarão de iniciar e parar o loop de eventos para você. No entanto, se você escreveu algum tratamento de evento personalizado ou está usando widgets
, precisará executar manualmente o loop de eventos da GUI usando os métodos descritos acima .
Você também pode usar os métodos descritos em Bloqueando o prompt
para suspender a execução do loop de eventos da GUI. Assim que o loop sair, seu código será retomado. Em geral, qualquer lugar que você usaria, time.sleep
você pode usar
pyplot.pause
com o benefício adicional de figuras interativas.
Por exemplo, se você deseja pesquisar dados, pode 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 pesquisaria novos dados e atualizaria a figura em 1 Hz.
Girando explicitamente o evento Loop #
Descarregue os eventos da GUI para a figura. |
|
Solicite um redesenho do widget assim que o controle retornar ao loop de eventos da GUI. |
Se você tiver janelas abertas com eventos de interface do usuário pendentes (cliques do mouse, pressionamentos de botão ou desenhos), poderá processar explicitamente esses eventos chamando FigureCanvasBase.flush_events
. Isso executará o loop de eventos da GUI até que todos os eventos da interface do usuário que estão aguardando tenham sido processados. O comportamento exato depende do back-end, mas normalmente os eventos em toda a figura são processados e apenas os eventos que aguardam para serem processados (não aqueles adicionados durante o processamento) serão tratados.
Por exemplo
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)
Embora isso pareça um pouco lento (já que estamos processando apenas a entrada do usuário a cada 100ms, enquanto 20-30ms é o que parece "responsivo"), ele responderá.
Se você fizer alterações na plotagem e quiser que ela seja renderizada novamente, você precisará chamar draw_idle
para solicitar que a tela seja desenhada novamente. Esse método pode ser pensado como draw_soon em analogia a
asyncio.loop.call_soon
.
Podemos adicionar isso ao nosso exemplo acima 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)
Quanto mais vezes você ligar FigureCanvasBase.flush_events
, mais responsiva sua figura parecerá, mas ao custo de gastar mais recursos na visualização e menos em sua computação.
Artistas obsoletos #
Artistas (a partir do Matplotlib 1.5) têm um atributo
obsoletoTrue
que é se o estado interno do artista mudou desde a última vez que foi renderizado. Por padrão, o estado obsoleto é propagado até os pais dos Artistas na árvore de desenho, por exemplo, se a cor de uma Line2D
instância for alterada, o Axes
e Figure
que a contém também será marcado como "obsoleto". Assim, fig.stale
irá informar se algum artista na figura foi modificado e está fora de sincronia com o que é exibido na tela. Destina-se a ser usado para determinar se draw_idle
deve ser chamado para agendar uma nova renderização da figura.
Cada artista possui um Artist.stale_callback
atributo que contém um callback com a assinatura
def callback(self: Artist, val: bool) -> None:
...
que por padrão é definido como uma função que encaminha o estado obsoleto para o pai do artista. Se você deseja impedir a propagação de um determinado artista, defina este atributo como Nenhum.
Figure
as instâncias não têm um artista recipiente e seu callback padrão é None
. Se você ligar pyplot.ion
e não estiver
IPython
, instalaremos um retorno de chamada para invocar
draw_idle
sempre que
Figure
ficar obsoleto. Usamos IPython
o
'post_execute'
gancho para invocar
draw_idle
qualquer figura obsoleta depois de executar a entrada do usuário, mas antes de retornar o prompt ao usuário. Se você não estiver usando pyplot
, pode usar o
Figure.stale_callback
atributo de retorno de chamada para ser notificado quando uma figura ficar obsoleta.
Sorteio ocioso #
Renderize o |
|
Solicite um redesenho do widget assim que o controle retornar ao loop de eventos da GUI. |
|
Descarregue os eventos da GUI para a figura. |
Em quase todos os casos, recomendamos o uso
backend_bases.FigureCanvasBase.draw_idle
de over
backend_bases.FigureCanvasBase.draw
. draw
força uma renderização da figura enquanto draw_idle
agenda uma renderização na próxima vez que a janela da GUI for repintar a tela. Isso melhora o desempenho renderizando apenas os pixels que serão mostrados na tela. Se você quiser ter certeza de que a tela será atualizada o mais rápido possível, faça
fig.canvas.draw_idle()
fig.canvas.flush_events()
Rosqueamento #
A maioria das estruturas de GUI exige que todas as atualizações na tela e, portanto, seu loop de evento principal sejam executados no thread principal. Isso impossibilita o envio de atualizações periódicas de um gráfico para um thread em segundo plano. Embora pareça retrógrado, normalmente é mais fácil enviar seus cálculos para um thread em segundo plano e atualizar periodicamente a figura no thread principal.
Em geral, o Matplotlib não é thread-safe. Se você for atualizar
Artist
objetos em um thread e desenhar em outro, certifique-se de bloquear as seções críticas.
Mecanismo de integração do loop de eventos #
CPython/linha de leitura #
A API Python C fornece um gancho, PyOS_InputHook
, para registrar uma função a ser executada ("A função será chamada quando o prompt do interpretador do Python estiver prestes a ficar ocioso e aguardar a entrada do usuário no terminal."). Esse gancho pode ser usado para integrar um segundo loop de eventos (o loop de eventos da GUI) com o loop de prompt de entrada do python. As funções de gancho geralmente esgotam todos os eventos pendentes na fila de eventos da GUI, executam o loop principal por um curto período de tempo fixo ou executam o loop de eventos até que uma tecla seja pressionada em stdin.
Atualmente, o Matplotlib não faz nenhum gerenciamento PyOS_InputHook
devido à ampla variedade de maneiras como o Matplotlib é usado. Esse gerenciamento é deixado para as bibliotecas downstream -- código do usuário ou shell. Figuras interativas, mesmo com Matplotlib em 'modo interativo', podem não funcionar no python repl se um apropriado PyOS_InputHook
não estiver registrado.
Os ganchos de entrada e os auxiliares para instalá-los geralmente são incluídos nas ligações do python para os kits de ferramentas da GUI e podem ser registrados na importação. O IPython também fornece funções de gancho de entrada para todas as estruturas de GUI que o Matplotlib suporta, que podem ser instaladas via %matplotlib
. Este é o método recomendado para integrar o Matplotlib e um prompt.
IPython / prompt_toolkit #
Com IPython >= 5.0 IPython mudou de usar o prompt baseado em linha de leitura do CPython para um prompt_toolkit
prompt baseado. prompt_toolkit
tem o mesmo gancho de entrada conceitual, que é alimentado por prompt_toolkit
meio do
IPython.terminal.interactiveshell.TerminalInteractiveShell.inputhook()
método. A origem dos prompt_toolkit
ganchos de entrada reside em
IPython.terminal.pt_inputhooks
.
notas de rodapé