Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 73b5101

Browse files
authored
Merge pull request #18408 from tacaswell/fix_pgf_draw
FIX/API: `fig.canvas.draw` always updates internal state
2 parents c152045 + 22e84f6 commit 73b5101

9 files changed

+123
-35
lines changed

lib/matplotlib/backend_bases.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1584,6 +1584,12 @@ def _draw(renderer): raise Done(renderer)
15841584
figure.canvas = orig_canvas
15851585

15861586

1587+
def _no_output_draw(figure):
1588+
renderer = _get_renderer(figure)
1589+
with renderer._draw_disabled():
1590+
figure.draw(renderer)
1591+
1592+
15871593
def _is_non_interactive_terminal_ipython(ip):
15881594
"""
15891595
Return whether we are in a a terminal IPython, but non interactive.
@@ -1621,7 +1627,9 @@ def _check_savefig_extra_args(func=None, extra_kwargs=()):
16211627
@functools.wraps(func)
16221628
def wrapper(*args, **kwargs):
16231629
name = 'savefig' # Reasonable default guess.
1624-
public_api = re.compile(r'^savefig|print_[A-Za-z0-9]+$')
1630+
public_api = re.compile(
1631+
r'^savefig|print_[A-Za-z0-9]+|_no_output_draw$'
1632+
)
16251633
seen_print_figure = False
16261634
for frame, line in traceback.walk_stack(None):
16271635
if frame is None:
@@ -1632,8 +1640,9 @@ def wrapper(*args, **kwargs):
16321640
frame.f_globals.get('__name__', '')):
16331641
if public_api.match(frame.f_code.co_name):
16341642
name = frame.f_code.co_name
1635-
if name == 'print_figure':
1643+
if name in ('print_figure', '_no_output_draw'):
16361644
seen_print_figure = True
1645+
16371646
else:
16381647
break
16391648

@@ -2021,7 +2030,14 @@ def release_mouse(self, ax):
20212030
self.mouse_grabber = None
20222031

20232032
def draw(self, *args, **kwargs):
2024-
"""Render the `.Figure`."""
2033+
"""
2034+
Render the `.Figure`.
2035+
2036+
It is important that this method actually walk the artist tree
2037+
even if not output is produced because this will trigger
2038+
deferred work (like computing limits auto-limits and tick
2039+
values) that users may want access to before saving to disk.
2040+
"""
20252041

20262042
def draw_idle(self, *args, **kwargs):
20272043
"""

lib/matplotlib/backends/backend_pdf.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from matplotlib._pylab_helpers import Gcf
2929
from matplotlib.backend_bases import (
3030
_Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase,
31-
GraphicsContextBase, RendererBase)
31+
GraphicsContextBase, RendererBase, _no_output_draw)
3232
from matplotlib.backends.backend_mixed import MixedModeRenderer
3333
from matplotlib.figure import Figure
3434
from matplotlib.font_manager import findfont, get_font
@@ -2730,6 +2730,10 @@ def print_pdf(self, filename, *,
27302730
else: # we opened the file above; now finish it off
27312731
file.close()
27322732

2733+
def draw(self):
2734+
_no_output_draw(self.figure)
2735+
return super().draw()
2736+
27332737

27342738
FigureManagerPdf = FigureManagerBase
27352739

lib/matplotlib/backends/backend_pgf.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
from matplotlib import _api, cbook, font_manager as fm
2020
from matplotlib.backend_bases import (
2121
_Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase,
22-
GraphicsContextBase, RendererBase)
22+
GraphicsContextBase, RendererBase, _no_output_draw
23+
)
2324
from matplotlib.backends.backend_mixed import MixedModeRenderer
2425
from matplotlib.backends.backend_pdf import (
2526
_create_pdf_info_dict, _datetime_to_pdf)
@@ -906,6 +907,10 @@ def print_png(self, fname_or_fh, *args, **kwargs):
906907
def get_renderer(self):
907908
return RendererPgf(self.figure, None)
908909

910+
def draw(self):
911+
_no_output_draw(self.figure)
912+
return super().draw()
913+
909914

910915
FigureManagerPgf = FigureManagerBase
911916

