Repository navigation
Closed
Labels
Release criticalFor bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.
Milestone
Description
Bug report
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
import numpy as np
import matplotlib.pyplot as plt
plt.switch_backend('pgf') # Switching to the PGF backend
fig, 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 figure
leg0 = 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 backend
fig.draw(fig.canvas.get_renderer()) # used to work before #15977
# ------------------------------------------------------
# Thanks constrained_layout, your work is done! turning you off now
fig.set_constrained_layout(False)
# Remove legend from axes and add a figure legend
# which includes entries from both axes
leg0.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'
Matplotlib version
- Operating system: Windows 10
- Matplotlib version: 3.3.1
- Matplotlib backend (
print(matplotlib.get_backend())): pgf - Python version: 3.8.5
- Jupyter version (if applicable):
- Other libraries:
Reacted by Tobias Laundal
Metadata
Metadata
Assignees
Labels
Release criticalFor bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.

Activity
anntzer commentedon 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)
fig.canvas.draw()is the canonical way to trigger a draw? (fig.draw(renderer)is in a sense an implementation detail...)get_renderer()and rely on backend_bases' handling?tacaswell commentedon Sep 4, 2020
@anntzer is about 2 steps ahead of me (as normal),
I think a better solution is to add a
drawmethod tobackend_pgf.FigureCanvasthat does the dummy draw (enough to update constrained layout) like we expect. Currently it does not definedrawat all which gets the default no-op implementation and runs counter to what we have been saying everywhere of "dofig.canvas.draw()to force updates".For short term fixes, either do what @anntzer is suggesting in your code or do the save with a
BytesIOobject as the filename.anntzer commentedon Sep 4, 2020
I have not looked carefully but what's wrong with just inheriting
FigureCanvasBase.draw?tacaswell commentedon Sep 4, 2020
It is a no-op
matplotlib/lib/matplotlib/backend_bases.py
Lines 2020 to 2023 in 80eabbc
anntzer commentedon 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 commentedon Sep 4, 2020
Agg draws itself (and then calls
super()) so that complicates things.anntzer commentedon 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 commentedon 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