Skip to content

Redrawing figure canvas after draw_event fails #10334

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

Closed
ImportanceOfBeingErnest opened this issue Jan 27, 2018 · 3 comments
Closed

Redrawing figure canvas after draw_event fails #10334

ImportanceOfBeingErnest opened this issue Jan 27, 2018 · 3 comments
Labels
status: closed as inactive Issues closed by the "Stale" Github Action. Please comment on any you think should still be open. status: inactive Marked by the “Stale” Github Action

Comments

@ImportanceOfBeingErnest
Copy link
Member

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.

draw_event

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.

draw_event2

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.

@anntzer
Copy link
Contributor

anntzer commented Jan 28, 2018

Recursive draws are explicitly ignored by qtagg

    def draw_idle(self):
        """Queue redraw of the Agg buffer and request Qt paintEvent.
        """
        # The Agg draw needs to be handled by the same thread matplotlib
        # modifies the scene graph from. Post Agg draw request to the
        # current event loop in order to ensure thread affinity and to
        # accumulate multiple draw requests from event handling.
        # TODO: queued signal connection might be safer than singleShot
        if not (self._agg_draw_pending or self._agg_is_drawing):  # <--- HERE
            self._agg_draw_pending = True
            QtCore.QTimer.singleShot(0, self.__draw_idle_agg)

but I haven't thought much whether that was strictly needed.

@ImportanceOfBeingErnest
Copy link
Member Author

Thanks for the hint. A workaround is therefore to delay the redraw for some time. I guess a solution to the above example could then look like

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):
        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.size = s
            self._redraw_later()
    
    def _redraw_later(self):
        self.timer = fig.canvas.new_timer(interval=10)
        self.timer.single_shot = True
        self.timer.add_callback(lambda : self.ax.figure.canvas.draw_idle())
        self.timer.start()
        
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()

This works well with TkAgg as well as Qt4Agg backends.

@github-actions
Copy link

This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!

@github-actions github-actions bot added the status: inactive Marked by the “Stale” Github Action label Apr 30, 2023
@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale May 30, 2023
@rcomer rcomer added the status: closed as inactive Issues closed by the "Stale" Github Action. Please comment on any you think should still be open. label May 30, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: closed as inactive Issues closed by the "Stale" Github Action. Please comment on any you think should still be open. status: inactive Marked by the “Stale” Github Action
Projects
None yet
Development

No branches or pull requests

3 participants