lib/matplotlib/backends/backend_ps.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from matplotlib.afm import AFM
2424
from matplotlib.backend_bases import (
2525
_Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase,
26-
GraphicsContextBase, RendererBase)
26+
GraphicsContextBase, RendererBase, _no_output_draw)
2727
from matplotlib.cbook import is_writable_file_like, file_requires_unicode
2828
from matplotlib.font_manager import get_font
2929
from matplotlib.ft2font import LOAD_NO_HINTING, LOAD_NO_SCALE
@@ -1129,6 +1129,10 @@ def _print_figure_tex(
11291129

11301130
_move_path_to_path_or_stream(tmpfile, outfile)
11311131

1132+
def draw(self):
1133+
_no_output_draw(self.figure)
1134+
return super().draw()
1135+
11321136

11331137
def convert_psfrags(tmpfile, psfrags, font_preamble, custom_preamble,
11341138
paper_width, paper_height, orientation):

lib/matplotlib/backends/backend_svg.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from matplotlib import _api, cbook
1818
from matplotlib.backend_bases import (
1919
_Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase,
20-
RendererBase)
20+
RendererBase, _no_output_draw)
2121
from matplotlib.backends.backend_mixed import MixedModeRenderer
2222
from matplotlib.colors import rgb2hex
2323
from matplotlib.dates import UTC
@@ -1363,6 +1363,10 @@ def _print_svg(self, filename, fh, *, dpi=None, bbox_inches_restore=None,
13631363
def get_default_filetype(self):
13641364
return 'svg'
13651365

1366+
def draw(self):
1367+
_no_output_draw(self.figure)
1368+
return super().draw()
1369+
13661370

13671371
FigureManagerSVG = FigureManagerBase
13681372

lib/matplotlib/backends/backend_template.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,14 @@ class methods button_press_event, button_release_event,
191191
"""
192192

193193
def draw(self):
194-
"""Draw the figure using the renderer."""
194+
"""
195+
Draw the figure using the renderer.
196+
197+
It is important that this method actually walk the artist tree
198+
even if not output is produced because this will trigger
199+
deferred work (like computing limits auto-limits and tick
200+
values) that users may want access to before saving to disk.
201+
"""
195202
renderer = RendererTemplate(self.figure.dpi)
196203
self.figure.draw(renderer)
197204

lib/matplotlib/testing/__init__.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
import locale
66
import logging
7+
import subprocess
8+
from pathlib import Path
9+
from tempfile import TemporaryDirectory
710

811
import matplotlib as mpl
912
from matplotlib import _api
@@ -44,3 +47,31 @@ def setup():
4447
# are not necessarily the default values as specified in rcsetup.py.
4548
set_font_settings_for_testing()
4649
set_reproducibility_for_testing()
50+
51+
52+
def check_for_pgf(texsystem):
53+
"""
54+
Check if a given TeX system + pgf is available
55+
56+
Parameters
57+
----------
58+
texsystem : str
59+
The executable name to check
60+
"""
61+
with TemporaryDirectory() as tmpdir:
62+
tex_path = Path(tmpdir, "test.tex")
63+
tex_path.write_text(r"""
64+
\documentclass{minimal}
65+
\usepackage{pgf}
66+
\begin{document}
67+
\typeout{pgfversion=\pgfversion}
68+
\makeatletter
69+
\@@end
70+
""")
71+
try:
72+
subprocess.check_call(
73+
[texsystem, "-halt-on-error", str(tex_path)], cwd=tmpdir,
74+
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
75+
except (OSError, subprocess.CalledProcessError):
76+
return False
77+
return True

lib/matplotlib/tests/test_backend_bases.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import re
22

3+
from matplotlib.testing import check_for_pgf
34
from matplotlib.backend_bases import (
45
FigureCanvasBase, LocationEvent, MouseButton, MouseEvent,
56
NavigationToolbar2, RendererBase)
@@ -13,6 +14,9 @@
1314
import numpy as np
1415
import pytest
1516

17+
needs_xelatex = pytest.mark.skipif(not check_for_pgf('xelatex'),
18+
reason='xelatex + pgf is required')
19+
1620

1721
def test_uses_per_path():
1822
id = transforms.Affine2D()
@@ -183,3 +187,38 @@ def test_toolbar_zoompan():
183187
assert ax.get_navigate_mode() == "ZOOM"
184188
ax.figure.canvas.manager.toolmanager.trigger_tool('pan')
185189
assert ax.get_navigate_mode() == "PAN"
190+
191+
192+
@pytest.mark.parametrize(
193+
"backend", ['svg', 'ps', 'pdf', pytest.param('pgf', marks=needs_xelatex)]
194+
)
195+
def test_draw(backend):
196+
from matplotlib.figure import Figure
197+
from matplotlib.backends.backend_agg import FigureCanvas
198+
test_backend = pytest.importorskip(
199+
f'matplotlib.backends.backend_{backend}'
200+
)
201+
TestCanvas = test_backend.FigureCanvas
202+
fig_test = Figure(constrained_layout=True)
203+
TestCanvas(fig_test)
204+
axes_test = fig_test.subplots(2, 2)
205+
206+
# defaults to FigureCanvasBase
207+
fig_agg = Figure(constrained_layout=True)
208+
# put a backends.backend_agg.FigureCanvas on it
209+
FigureCanvas(fig_agg)
210+
axes_agg = fig_agg.subplots(2, 2)
211+
212+
init_pos = [ax.get_position() for ax in axes_test.ravel()]
213+
214+
fig_test.canvas.draw()
215+
fig_agg.canvas.draw()
216+
217+
layed_out_pos_test = [ax.get_position() for ax in axes_test.ravel()]
218+
layed_out_pos_agg = [ax.get_position() for ax in axes_agg.ravel()]
219+
220+
for init, placed in zip(init_pos, layed_out_pos_test):
221+
assert not np.allclose(init, placed, atol=0.005)
222+
223+
for ref, test in zip(layed_out_pos_agg, layed_out_pos_test):
224+
np.testing.assert_allclose(ref, test, atol=0.005)

lib/matplotlib/tests/test_backend_pgf.py

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import datetime
22
from io import BytesIO
33
import os
4-
from pathlib import Path
54
import shutil
65
import subprocess
7-
from tempfile import TemporaryDirectory
86

97
import numpy as np
108
import pytest
119

1210
import matplotlib as mpl
1311
import matplotlib.pyplot as plt
12+
from matplotlib.testing import check_for_pgf
1413
from matplotlib.testing.compare import compare_images, ImageComparisonFailure
1514
from matplotlib.backends.backend_pgf import PdfPages, common_texification
1615
from matplotlib.testing.decorators import (_image_directories,
@@ -19,32 +18,11 @@
1918

2019
baseline_dir, result_dir = _image_directories(lambda: 'dummy func')
2120

22-
23-
def check_for(texsystem):
24-
with TemporaryDirectory() as tmpdir:
25-
tex_path = Path(tmpdir, "test.tex")
26-
tex_path.write_text(r"""
27-
\documentclass{minimal}
28-
\usepackage{pgf}
29-
\begin{document}
30-
\typeout{pgfversion=\pgfversion}
31-
\makeatletter
32-
\@@end
33-
""")
34-
try:
35-
subprocess.check_call(
36-
[texsystem, "-halt-on-error", str(tex_path)], cwd=tmpdir,
37-
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
38-
except (OSError, subprocess.CalledProcessError):
39-
return False
40-
return True
41-
42-
43-
needs_xelatex = pytest.mark.skipif(not check_for('xelatex'),
21+
needs_xelatex = pytest.mark.skipif(not check_for_pgf('xelatex'),
4422
reason='xelatex + pgf is required')
45-
needs_pdflatex = pytest.mark.skipif(not check_for('pdflatex'),
23+
needs_pdflatex = pytest.mark.skipif(not check_for_pgf('pdflatex'),
4624
reason='pdflatex + pgf is required')
47-
needs_lualatex = pytest.mark.skipif(not check_for('lualatex'),
25+
needs_lualatex = pytest.mark.skipif(not check_for_pgf('lualatex'),
4826
reason='lualatex + pgf is required')
4927
needs_ghostscript = pytest.mark.skipif(
5028
"eps" not in mpl.testing.compare.converter,
@@ -338,7 +316,7 @@ def test_unknown_font(caplog):
338316
@pytest.mark.parametrize("texsystem", ("pdflatex", "xelatex", "lualatex"))
339317
@pytest.mark.backend("pgf")
340318
def test_minus_signs_with_tex(fig_test, fig_ref, texsystem):
341-
if not check_for(texsystem):
319+
if not check_for_pgf(texsystem):
342320
pytest.skip(texsystem + ' + pgf is required')
343321
mpl.rcParams["pgf.texsystem"] = texsystem
344322
fig_test.text(.5, .5, "$-1$")

0 commit comments

Comments
 (0)