From 71db23e6863031414cda1f105a75f6ebb1e735bf Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 5 May 2019 16:38:07 +0200 Subject: [PATCH] Deprecate the dryrun parameter to print_foo(). The print_foo() methods of all backends (that implement saving to a given format -- print_svg saves to svg, etc.) all have an undocumented `dryrun` parameter that effectively means "don't actually do the drawing; just set the _cachedRenderer attribute on the figure" with the intent of using that renderer object to determine the figure tight bbox for saving with bbox_inches="tight". - This behavior is not actually implemented for the pdf backend, so saving to pdf with bbox_inches="tight" will currently fully walk the artist tree twice. - This is a parameter that needs to be reimplemented again and again for all third-party backends (cough cough, mplcairo). Instead, we can extract that renderer object by fiddling with Figure.draw (see implementation of _get_renderer), which may not be the most elegant, but avoids having to reimplement the same functionality across each backend. This patch also found that Table's window_extent is incorrect until a draw() is performed. Fix that problem by correctly setting the table's text positions in get_window_extent(). --- doc/api/next_api_changes/2019-05-05-AL.rst | 4 +++ lib/matplotlib/backend_bases.py | 39 ++++++++++++++-------- lib/matplotlib/backends/backend_agg.py | 2 ++ lib/matplotlib/backends/backend_pgf.py | 1 + lib/matplotlib/backends/backend_ps.py | 1 + lib/matplotlib/table.py | 1 + 6 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 doc/api/next_api_changes/2019-05-05-AL.rst diff --git a/doc/api/next_api_changes/2019-05-05-AL.rst b/doc/api/next_api_changes/2019-05-05-AL.rst new file mode 100644 index 000000000000..d79ebbb56ef3 --- /dev/null +++ b/doc/api/next_api_changes/2019-05-05-AL.rst @@ -0,0 +1,4 @@ +Deprecations +```````````` + +The ``dryrun`` parameter to the various ``FigureCanvasFoo.print_foo`` methods is deprecated. diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 392970fce518..bff765e2b591 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1528,6 +1528,27 @@ def __init__(self, name, canvas, key, x=0, y=0, guiEvent=None): self.key = key +def _get_renderer(figure, print_method): + """ + Get the renderer that would be used to save a `~.Figure`, and cache it on + the figure. + """ + # This is implemented by triggering a draw, then immediately jumping out of + # Figure.draw() by raising an exception. + + class Done(Exception): + pass + + def _draw(renderer): raise Done(renderer) + + with cbook._setattr_cm(figure, draw=_draw): + try: + print_method(io.BytesIO()) + except Done as exc: + figure._cachedRenderer, = exc.args + return figure._cachedRenderer + + class FigureCanvasBase(object): """ The canvas the figure renders into. @@ -2038,20 +2059,11 @@ def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None, bbox_inches = rcParams['savefig.bbox'] if bbox_inches: - # call adjust_bbox to save only the given area if bbox_inches == "tight": - # When bbox_inches == "tight", it saves the figure twice. - # The first save command (to a BytesIO) is just to estimate - # the bounding box of the figure. - result = print_method( - io.BytesIO(), - dpi=dpi, - facecolor=facecolor, - edgecolor=edgecolor, - orientation=orientation, - dryrun=True, - **kwargs) - renderer = self.figure._cachedRenderer + renderer = _get_renderer( + self.figure, + functools.partial( + print_method, dpi=dpi, orientation=orientation)) bbox_artists = kwargs.pop("bbox_extra_artists", None) bbox_inches = self.figure.get_tightbbox(renderer, bbox_extra_artists=bbox_artists) @@ -2061,6 +2073,7 @@ def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None, bbox_inches = bbox_inches.padded(pad) + # call adjust_bbox to save only the given area restore_bbox = tight_bbox.adjust_bbox(self.figure, bbox_inches, canvas.fixed_dpi) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index f60bdb973b62..904b4693a9a7 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -544,6 +544,7 @@ def print_to_buffer(self): # print_figure(), and the latter ensures that `self.figure.dpi` already # matches the dpi kwarg (if any). + @cbook._delete_parameter("3.2", "dryrun") def print_jpg(self, filename_or_obj, *args, dryrun=False, pil_kwargs=None, **kwargs): """ @@ -598,6 +599,7 @@ def print_jpg(self, filename_or_obj, *args, dryrun=False, print_jpeg = print_jpg + @cbook._delete_parameter("3.2", "dryrun") def print_tif(self, filename_or_obj, *args, dryrun=False, pil_kwargs=None, **kwargs): buf, size = self.print_to_buffer() diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index 989296813e1b..f44975690c84 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -775,6 +775,7 @@ class FigureCanvasPgf(FigureCanvasBase): def get_default_filetype(self): return 'pdf' + @cbook._delete_parameter("3.2", "dryrun") def _print_pgf_to_fh(self, fh, *args, dryrun=False, bbox_inches_restore=None, **kwargs): if dryrun: diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index 4496131a3a08..d4340335762c 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -870,6 +870,7 @@ def _print_ps(self, outfile, format, *args, orientation, isLandscape, papertype, **kwargs) + @cbook._delete_parameter("3.2", "dryrun") def _print_figure( self, outfile, format, dpi=72, facecolor='w', edgecolor='w', orientation='portrait', isLandscape=False, papertype=None, diff --git a/lib/matplotlib/table.py b/lib/matplotlib/table.py index 8fb5c4708cd7..28e5e0d03d13 100644 --- a/lib/matplotlib/table.py +++ b/lib/matplotlib/table.py @@ -458,6 +458,7 @@ def get_children(self): def get_window_extent(self, renderer): """Return the bounding box of the table in window coords.""" + self._update_positions(renderer) boxes = [cell.get_window_extent(renderer) for cell in self._cells.values()] return Bbox.union(boxes)