Description
It seems that redrawing a figure inside a callback function connected to a "draw_event"
does not work as intended. This issue would occur when a callback function tries to call fig.canvas.draw_idle()
.
Sketch of the setup:
def update(evt):
if some_condition:
# change something in the figure
fig.canvas.draw_idle()
# the figure is not redrawn
fig.canvas.mpl_connect('draw_event', update)
This issue is reproduced with python 2.7, matplotlib 2.1.2 on windows 8.1 using the Qt4Agg as well as the TkAgg backend.
Here is a full working example. This example is expected to draw a scatter point of the same size as a box in data coordinates. On draw_events, which happen e.g. by zooming the axes, the scatter size should update to match the box' extention.
import matplotlib
matplotlib.use("Qt4Agg")
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(figsize=(3,2.2))
class scatter():
def __init__(self,x,y,ax,size=1,**kwargs):
self.n = len(x)
self.ax = ax
self.ax.figure.canvas.draw()
self.size_data=size
self.size = size
self.sc = ax.scatter(x,y,s=self.size,**kwargs)
self._resize()
self.cid = ax.figure.canvas.mpl_connect('draw_event', self._resize)
def _resize(self,event=None):
print("draw_event")
ppd=72./self.ax.figure.dpi
trans = self.ax.transData.transform
s = ((trans((1,self.size_data))-trans((0,0)))*ppd)[1]
if s != self.size:
self.sc.set_sizes(s**2*np.ones(self.n))
self.ax.figure.canvas.draw_idle()
self.size = s
ax.axis([0,1,0,1])
sc = scatter([0.5],[0.5],ax, size=.2, linewidth=0)
ax.plot([.4,.6,.6,.4,.4],[.4,.4,.6,.6,.4], color="k")
ax.set_aspect(1)
plt.show()
The actual outcome of this is that when zooming, the scatter size is updated, but the figure is not redrawn, such that the scatter keeps its previous size on screen. One needs to explicitely trigger another draw event (e.g. by clicking inside the axes) for the changes to be reflected in the figure.
Workaround:
As a workaround one may explicitely draw the axes again inside the updating funtion.
if s != self.size:
self.sc.set_sizes(s**2*np.ones(self.n))
self.ax.draw_artist(self.ax) # <---- draw axes explicitely here
self.ax.figure.canvas.draw_idle()
self.size = s
However, this results in the axes being drawn twice. This at least seems so, because the axis labels are now bolder than before, suggesting that they are overlayed by a previous version of them. Again after clicking (and hence drawing the figure again), the labels appear in their normal typesetting.
The question would be: Why is fig.canvas.draw_idle()
ignored? Is this a wrong expectation from my side? Is there something in the code that explicitely forbidds a draw_event beeing triggered from a draw_event callback?
Note that according to some discussion below a stackoverflow question it seems at least one person cannot reproduce the reported behaviour, so it may well be an issue restricted to python 2 or windows or some other unknown parameter.