diff --git a/IPython/lib/pylabtools.py b/IPython/lib/pylabtools.py index 3fef3fd..8c035cb 100644 --- a/IPython/lib/pylabtools.py +++ b/IPython/lib/pylabtools.py @@ -87,8 +87,8 @@ def figsize(sizex, sizey): matplotlib.rcParams['figure.figsize'] = [sizex, sizey] -def figure_to_svg(fig): - """Convert a figure to svg for inline display.""" +def print_figure(fig, fmt='png'): + """Convert a figure to svg or png for inline display.""" # When there's an empty figure, we shouldn't return anything, otherwise we # get big blank areas in the qt console. if not fig.axes: @@ -100,12 +100,15 @@ def figure_to_svg(fig): fig.set_edgecolor('white') try: string_io = StringIO() - fig.canvas.print_figure(string_io, format='svg') - svg = string_io.getvalue() + # use 72 dpi to match QTConsole's dpi + fig.canvas.print_figure(string_io, format=fmt, dpi=72) + data = string_io.getvalue() finally: fig.set_facecolor(fc) fig.set_edgecolor(ec) - return svg + if fmt == 'png': + data = data.encode('base64') + return data # We need a little factory function here to create the closure where @@ -150,6 +153,29 @@ def mpl_runner(safe_execfile): return mpl_execfile +def select_figure_format(shell, fmt): + """Select figure format for inline backend, either 'png' or 'svg'. + + Using this method ensures only one figure format is active at a time. + """ + from matplotlib.figure import Figure + from IPython.zmq.pylab import backend_inline + + svg_formatter = shell.display_formatter.formatters['image/svg+xml'] + png_formatter = shell.display_formatter.formatters['image/png'] + + if fmt=='png': + svg_formatter.type_printers.pop(Figure, None) + png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png')) + elif fmt=='svg': + png_formatter.type_printers.pop(Figure, None) + svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg')) + else: + raise ValueError("supported formats are: 'png', 'svg', not %r"%fmt) + + # set the format to be used in the backend() + backend_inline._figure_format = fmt + #----------------------------------------------------------------------------- # Code for initializing matplotlib and importing pylab #----------------------------------------------------------------------------- @@ -208,7 +234,6 @@ def activate_matplotlib(backend): # For this, we wrap it into a decorator which adds a 'called' flag. pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive) - def import_pylab(user_ns, backend, import_all=True, shell=None): """Import the standard pylab symbols into user_ns.""" @@ -228,43 +253,32 @@ def import_pylab(user_ns, backend, import_all=True, shell=None): # If using our svg payload backend, register the post-execution # function that will pick up the results for display. This can only be # done with access to the real shell object. + # + from IPython.zmq.pylab.backend_inline import InlineBackendConfig + + cfg = InlineBackendConfig.instance(config=shell.config) + cfg.shell = shell + if backend == backends['inline']: - from IPython.zmq.pylab.backend_inline import flush_svg + from IPython.zmq.pylab.backend_inline import flush_figures from matplotlib import pyplot - shell.register_post_execute(flush_svg) - # The typical default figure size is too large for inline use, - # so we shrink the figure size to 6x4, and tweak fonts to - # make that fit. This is configurable via Global.pylab_inline_rc, - # or rather it will be once the zmq kernel is hooked up to - # the config system. - - default_rc = { - 'figure.figsize': (6.0,4.0), - # 12pt labels get cutoff on 6x4 logplots, so use 10pt. - 'font.size': 10, - # 10pt still needs a little more room on the xlabel: - 'figure.subplot.bottom' : .125 - } - rc = getattr(shell.config.Global, 'pylab_inline_rc', default_rc) - pyplot.rcParams.update(rc) - shell.config.Global.pylab_inline_rc = rc + shell.register_post_execute(flush_figures) + # load inline_rc + pyplot.rcParams.update(cfg.rc) # Add 'figsize' to pyplot and to the user's namespace user_ns['figsize'] = pyplot.figsize = figsize shell.user_ns_hidden['figsize'] = figsize + # Setup the default figure format + fmt = cfg.figure_format + select_figure_format(shell, fmt) + # The old pastefig function has been replaced by display - # Always add this svg formatter so display works. - from IPython.core.display import display, display_svg - svg_formatter = shell.display_formatter.formatters['image/svg+xml'] - svg_formatter.for_type_by_name( - 'matplotlib.figure','Figure',figure_to_svg - ) + from IPython.core.display import display # Add display and display_png to the user's namespace user_ns['display'] = display shell.user_ns_hidden['display'] = display - user_ns['display_svg'] = display_svg - shell.user_ns_hidden['display_svg'] = display_svg user_ns['getfigs'] = getfigs shell.user_ns_hidden['getfigs'] = getfigs diff --git a/IPython/zmq/pylab/backend_inline.py b/IPython/zmq/pylab/backend_inline.py index 5eb9091..7944ac1 100644 --- a/IPython/zmq/pylab/backend_inline.py +++ b/IPython/zmq/pylab/backend_inline.py @@ -10,12 +10,46 @@ import sys # Third-party imports import matplotlib -from matplotlib.backends.backend_svg import new_figure_manager +from matplotlib.backends.backend_agg import new_figure_manager from matplotlib._pylab_helpers import Gcf # Local imports. +from IPython.config.configurable import SingletonConfigurable from IPython.core.displaypub import publish_display_data -from IPython.lib.pylabtools import figure_to_svg +from IPython.lib.pylabtools import print_figure, select_figure_format +from IPython.utils.traitlets import Dict, Instance, CaselessStrEnum +#----------------------------------------------------------------------------- +# Configurable for inline backend options +#----------------------------------------------------------------------------- + +class InlineBackendConfig(SingletonConfigurable): + """An object to store configuration of the inline backend.""" + + # The typical default figure size is too large for inline use, + # so we shrink the figure size to 6x4, and tweak fonts to + # make that fit. This is configurable via Global.pylab_inline_rc, + # or rather it will be once the zmq kernel is hooked up to + # the config system. + rc = Dict({'figure.figsize': (6.0,4.0), + # 12pt labels get cutoff on 6x4 logplots, so use 10pt. + 'font.size': 10, + # 10pt still needs a little more room on the xlabel: + 'figure.subplot.bottom' : .125 + }, config=True, + help="""Subset of matplotlib rcParams that should be different for the + inline backend.""" + ) + figure_format = CaselessStrEnum(['svg', 'png'], default_value='png', config=True, + help="The image format for figures with the inline backend.") + + def _format_changed(self, name, old, new): + if self.shell is None: + return + else: + select_figure_format(self.shell, new) + + shell = Instance('IPython.core.interactiveshell.InteractiveShellABC') + #----------------------------------------------------------------------------- # Functions @@ -32,7 +66,7 @@ def show(close=True): removed from the internal list of figures. """ for figure_manager in Gcf.get_all_fig_managers(): - send_svg_figure(figure_manager.canvas.figure) + send_figure(figure_manager.canvas.figure) if close: matplotlib.pyplot.close('all') @@ -50,8 +84,8 @@ def draw_if_interactive(): show._draw_called = True -def flush_svg(): - """Call show, close all open figures, sending all SVG images. +def flush_figures(): + """Call show, close all open figures, sending all figure images. This is meant to be called automatically and will call show() if, during prior code execution, there had been any calls to draw_if_interactive. @@ -61,21 +95,23 @@ def flush_svg(): show._draw_called = False -def send_svg_figure(fig): - """Draw the current figure and send it as an SVG payload. +def send_figure(fig): + """Draw the current figure and send it as a PNG payload. """ # For an empty figure, don't even bother calling figure_to_svg, to avoid # big blank spaces in the qt console if not fig.axes: return - - svg = figure_to_svg(fig) + fmt = InlineBackendConfig.instance().figure_format + data = print_figure(fig, fmt) + mimetypes = { 'png' : 'image/png', 'svg' : 'image/svg+xml' } + mime = mimetypes[fmt] # flush text streams before sending figures, helps a little with output # synchronization in the console (though it's a bandaid, not a real sln) sys.stdout.flush(); sys.stderr.flush() publish_display_data( - 'IPython.zmq.pylab.backend_inline.send_svg_figure', + 'IPython.zmq.pylab.backend_inline.send_figure', 'Matplotlib Plot', - {'image/svg+xml' : svg} + {mime : data} )