From 67beb9539e9e4eb726e67f7b1acc352e3198c387 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 26 Sep 2015 15:12:51 -0400 Subject: [PATCH 1/7] FIX: set internal flags first in FigureCanvasBase If interactive mode is on using the model where every time any artist is invalidated/marked as stale a `draw_idle` is triggered and the user is using a non-Agg based backend, saving a png will result in a draw_idle call triggered from inside the __init__ method of `FigureCanvasBase` which then fails because the full object has not been set up (this is not a problem using the IPython hooks because the stale state is only checked once when all user code has completed executing). closes #5094 --- lib/matplotlib/backend_bases.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index e3a71cb45eda..03cb4b0dbfcc 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1678,6 +1678,14 @@ class FigureCanvasBase(object): 'Tagged Image File Format') def __init__(self, figure): + # put this first to make sure that we prevent any attempts to + # re-draw while we are constructing the canvas. + # See issue #5094, this only trigged on saving a png when: + # - using a non-agg backend + # - in interactive mode + # - not bunching `draw_idle` calls (ex not in IPython) + self._is_idle_drawing = True + figure.set_canvas(self) self.figure = figure # a dictionary from event name to a dictionary that maps cid->func From e6be6b007c0d6a635f18cf6bb0ae87ccf98a26a9 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 26 Sep 2015 16:15:43 -0400 Subject: [PATCH 2/7] MNT: don't trigger draw idle if canvas is saving --- lib/matplotlib/backend_bases.py | 3 +-- lib/matplotlib/pyplot.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 03cb4b0dbfcc..6c366982e111 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1685,7 +1685,7 @@ def __init__(self, figure): # - in interactive mode # - not bunching `draw_idle` calls (ex not in IPython) self._is_idle_drawing = True - + self._is_saving = False figure.set_canvas(self) self.figure = figure # a dictionary from event name to a dictionary that maps cid->func @@ -1698,7 +1698,6 @@ def __init__(self, figure): self.scroll_pick_id = self.mpl_connect('scroll_event', self.pick) self.mouse_grabber = None # the axes currently grabbing mouse self.toolbar = None # NavigationToolbar2 will set me - self._is_saving = False self._is_idle_drawing = False @contextmanager diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index aba7af68263b..f3da99e36ccf 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -564,7 +564,7 @@ def _auto_draw_if_interactive(fig, val): fig : Figure A figure object which is assumed to be associated with a canvas """ - if val and matplotlib.is_interactive(): + if val and matplotlib.is_interactive() and not fig.canvas.is_saving(): fig.canvas.draw_idle() From 77e815e16db12bbbd81da0d6e151472b11be5477 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 26 Sep 2015 18:45:23 -0400 Subject: [PATCH 3/7] FIX/API: set_canvas does not mark figure as stale This is not part of the state of the figure that will trigger a re-drawing. If the user resets the canvas, it is their responsibility to schedule the redraw. --- lib/matplotlib/backend_bases.py | 6 ------ lib/matplotlib/figure.py | 1 - 2 files changed, 7 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 6c366982e111..1ccb528abe54 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1678,12 +1678,6 @@ class FigureCanvasBase(object): 'Tagged Image File Format') def __init__(self, figure): - # put this first to make sure that we prevent any attempts to - # re-draw while we are constructing the canvas. - # See issue #5094, this only trigged on saving a png when: - # - using a non-agg backend - # - in interactive mode - # - not bunching `draw_idle` calls (ex not in IPython) self._is_idle_drawing = True self._is_saving = False figure.set_canvas(self) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index fccf69e6e628..b640fed01317 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -560,7 +560,6 @@ def set_canvas(self, canvas): ACCEPTS: a FigureCanvas instance """ self.canvas = canvas - self.stale = True def hold(self, b=None): """ From 644b4b246d58543448c61ba2d580038a75636c01 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 26 Sep 2015 19:00:38 -0400 Subject: [PATCH 4/7] PRF: prevent possible over draws Set the `_is_saving` flag as early as possible in `print_figure` to prevent aggressive auto-drawing during the same process. --- lib/matplotlib/backend_bases.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 1ccb528abe54..b856fade9ab8 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2123,6 +2123,8 @@ def print_figure(self, filename, dpi=None, facecolor='w', edgecolor='w', tight bbox is calculated. """ + self._is_saving = True + if format is None: # get format from filename, or from backend's default filetype if cbook.is_string_like(filename): @@ -2216,7 +2218,6 @@ def print_figure(self, filename, dpi=None, facecolor='w', edgecolor='w', else: _bbox_inches_restore = None - self._is_saving = True try: #result = getattr(self, method_name)( result = print_method( From db31e6cc1b197b056683d03b58da70fbffc3e074 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 26 Sep 2015 19:05:55 -0400 Subject: [PATCH 5/7] PRF: replace more draw -> draw_idle calls --- lib/matplotlib/pyplot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index f3da99e36ccf..cfd7e204b976 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -681,11 +681,11 @@ def draw(): :class:`~matplotlib.figure.Figure` instance, :attr:`fig`, that was created using a :mod:`~matplotlib.pyplot` function, is:: - fig.canvas.draw() + fig.canvas.draw_idle() """ - get_current_fig_manager().canvas.draw() + get_current_fig_manager().canvas.draw_idle() @docstring.copy_dedent(Figure.savefig) From 621b98cd1151365e64162cb58daf000b11ca1152 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 26 Sep 2015 21:34:55 -0400 Subject: [PATCH 6/7] DOC: update plt.draw docstring --- lib/matplotlib/pyplot.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index cfd7e204b976..88979ec9bb5f 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -666,24 +666,18 @@ def clf(): def draw(): - """ - Redraw the current figure. + """Redraw the current figure. - This is used in interactive mode to update a figure that - has been altered using one or more plot object method calls; - it is not needed if figure modification is done entirely - with pyplot functions, if a sequence of modifications ends - with a pyplot function, or if matplotlib is in non-interactive - mode and the sequence of modifications ends with :func:`show` or - :func:`savefig`. + This is used in interactive mode to update a figure that has been + altered, but not automatically re-drawn. This should be only rarely + needed, but there may be ways to modify the state of a figure with + out marking it as `stale`. Please report these cases as bugs. A more object-oriented alternative, given any :class:`~matplotlib.figure.Figure` instance, :attr:`fig`, that was created using a :mod:`~matplotlib.pyplot` function, is:: fig.canvas.draw_idle() - - """ get_current_fig_manager().canvas.draw_idle() @@ -692,7 +686,7 @@ def draw(): def savefig(*args, **kwargs): fig = gcf() res = fig.savefig(*args, **kwargs) - draw() # need this if 'transparent=True' to reset colors + fig.canvas.draw_idle() # need this if 'transparent=True' to reset colors return res From 0d465acf1700dabeef66cee1940f045c3c7592f3 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 26 Sep 2015 21:42:50 -0400 Subject: [PATCH 7/7] TST: be explicit in test_backend_ps Random thrashing to prevent the intermittent failures --- lib/matplotlib/tests/test_backend_ps.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_ps.py b/lib/matplotlib/tests/test_backend_ps.py index dc9c535f7428..c01395c96d7f 100644 --- a/lib/matplotlib/tests/test_backend_ps.py +++ b/lib/matplotlib/tests/test_backend_ps.py @@ -24,20 +24,19 @@ def _test_savefig_to_stringio(format='ps', use_log=False): + fig, ax = plt.subplots() buffers = [ six.moves.StringIO(), io.StringIO(), io.BytesIO()] - plt.figure() - if use_log: - plt.yscale('log') + ax.set_yscale('log') - plt.plot([1, 2], [1, 2]) - plt.title("Déjà vu") + ax.plot([1, 2], [1, 2]) + ax.set_title("Déjà vu") for buffer in buffers: - plt.savefig(buffer, format=format) + fig.savefig(buffer, format=format) values = [x.getvalue() for x in buffers]