Because of the cleanups in #15977, the pgf backend no longer supports fig.draw(fig.canvas.get_renderer()) to trigger a draw.
If this is not supposed to be a bug and is the correct expected behavior, is there a cleaner way to trigger a draw without using fig.savefig?
Bug summary
Code for reproduction
importnumpyasnpimportmatplotlib.pyplotaspltplt.switch_backend('pgf') # Switching to the PGF backendfig, ax=plt.subplots(figsize=(6, 3.0), ncols=2, nrows=1,
constrained_layout=True, sharey=True)
fig.set_constrained_layout_pads(wspace=0.03)
ax[0].plot(np.arange(4), 'r', label=r'Really longgggg label 1')
ax[1].plot(np.arange(5), 'b', label=r'Really longgggg label 2')
(leg_h0, leg_lab0) =ax[0].get_legend_handles_labels()
(leg_h1, leg_lab1) =ax[1].get_legend_handles_labels()
# add a legend to one of the axes to make space at the top of the figureleg0=ax[0].legend(leg_h0, leg_lab0, loc='lower center',
bbox_to_anchor=(0.5, 1.07))
leg0.set_in_layout(True) # force to steal space from top of the figure# ------------------------------------------------------# Trigger a draw (required for constrained_layout to work)# fig.canvas.draw() # Doesn't work since pgf is a non-interactive backendfig.draw(fig.canvas.get_renderer()) # used to work before #15977# ------------------------------------------------------# Thanks constrained_layout, your work is done! turning you off nowfig.set_constrained_layout(False)
# Remove legend from axes and add a figure legend# which includes entries from both axesleg0.remove()
fig_leg=fig.legend(*zip(leg_h0, leg_h1), *zip(leg_lab0, leg_lab1),
loc='lower center', ncol=2, columnspacing=5,
bbox_to_anchor=(0.5, 0.85))
fig.savefig('test.png', dpi=200)
Actual outcome
Traceback (most recent call last):
File "test.py", line 22, in <module>
fig.draw(fig.canvas.get_renderer()) # works!
File "---\Miniconda3\lib\site-packages\matplotlib\artist.py", line 41, in draw_wrapper
return draw(artist, renderer, *args, **kwargs)
File "---\Miniconda3\lib\site-packages\matplotlib\figure.py", line 1862, in draw
self.patch.draw(renderer)
File "---\Miniconda3\lib\site-packages\matplotlib\artist.py", line 41, in draw_wrapper
return draw(artist, renderer, *args, **kwargs)
File "---\Miniconda3\lib\site-packages\matplotlib\patches.py", line 587, in draw
draw_path(tpath, affine,
File "---\Miniconda3\lib\site-packages\matplotlib\backends\backend_pgf.py", line 475, in draw_path
writeln(self.fh, r"\begin{pgfscope}")
File "---\Miniconda3\lib\site-packages\matplotlib\backends\backend_pgf.py", line 129, in writeln
fh.write(line)
AttributeError: 'NoneType' object has no attribute 'write'
@anntzer is about 2 steps ahead of me (as normal),
I think a better solution is to add a draw method to backend_pgf.FigureCanvas that does the dummy draw (enough to update constrained layout) like we expect. Currently it does not define draw at all which gets the default no-op implementation and runs counter to what we have been saying everywhere of "do fig.canvas.draw() to force updates".
For short term fixes, either do what @anntzer is suggesting in your code or do the save with a BytesIO object as the filename.
Woops. But I guess the same problem exists for the other non-interactive backends, so I guess we could just change the base class implementation to do a fig.draw(renderer) using a draw-disabled renderer? fixing all non-interactive backends at once...
Activity
anntzer commented on Sep 4, 2020
Looks like something like
works (note that the old behavior was indeed to return an always-dummy renderer here).
On the other hand(s)
tacaswell commented on Sep 4, 2020
@anntzer is about 2 steps ahead of me (as normal),
I think a better solution is to add a draw method to backend_pgf.FigureCanvas that does the dummy draw (enough to update constrained layout) like we expect. Currently it does not define draw at all which gets the default no-op implementation and runs counter to what we have been saying everywhere of "do fig.canvas.draw() to force updates".
For short term fixes, either do what @anntzer is suggesting in your code or do the save with a BytesIO object as the filename.
anntzer commented on Sep 4, 2020
I have not looked carefully but what's wrong with just inheriting FigureCanvasBase.draw?
tacaswell commented on Sep 4, 2020
It is a no-op
matplotlib/lib/matplotlib/backend_bases.py
Lines 2020 to 2023 in 80eabbc
anntzer commented on Sep 4, 2020
Woops. But I guess the same problem exists for the other non-interactive backends, so I guess we could just change the base class implementation to do a fig.draw(renderer) using a draw-disabled renderer? fixing all non-interactive backends at once...
tacaswell commented on Sep 4, 2020
Agg draws itself (and then calls super()) so that complicates things.
anntzer commented on Sep 4, 2020
Ah, that's annoying. We should still try to at least keep pdf/ps/svg/pgf consistent, even if by copy-pasting the same draw() implementation.
tacaswell commented on Sep 4, 2020
I'll have a PR soon.
FIX/API: `fig.canvas.draw` always updates internal state
FIX/API: `fig.canvas.draw` always updates internal state
FIX/API: `fig.canvas.draw` always updates internal state
FIX/API: `fig.canvas.draw` always updates internal state
Tests: possible xfail in seaborn example due to Matplotlib bug.
FIX/API: `fig.canvas.draw` always updates internal state
FIX/API: `fig.canvas.draw` always updates internal state
FIX/API: `fig.canvas.draw` always updates internal state
FIX/API: `fig.canvas.draw` always updates internal state
FIX/API: `fig.canvas.draw` always updates internal state
FIX/API: `fig.canvas.draw` always updates internal state
FIX/API: `fig.canvas.draw` always updates internal state