New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The response of mouse zoom & pan is slow with Qt4Agg backend. #2559
Comments
Can Confirm that matplotlib is much more responsive with the change. I always taught |
Until this is released, is there a workaround in the meantime, e.g. subclassing FigureCanvasQTAgg? I'm not that familiar with the nuances of trying to do so. I'm using an explicit FigureCanvasQTAgg with other UI elements:
so if subclassing would work I can definitely do it. |
Use the patch_qt4agg() function:
|
@zhangruoyu @jason-s @t What OS/qt version are you using? pyqt or pysides? I use qt4agg + pysides for my daily work and have never noticed any behaviour I would call slow and the example program is snappy on my system. The Qt docs state that using http://qt-project.org/doc/qt-4.8/qwidget.html#update |
Windows 7. PySide. Anaconda. If I get time, I will put together an example. |
I am using windows 7, PyQt4.9.6, WinPython I think because |
@zhangruoyu What do you mean by 'slow'? I can not reproduce this at all. If I plot 500k points ( I suspect that the problem is windows (I am using linux) (edit: removed double sentence) |
Windows user here, the response is sluggish while panning even for a simple [1, 2, 3] plot. |
The difference between However, I can't detect a difference on Linux -- so I would argue if changing to repaint has no detectable change on Linux and Mac, but improves things considerably on Windows, than we make the change. |
Just a voice possibly against this idea... what if you have a very large On Mon, Feb 24, 2014 at 11:23 AM, Michael Droettboom <
|
My plan for dealing with this was to add an instantiation time switch:
and then have If the |
@WeatherGod: The actually drawing of the frame happens always, regardless, in order to support the animation model
It is only the copying of the buffer to the window/screen that would make any difference here. |
@tacaswell: That's a good point about the infinite loop. I suppose we have to guard against that somehow... |
I would like to reinforce @WeatherGod's point; I am strongly opposed to putting in code that is contrary to the basic design of the gui toolkit to handle this Windows problem. A Windows conditional as suggested by @tacaswell would be reasonable to try. It should include a comment explaining the rationale. |
@zhangruoyu @Tillsten @jason-s Please see #2844. I think this should address the issue. |
Closing as #2844 should have fixed this. |
The patch_qt4agg solution of zhangruoyu works for me. Thanks! |
@eelcovv Does the issue persist in 1.4.3 with out the patch? |
Actually, I had a slightly different situation. I have a matplotlib contourf figure in a pyQt window which I continuously want to update (playing a movie). One update works, however, when I looped over time the canvas was not updated but it seemed that all changes were hold until the last time step, and then only the last frame would show up. I used matplotlib 1.4.3 with python 2.7.9 and pyqt 4.10.4 For the canvas I created a manual widget in qt-designer which I promoted to a mplCanvas class. something like from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt
class MplCanvas(FigureCanvas):
def __init__(self):
.... so me lines here
self.figure, self.axes = plt.subplots(nrows=1, ncols=2, subplot_kw=dict(projection='polar'), figsize=(13, 6))
self.contour_plots = []
for index, ax in enumerate(self.axes):
self.contour_plots.append(
ax.contourf(np.zeros([1,1]),np.zeros([1,1]),np.zeros([1,1])))
class mplWidget(QtGui.QWidget):
def __init__(self, parent = None):
QtGui.QWidget.__init__(self, parent)
self.mplcanvas = MplCanvas()
self.vbl = QtGui.QVBoxLayout()
self.vbl.addWidget(self.mplcanvas)
self.setLayout(self.vbl)
def updatePlots(self,radar):
"""
Here the radar plot is defined.
:param radar:
:return:
"""
# this line force the canvas to be redrawn after leaving the routine
patch_qt4agg()
for index, ax in enumerate(self.mplcanvas.axes):
ax.clear()
for index, ax in enumerate(self.mplcanvas.axes):
ax.hold(True)
tmpcont.append(ax.contourf( data_of_plot[index]))
ax.hold(False)
# loop over the axes of the plot and create plot
for index,contourplot in enumerate(self.mplcanvas.contour_plots):
contourplot.collections=[]
for coll in tmpcont[index].collections:
contourplot.collections.append(coll)
... some more stuff
self.mplcanvas.figure.canvas.draw()
self.mplcanvas.repaint() Those last two line did not cause an update of the screen when playing a movie. With your patch patch_qt4agg() it works. sorry, bit more complicated example because of the qt implementation. In the update routine I need to clear the canvas from the old plot and then push the new plot in the the collections (by making a temporary plot). Or is there something else I did wrong which could prevent from using your patch ? Anyway, it works now, so thanks for that. But any hints for improvement appreciated! Eelco |
It is rather hard to follow your example. I don't really understand what you are doing with temporary plots and moving artists between axes. I would not use You also probably do not want to monkey patch the class every time you call update. I suspect you want to be doing something like class MyMplCanvas(FigureCanvas):
"""Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
def __init__(self, parent=None, width=5, height=4, dpi=100, ncols=2):
fig = Figure(figsize=(width, height), dpi=dpi)
FigureCanvas.__init__(self, fig)
gs = gridspec.GridSpec(1, ncols)
self._axes_contours = []
for n in range(ncols):
ax = fig.add_subplot(gs[:, n], projection='polar')
self._axes_contours.appond(dict('ax': ax, 'contour': None))
def update_plots(self):
for index, ax_contour in enumerate(self._axes_contours):
if ax_contour['contour']:
ax_contour['contour'].remove()
ax_contour['contour'] = None
ax_contour['countour'] = ax_contour['ax'].contourf(data[index])
self.draw() |
And a related issue you are probably having is that you are not giving the gui event loop a chance to re-paint it's self. Have a look at how the animation module code works. |
Hi Tacaswell, Anyway, I think that is not relevant. The problem I have now is that althoug you example (slightly modified) works, however, the old contour plot is not deleted. I can see that because it is a part of a pie which rotates and every new update of the rotation angle to old contour is not deleted, although I have looped over the collection and called the remove button. Here my example. Do you have any idea why the old contours do not get removed? import Modules.CoordinateTransforms as acf
# -*- coding: utf-8 -*-
"""
Created on Sat Feb 7 21:25:39 2015
@author: eelco
"""
from PyQt4 import QtGui
import numpy as np
from matplotlib.figure import Figure
from matplotlib import cm
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.gridspec as gridspec
import logging
logging.root.handlers = []
logging.basicConfig(format='[%(levelname)-7s] %(message)s', level=logging.DEBUG)
def patch_qt4agg():
import matplotlib.backends.backend_qt4agg as backend
code = """
def draw( self ):
FigureCanvasAgg.draw(self)
self.repaint()
FigureCanvasQTAgg.draw = draw
"""
exec(code, backend.__dict__)
class MplCanvas(FigureCanvas):
"""
Create the canvas containing 2 sub plots with the amplitude and phase
"""
def __init__(self):
ncols=2
plotlabels = ["Wave field", "Radar scan"]
self.plot_type="Wave Field"
self.figure=Figure(figsize=(13, 10),dpi=100)
gs=gridspec.GridSpec(1,ncols)
# create some extra space
gs.update(left=0.15,right=.85,bottom=0.3, wspace=0.25)
self._axes_contours = []
self._axes_lines = []
self._axes_point = []
# add the empty sub figures
for n in range(ncols):
ax=self.figure.add_subplot(gs[:,n],projection='polar')
ax.set_title(plotlabels[n], y=1.08)
ax.set_theta_zero_location("N")
ax.set_theta_direction(-1)
ax.set_ylim(0, 1000)
ax.set_yticks(np.linspace(0, 1000, 4, endpoint=True))
self._axes_contours.append({'ax': ax,'contour': None,'points': None,'lines': None})
# add two color bars
self._axes_cbars=[]
self._axes_cbars.append(self.figure.add_axes([.02, 0.35, 0.02, 0.5]))
self._axes_cbars.append(self.figure.add_axes([.92, 0.35, 0.02, 0.5]))
FigureCanvas.__init__(self, self.figure)
FigureCanvas.setSizePolicy(self,
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
class mplWidget(QtGui.QWidget):
"""
Widget for the matplotlib canvas and some routines to update the data
and the x and y ranges. There as 2 subplots
"""
def __init__(self, parent = None):
QtGui.QWidget.__init__(self, parent)
self.mplcanvas = MplCanvas()
self.vbl = QtGui.QVBoxLayout()
self.vbl.addWidget(self.mplcanvas)
self.setLayout(self.vbl)
# @profile
def updatePlots(self,radar):
"""
Here the radar plot is defined.
:param radar:
:return:
"""
# this line force the canvas to be redrawn after leaving the routine
#patch_qt4agg()
logging.debug("Updating the plots")
radiigrid=[radar.polar_meshgrid[1]]
radiigrid.append(radar.scanned_area_rt_grid[1])
anglegrid=[radar.polar_meshgrid[0]]
anglegrid.append(radar.scanned_area_rt_grid[0])
valuegrid=[radar.wave_field.eta1,radar.wave_field.eta2]
vrange=[[-2,2],[-2,2]]
cmbar=[cm.coolwarm,cm.coolwarm]
# set the contour levels for both plots
number_of_contour_levels=7
contour_levels=[]
for i in range(2):
contour_levels.append(np.linspace(vrange[i][0], vrange[i][1],
number_of_contour_levels, endpoint=True))
# loop over the axes of the plot and create plot
for index,ax_contour in enumerate(self.mplcanvas._axes_contours):
if ax_contour['contour']:
for i,coll in enumerate(ax_contour['contour'].collections):
logging.debug("remove coll {} ".format(i))
ax_contour['contour'].collections.remove(coll)
ax_contour['contour'].collections=None
ax_contour['contour']=None
ax_contour['contour']=ax_contour['ax'].contourf(
radiigrid[index], anglegrid[index],
valuegrid[index],
contour_levels[index],
cmap=cmbar[index], linewidth=0, antialiased=False)
# finally, call the redrawing of the canvas
self.mplcanvas.figure.canvas.draw()
|
To answer my own question, I found a way how to remove the contour plots The second is that I also have to delete the contours from the axis itself, which I stored in the ax field of the dictionary. The part of the code which does the redraw of the contour plot now is: for index,ax_contour in enumerate(self.mplcanvas._axes_contours):
if ax_contour['contour']:
while ax_contour['contour'].collections:
for coll in ax_contour['contour'].collections:
ax_contour['ax'].collections.remove(coll)
ax_contour['contour'].collections.remove(coll)
ax_contour['contour'].collections=[]
ax_contour['ax'].collections=[]
ax_contour['contour']=ax_contour['ax'].contourf(
radiigrid[index], anglegrid[index],
valuegrid[index],
contour_levels[index],
cmap=cmbar[index], linewidth=0, antialiased=False) |
Although the redrawing of the frames is now correct, if I play a movie again I run in the problem that frames got buffered. Now also the trick with the patch does not work. I read you comment about the time needed to redraw the canvas. I can not find exactly in the animation module what I need to do to force a redraw. If I simple add a time.sleep(1) this does not work either. Any hint? |
Found the solution here: http://stackoverflow.com/questions/9465047/make-an-animated-wave-with-drawpolyline-in-pyside-pyqt Just adding
In the time loop after updating the plot prevents blocking the screen |
Consider following simple program:
The response of mouse action is very slow.
I found that this can be fixed by changing the
self.update()
linein
backend_qt4agg.py
toself.repaint()
:The text was updated successfully, but these errors were encountered: