##// END OF EJS Templates
Add a comment to document why the figurecanvas is needed...
Jens H. Nielsen -
Show More
@@ -1,212 +1,215 b''
1 """Produce SVG versions of active plots for display by the rich Qt frontend.
1 """Produce SVG versions of active plots for display by the rich Qt frontend.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Imports
4 # Imports
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 from __future__ import print_function
6 from __future__ import print_function
7
7
8 # Standard library imports
8 # Standard library imports
9 import sys
9 import sys
10
10
11 # Third-party imports
11 # Third-party imports
12 import matplotlib
12 import matplotlib
13 from matplotlib.backends.backend_agg import new_figure_manager, FigureCanvasAgg
13 from matplotlib.backends.backend_agg import new_figure_manager, FigureCanvasAgg
14 from matplotlib._pylab_helpers import Gcf
14 from matplotlib._pylab_helpers import Gcf
15
15
16 # Local imports.
16 # Local imports.
17 from IPython.config.configurable import SingletonConfigurable
17 from IPython.config.configurable import SingletonConfigurable
18 from IPython.core.display import display
18 from IPython.core.display import display
19 from IPython.core.displaypub import publish_display_data
19 from IPython.core.displaypub import publish_display_data
20 from IPython.core.pylabtools import print_figure, select_figure_format
20 from IPython.core.pylabtools import print_figure, select_figure_format
21 from IPython.utils.traitlets import Dict, Instance, CaselessStrEnum, CBool
21 from IPython.utils.traitlets import Dict, Instance, CaselessStrEnum, CBool
22 from IPython.utils.warn import warn
22 from IPython.utils.warn import warn
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Configurable for inline backend options
25 # Configurable for inline backend options
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # inherit from InlineBackendConfig for deprecation purposes
27 # inherit from InlineBackendConfig for deprecation purposes
28 class InlineBackendConfig(SingletonConfigurable):
28 class InlineBackendConfig(SingletonConfigurable):
29 pass
29 pass
30
30
31 class InlineBackend(InlineBackendConfig):
31 class InlineBackend(InlineBackendConfig):
32 """An object to store configuration of the inline backend."""
32 """An object to store configuration of the inline backend."""
33
33
34 def _config_changed(self, name, old, new):
34 def _config_changed(self, name, old, new):
35 # warn on change of renamed config section
35 # warn on change of renamed config section
36 if new.InlineBackendConfig != old.InlineBackendConfig:
36 if new.InlineBackendConfig != old.InlineBackendConfig:
37 warn("InlineBackendConfig has been renamed to InlineBackend")
37 warn("InlineBackendConfig has been renamed to InlineBackend")
38 super(InlineBackend, self)._config_changed(name, old, new)
38 super(InlineBackend, self)._config_changed(name, old, new)
39
39
40 # The typical default figure size is too large for inline use,
40 # The typical default figure size is too large for inline use,
41 # so we shrink the figure size to 6x4, and tweak fonts to
41 # so we shrink the figure size to 6x4, and tweak fonts to
42 # make that fit.
42 # make that fit.
43 rc = Dict({'figure.figsize': (6.0,4.0),
43 rc = Dict({'figure.figsize': (6.0,4.0),
44 # play nicely with white background in the Qt and notebook frontend
44 # play nicely with white background in the Qt and notebook frontend
45 'figure.facecolor': 'white',
45 'figure.facecolor': 'white',
46 'figure.edgecolor': 'white',
46 'figure.edgecolor': 'white',
47 # 12pt labels get cutoff on 6x4 logplots, so use 10pt.
47 # 12pt labels get cutoff on 6x4 logplots, so use 10pt.
48 'font.size': 10,
48 'font.size': 10,
49 # 72 dpi matches SVG/qtconsole
49 # 72 dpi matches SVG/qtconsole
50 # this only affects PNG export, as SVG has no dpi setting
50 # this only affects PNG export, as SVG has no dpi setting
51 'savefig.dpi': 72,
51 'savefig.dpi': 72,
52 # 10pt still needs a little more room on the xlabel:
52 # 10pt still needs a little more room on the xlabel:
53 'figure.subplot.bottom' : .125
53 'figure.subplot.bottom' : .125
54 }, config=True,
54 }, config=True,
55 help="""Subset of matplotlib rcParams that should be different for the
55 help="""Subset of matplotlib rcParams that should be different for the
56 inline backend."""
56 inline backend."""
57 )
57 )
58
58
59 figure_format = CaselessStrEnum(['svg', 'png'], default_value='png', config=True,
59 figure_format = CaselessStrEnum(['svg', 'png'], default_value='png', config=True,
60 help="The image format for figures with the inline backend.")
60 help="The image format for figures with the inline backend.")
61
61
62 def _figure_format_changed(self, name, old, new):
62 def _figure_format_changed(self, name, old, new):
63 if self.shell is None:
63 if self.shell is None:
64 return
64 return
65 else:
65 else:
66 select_figure_format(self.shell, new)
66 select_figure_format(self.shell, new)
67
67
68 close_figures = CBool(True, config=True,
68 close_figures = CBool(True, config=True,
69 help="""Close all figures at the end of each cell.
69 help="""Close all figures at the end of each cell.
70
70
71 When True, ensures that each cell starts with no active figures, but it
71 When True, ensures that each cell starts with no active figures, but it
72 also means that one must keep track of references in order to edit or
72 also means that one must keep track of references in order to edit or
73 redraw figures in subsequent cells. This mode is ideal for the notebook,
73 redraw figures in subsequent cells. This mode is ideal for the notebook,
74 where residual plots from other cells might be surprising.
74 where residual plots from other cells might be surprising.
75
75
76 When False, one must call figure() to create new figures. This means
76 When False, one must call figure() to create new figures. This means
77 that gcf() and getfigs() can reference figures created in other cells,
77 that gcf() and getfigs() can reference figures created in other cells,
78 and the active figure can continue to be edited with pylab/pyplot
78 and the active figure can continue to be edited with pylab/pyplot
79 methods that reference the current active figure. This mode facilitates
79 methods that reference the current active figure. This mode facilitates
80 iterative editing of figures, and behaves most consistently with
80 iterative editing of figures, and behaves most consistently with
81 other matplotlib backends, but figure barriers between cells must
81 other matplotlib backends, but figure barriers between cells must
82 be explicit.
82 be explicit.
83 """)
83 """)
84
84
85 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
85 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
86
86
87
87
88 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
89 # Functions
89 # Functions
90 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
91
91
92 def show(close=None):
92 def show(close=None):
93 """Show all figures as SVG/PNG payloads sent to the IPython clients.
93 """Show all figures as SVG/PNG payloads sent to the IPython clients.
94
94
95 Parameters
95 Parameters
96 ----------
96 ----------
97 close : bool, optional
97 close : bool, optional
98 If true, a ``plt.close('all')`` call is automatically issued after
98 If true, a ``plt.close('all')`` call is automatically issued after
99 sending all the figures. If this is set, the figures will entirely
99 sending all the figures. If this is set, the figures will entirely
100 removed from the internal list of figures.
100 removed from the internal list of figures.
101 """
101 """
102 if close is None:
102 if close is None:
103 close = InlineBackend.instance().close_figures
103 close = InlineBackend.instance().close_figures
104 try:
104 try:
105 for figure_manager in Gcf.get_all_fig_managers():
105 for figure_manager in Gcf.get_all_fig_managers():
106 display(figure_manager.canvas.figure)
106 display(figure_manager.canvas.figure)
107 finally:
107 finally:
108 show._to_draw = []
108 show._to_draw = []
109 if close:
109 if close:
110 matplotlib.pyplot.close('all')
110 matplotlib.pyplot.close('all')
111
111
112
112
113
113
114 # This flag will be reset by draw_if_interactive when called
114 # This flag will be reset by draw_if_interactive when called
115 show._draw_called = False
115 show._draw_called = False
116 # list of figures to draw when flush_figures is called
116 # list of figures to draw when flush_figures is called
117 show._to_draw = []
117 show._to_draw = []
118
118
119
119
120 def draw_if_interactive():
120 def draw_if_interactive():
121 """
121 """
122 Is called after every pylab drawing command
122 Is called after every pylab drawing command
123 """
123 """
124 # signal that the current active figure should be sent at the end of
124 # signal that the current active figure should be sent at the end of
125 # execution. Also sets the _draw_called flag, signaling that there will be
125 # execution. Also sets the _draw_called flag, signaling that there will be
126 # something to send. At the end of the code execution, a separate call to
126 # something to send. At the end of the code execution, a separate call to
127 # flush_figures() will act upon these values
127 # flush_figures() will act upon these values
128
128
129 fig = Gcf.get_active().canvas.figure
129 fig = Gcf.get_active().canvas.figure
130
130
131 # Hack: matplotlib FigureManager objects in interacive backends (at least
131 # Hack: matplotlib FigureManager objects in interacive backends (at least
132 # in some of them) monkeypatch the figure object and add a .show() method
132 # in some of them) monkeypatch the figure object and add a .show() method
133 # to it. This applies the same monkeypatch in order to support user code
133 # to it. This applies the same monkeypatch in order to support user code
134 # that might expect `.show()` to be part of the official API of figure
134 # that might expect `.show()` to be part of the official API of figure
135 # objects.
135 # objects.
136 # For further reference:
136 # For further reference:
137 # https://github.com/ipython/ipython/issues/1612
137 # https://github.com/ipython/ipython/issues/1612
138 # https://github.com/matplotlib/matplotlib/issues/835
138 # https://github.com/matplotlib/matplotlib/issues/835
139
139
140 if not hasattr(fig, 'show'):
140 if not hasattr(fig, 'show'):
141 # Queue up `fig` for display
141 # Queue up `fig` for display
142 fig.show = lambda *a: display(fig)
142 fig.show = lambda *a: display(fig)
143
143
144 # If matplotlib was manually set to non-interactive mode, this function
144 # If matplotlib was manually set to non-interactive mode, this function
145 # should be a no-op (otherwise we'll generate duplicate plots, since a user
145 # should be a no-op (otherwise we'll generate duplicate plots, since a user
146 # who set ioff() manually expects to make separate draw/show calls).
146 # who set ioff() manually expects to make separate draw/show calls).
147 if not matplotlib.is_interactive():
147 if not matplotlib.is_interactive():
148 return
148 return
149
149
150 # ensure current figure will be drawn, and each subsequent call
150 # ensure current figure will be drawn, and each subsequent call
151 # of draw_if_interactive() moves the active figure to ensure it is
151 # of draw_if_interactive() moves the active figure to ensure it is
152 # drawn last
152 # drawn last
153 try:
153 try:
154 show._to_draw.remove(fig)
154 show._to_draw.remove(fig)
155 except ValueError:
155 except ValueError:
156 # ensure it only appears in the draw list once
156 # ensure it only appears in the draw list once
157 pass
157 pass
158 # Queue up the figure for drawing in next show() call
158 # Queue up the figure for drawing in next show() call
159 show._to_draw.append(fig)
159 show._to_draw.append(fig)
160 show._draw_called = True
160 show._draw_called = True
161
161
162
162
163 def flush_figures():
163 def flush_figures():
164 """Send all figures that changed
164 """Send all figures that changed
165
165
166 This is meant to be called automatically and will call show() if, during
166 This is meant to be called automatically and will call show() if, during
167 prior code execution, there had been any calls to draw_if_interactive.
167 prior code execution, there had been any calls to draw_if_interactive.
168
168
169 This function is meant to be used as a post_execute callback in IPython,
169 This function is meant to be used as a post_execute callback in IPython,
170 so user-caused errors are handled with showtraceback() instead of being
170 so user-caused errors are handled with showtraceback() instead of being
171 allowed to raise. If this function is not called from within IPython,
171 allowed to raise. If this function is not called from within IPython,
172 then these exceptions will raise.
172 then these exceptions will raise.
173 """
173 """
174 if not show._draw_called:
174 if not show._draw_called:
175 return
175 return
176
176
177 if InlineBackend.instance().close_figures:
177 if InlineBackend.instance().close_figures:
178 # ignore the tracking, just draw and close all figures
178 # ignore the tracking, just draw and close all figures
179 try:
179 try:
180 return show(True)
180 return show(True)
181 except Exception as e:
181 except Exception as e:
182 # safely show traceback if in IPython, else raise
182 # safely show traceback if in IPython, else raise
183 try:
183 try:
184 get_ipython
184 get_ipython
185 except NameError:
185 except NameError:
186 raise e
186 raise e
187 else:
187 else:
188 get_ipython().showtraceback()
188 get_ipython().showtraceback()
189 return
189 return
190 try:
190 try:
191 # exclude any figures that were closed:
191 # exclude any figures that were closed:
192 active = set([fm.canvas.figure for fm in Gcf.get_all_fig_managers()])
192 active = set([fm.canvas.figure for fm in Gcf.get_all_fig_managers()])
193 for fig in [ fig for fig in show._to_draw if fig in active ]:
193 for fig in [ fig for fig in show._to_draw if fig in active ]:
194 try:
194 try:
195 display(fig)
195 display(fig)
196 except Exception as e:
196 except Exception as e:
197 # safely show traceback if in IPython, else raise
197 # safely show traceback if in IPython, else raise
198 try:
198 try:
199 get_ipython
199 get_ipython
200 except NameError:
200 except NameError:
201 raise e
201 raise e
202 else:
202 else:
203 get_ipython().showtraceback()
203 get_ipython().showtraceback()
204 break
204 break
205 finally:
205 finally:
206 # clear flags for next round
206 # clear flags for next round
207 show._to_draw = []
207 show._to_draw = []
208 show._draw_called = False
208 show._draw_called = False
209
209
210
210
211 # Changes to matplotlib in version 1.2 requires a mpl backend to supply a default
212 # figurecanvas. This is set here to a Agg canvas
213 # See https://github.com/matplotlib/matplotlib/pull/1125
211 FigureCanvas = FigureCanvasAgg
214 FigureCanvas = FigureCanvasAgg
212
215
General Comments 0
You need to be logged in to leave comments. Login now