matplotlib e PyQt: A figura dinâmica é executada lentamente após vários carregamentos ou parece confusa
EDITAR:
Eu decidi reescrever isso para incluir um exemplo funcional do meu problema. Embora isso seja muito longo, espero que seja útil para muitos no futuro.
import sys
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setGeometry(100, 100, 640, 480)
showButton = QPushButton('Show')
toolbarShowButton = self.addToolBar('Show button toolbar')
toolbarShowButton.addWidget(showButton)
self.connect(showButton, SIGNAL('clicked()'), self.showButtonClicked)
self.graphLabel = GraphCanvas(self);
self.setCentralWidget(self.graphLabel)
def showButtonClicked(self):
self.graphLabel.drawGraph()
def resizeEvent(self, event):
try:
self.graphLabel.setFig()
except AttributeError:
pass
class GraphCanvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100):
self.fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = self.fig.add_subplot(111)
FigureCanvas.__init__(self, self.fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QSizePolicy.Expanding,
QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
self.background = None
def drawGraph(self):
self.axes.cla()
self.someplot = self.axes.plot(range(1,5), range(1,5))
self.redVert, = self.axes.plot(None, None, 'r--')
self.greenVert, = self.axes.plot(None, None, 'g--')
self.yellowVert, = self.axes.plot(None, None, 'y--')
self.verticalLines = (self.redVert, self.greenVert, self.yellowVert)
self.fig.canvas.mpl_connect('motion_notify_event', self.onMove)
self.draw()
self.background = self.fig.canvas.copy_from_bbox(self.axes.bbox)
def onMove(self, event):
# cursor moves on the canvas
if event.inaxes:
# restore the clean background
self.fig.canvas.restore_region(self.background)
ymin, ymax = self.axes.get_ylim()
x = event.xdata - 1
# draw each vertical line
for line in self.verticalLines:
line.set_xdata((x,))
line.set_ydata((ymin, ymax))
self.axes.draw_artist(line)
x += 1
self.fig.canvas.blit(self.axes.bbox)
def setFig(self):
'''
Draws the canvas again after the main window
has been resized.
'''
try:
# hide all vertical lines
for line in self.verticalLines:
line.set_visible(False)
except AttributeError:
pass
else:
# draw canvas again and capture the background
self.draw()
self.background = self.fig.canvas.copy_from_bbox(self.axes.bbox)
# set all vertical lines visible again
for line in self.verticalLines:
line.set_visible(True)
def main():
app = QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
if __name__ == '__main__': main()
Descrição do código
Eu tenho um básicoQMainWindow
com uma barra de ferramentas que tenha um botão "show". A janela principal também cria uma tela para ummatplotlib
figura e define que seja o widget central.
Quando o usuário acessa o botão "show", alguns dados são exibidos chamandodrawGraph()
método deGraphCanvas
. Em um programa real, os dados mudam dependendo do que o usuário selecionou para ser mostrado antes de clicar no botão. O métodoresizeEvent()
basicamente desenha novamente a figura para acomodar o novo tamanho do widget central.
odrawGraph()
O método cria quatro gráficos dos quais o primeiro possui dados, portanto, é visível. As duas últimas linhas desenham a figura e salva o plano de fundo estático na variávelself.background
.
Quando o usuário move o mouse na tela, o segundo plano é carregado pela primeira vez. Eu queria salvar e carregar o fundo estático para fazer a figura desenhar mais rápido. Depois disso, os últimos três gráficos obtêm seus dados dinamicamente e são mostrados como 3 linhas verticais que se movem com o cursor do mouse.
O problema
1) A figura fica gradualmente mais lenta à medida que você continua clicando no botão "mostrar". Se você tentar acertar 50 vezes e mover o mouse na figura, verá que as linhas verticais são muito mais lentas. Quando há muito mais gráficos dinâmicos e anotações, etc., em uma figura real, o programa fica inutilizável após apenas alguns cliques.
Alguém mais sábio pode provavelmente dizer por que esta desaceleração está acontecendo, mas eu acho que as figuras carregadas anteriormente são mantidas em algum lugar na memória e talvez desenhadas sob a figura recém-criada. E a pilha continua ficando cada vez maior.
2) A figura é mostrada logo após o início do programa. Não é um grande negócio, mas eu prefiro apenas uma área em branco até que o botão seja clicado.
Uma solução tentou
O que eu tentei foi que eu movesse essas duas linhas dedef __init__(self)
doclass MainWindow
paradef showButtonClicked(self)
:
self.graphLabel = GraphCanvas(self);
self.setCentralWidget(self.graphLabel)
Então parece agora assim:
def showButtonClicked(self):
self.graphLabel = GraphCanvas(self);
self.setCentralWidget(self.graphLabel)
self.graphLabel.drawGraph()
Então criei a figura somente depois que o botão foi pressionado. Isso resolve o problema de desaceleração, mas traz outro problema. Agora, quando você aperta o botão "show" e move o mouse sobre a tela, o fundo salvo da figura é carregado, mas no tamanho original de 5 por 4 polegadas e nós temos uma bagunça. Então basicamente o plano de fundo foi salvo não no tamanho que a figura foi desenhada, mas no tamanho em que foi criada.
Se eu redimensionar a janela, no entanto, tudo funciona bem. Mas na próxima vez que eu clicar no botão "mostrar", o problema reaparecerá e eu preciso redimensionar a janela novamente.
O que eu preciso
Eu preciso fazer essa coisa funcionar com fluidez e parecer como deveria, não importa quantas vezes o botão "show" seja clicado. Além disso, eu preferiria que a figura não fosse exibida até que o botão "show" fosse clicado pela primeira vez e desse ponto em diante ficasse visível até o programa ser fechado.
Alguns hacks vêm ao meu como redimensionar o pixel da janela quando o botão "show" é clicado, mas essa não é a abordagem correta, é?
Todas as idéias e sugestões são mais que bem-vindas. Obrigado.