From 78e198f21ec1f7c52e111a6682d28966a6fda14e 2014-02-14 03:53:05 From: MinRK Date: 2014-02-14 03:53:05 Subject: [PATCH] add InlineBackend.print_figure_kwargs removes special handling of 'quality', in favor of passing arbitrary kwargs to fig.canvas.print_figure. This lets people override the occasionally problematic use of `bbox_inches='tight'`, and any potential future issue that might be solved by adding/changing kwargs to print_figure. --- diff --git a/IPython/core/pylabtools.py b/IPython/core/pylabtools.py index 106b856..da5bf9d 100644 --- a/IPython/core/pylabtools.py +++ b/IPython/core/pylabtools.py @@ -95,9 +95,11 @@ def figsize(sizex, sizey): matplotlib.rcParams['figure.figsize'] = [sizex, sizey] -def print_figure(fig, fmt='png', quality=90): - """Convert a figure to svg, png or jpg for inline display. - Quality is only relevant for jpg. +def print_figure(fig, fmt='png', **kwargs): + """Print a figure to an image, and return the resulting bytes + + Any extra keyword args are passed to fig.canvas.print_figure, + such as ``quality`` or ``bbox_inches``. """ from matplotlib import rcParams # When there's an empty figure, we shouldn't return anything, otherwise we @@ -105,21 +107,29 @@ def print_figure(fig, fmt='png', quality=90): if not fig.axes and not fig.lines: return - fc = fig.get_facecolor() - ec = fig.get_edgecolor() - bytes_io = BytesIO() dpi = rcParams['savefig.dpi'] if fmt == 'retina': dpi = dpi * 2 fmt = 'png' - fig.canvas.print_figure(bytes_io, format=fmt, bbox_inches='tight', - facecolor=fc, edgecolor=ec, dpi=dpi, quality=quality) - data = bytes_io.getvalue() - return data -def retina_figure(fig): + # build keyword args + kw = dict( + format=fmt, + fc=fig.get_facecolor(), + ec=fig.get_edgecolor(), + dpi=dpi, + ) + # **kwargs get higher priority + kw.update(kwargs) + print(kw) + + bytes_io = BytesIO() + fig.canvas.print_figure(bytes_io, **kw) + return bytes_io.getvalue() + +def retina_figure(fig, **kwargs): """format a figure as a pixel-doubled (retina) PNG""" - pngdata = print_figure(fig, fmt='retina') + pngdata = print_figure(fig, fmt='retina', **kwargs) w, h = _pngxy(pngdata) metadata = dict(width=w//2, height=h//2) return pngdata, metadata @@ -166,17 +176,17 @@ def mpl_runner(safe_execfile): return mpl_execfile -def select_figure_formats(shell, formats, quality=90): +def select_figure_formats(shell, formats, **kwargs): """Select figure formats for the inline backend. Parameters ========== shell : InteractiveShell The main IPython instance. - formats : list + formats : str or set One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'. - quality : int - A percentage for the quality of JPEG figures. + **kwargs : any + Extra keyword arguments to be passed to fig.canvas.print_figure. """ from matplotlib.figure import Figure from IPython.kernel.zmq.pylab import backend_inline @@ -188,22 +198,27 @@ def select_figure_formats(shell, formats, quality=90): if isinstance(formats, py3compat.string_types): formats = {formats} + # cast in case of list / tuple + formats = set(formats) - [ f.type_printers.pop(Figure, None) for f in {svg_formatter, png_formatter, jpg_formatter} ] - - for fmt in formats: - if fmt == 'png': - png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png')) - elif fmt in ('png2x', 'retina'): - png_formatter.for_type(Figure, retina_figure) - elif fmt in ('jpg', 'jpeg'): - jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', quality)) - elif fmt == 'svg': - svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg')) - elif fmt == 'pdf': - pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf')) - else: - raise ValueError("supported formats are: 'png', 'retina', 'svg', 'jpg', 'pdf' not %r" % fmt) + [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ] + + supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'} + bad = formats.difference(supported) + if bad: + s = "{%s}" % ",".join([repr(f) for f in bad]) + raise ValueError("supported formats are: 'png', 'retina', 'svg', 'jpg', 'pdf' not %s" % s) + + if 'png' in formats: + png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs)) + if 'retina' in formats or 'png2x' in formats: + png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs)) + if 'jpg' in formats or 'jpeg' in formats: + jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', **kwargs)) + if 'svg' in formats: + svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg', **kwargs)) + if 'pdf' in formats: + pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf', **kwargs)) #----------------------------------------------------------------------------- # Code for initializing matplotlib and importing pylab @@ -354,5 +369,5 @@ def configure_inline_support(shell, backend): del shell._saved_rcParams # Setup the default figure format - select_figure_formats(shell, cfg.figure_formats, cfg.quality) + select_figure_formats(shell, cfg.figure_formats, **cfg.print_figure_kwargs) diff --git a/IPython/kernel/zmq/pylab/config.py b/IPython/kernel/zmq/pylab/config.py index 680155b..bf0d99b 100644 --- a/IPython/kernel/zmq/pylab/config.py +++ b/IPython/kernel/zmq/pylab/config.py @@ -69,6 +69,10 @@ class InlineBackend(InlineBackendConfig): help="""A set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.""") + def _update_figure_formatters(self): + if self.shell is not None: + select_figure_formats(self.shell, self.figure_formats, **self.print_figure_kwargs) + def _figure_formats_changed(self, name, old, new): from IPython.core.pylabtools import select_figure_formats if 'jpg' in new or 'jpeg' in new: @@ -77,7 +81,7 @@ class InlineBackend(InlineBackendConfig): if self.shell is None: return else: - select_figure_formats(self.shell, new) + self._update_figure_formatters() figure_format = Unicode(config=True, help="""The figure format to enable (deprecated use `figure_formats` instead)""") @@ -86,12 +90,13 @@ class InlineBackend(InlineBackendConfig): if new: self.figure_formats = {new} - quality = Int(default_value=90, config=True, - help="Quality of compression [10-100], currently for lossy JPEG only.") - - def _quality_changed(self, name, old, new): - if new < 10 or new > 100: - raise TraitError("figure JPEG quality must be in [10-100] range.") + print_figure_kwargs = Dict({'bbox_inches' : 'tight'}, config=True, + help="""Extra kwargs to be passed to fig.canvas.print_figure. + + Logical examples include: bbox_inches, quality (for jpeg figures), etc. + """ + ) + _print_figure_kwargs_changed = _update_figure_formatters close_figures = Bool(True, config=True, help="""Close all figures at the end of each cell. @@ -109,7 +114,7 @@ class InlineBackend(InlineBackendConfig): other matplotlib backends, but figure barriers between cells must be explicit. """) - + shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')