Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Fixes to the display system #370

Merged
merged 4 commits into from

5 participants

@ellisonbg
Owner

The IPython formatters use special methods to compute the format
of objects. These special methods have names like __html__, but
with this commit these have been changed to _repr_html_.

I have also added a Javascript formatter and fixed a bug in
pylab tools in getfigs.

Some remaining questions:

  • We are still using the old logic in pretty.py and the special name __pretty__. Do we want to update pretty.py as well and use _repr_pretty_?
  • Are we happy with the special names in this branch?
  • Should _repr_png_ return the raw png data or base64 encoded data (required for sending over json). Currently we base64 encode.
  • Should the latex representation include the wrapping $ or $$?
  • Should the javascript repr output the raw javascript or include it in a <script> tag pair.
@mdboom

To answer your question: "Should the latex representation include the wrapping $ or $$?", meaning (I think), the result of repr_latex should include the dollar signs, I would advocate for "yes". The matplotlib mathtext parser supports text outside of math mode, eg.:

r"Result of expression: $\sqrt{x}$"

Also, if one ever wanted to use a "call out to the real LaTeX" approach to this, there's all kinds of text layout features in LaTeX outside of math mode that one might want to use.

@mdboom

Quick sanity check also: Is the LaTeX inline display not yet working? I see how I can get it to display LaTeX for Sympy objects, but what about an arbitrary object with a repr_latex method?

@ellisonbg
Owner

The repr_latex method does work, but the qtconsole does not have native latex rendering. The htmlnotebook we are working on does have native latex support, but it is not all wired up in the Javascript yet. Not sure how to get qt to handle latex (without rendering to png like we do for sympy currently).

@mdboom
@ellisonbg
Owner
@mdboom

Yeah -- the mathtext stuff in matplotlib is still a subset of what a standard LaTeX distribution has. It's reasonably complete wrt to TeX, on which it is based, but it's missing most of the macros that LaTeX layers on top.

You could use dvipng and real LaTeX to generate the equation images if LaTeX is available. It adds a really heavy dependency, but it's the only thing I'm aware of that is as complete as LaTeX (because it is!). The Sphinx mathpng extension is a good concise example of using dvipng to get a LaTeX image. There is dvi parsing code in matplotlib, but it is complicated by the fact that it deals with scalable fonts etc. -- something you wouldn't necessary want to do for an interactive console.

@mdboom

I've added a function to matplotlib master that converts a math expression alone to any of the image formats that matplotlib supports. See this pull request:

matplotlib/matplotlib#84

Since that function won't exist in a released matplotlib for a while, you may want to copy it into IPython and use it when it isn't available in matplotlib -- it uses things that have been stable for quite some time.

@fperez
Owner
@minrk
Owner
  • Should the javascript repr output the raw javascript or include it in a <script> tag pair.

It should definitely exclude the script tag, because the tag is HTML, and it would no longer be a valid javascript representation if you include it.

The use case is javascript-based frontends. If the frontend wants to do any handling on the data, it would have to strip the tags before parsing. It's more trivial to add tags in frontends that just want to echo into HTML than it is to strip tags for frontends that want to look at the data.

@ellisonbg
Owner

OK, I am going to make $/$$ required for latex output, the png/etc formats will not encode (zmq layer will do that) and the javascript will includes the <script> tags. Still waiting for @rkern to give us his thoughts on the pretty.py issues. Also, we will copy the new function @mdbloom wrote for generating svg output from latex and incorporate that into a default latex formatter.

@rkern

Sure.

@minrk
Owner

Please don't include <script> tags, for reasons described in my previous comment.

@ellisonbg
Owner

@minrk: OK, I understand your logic here and I think I agree with you.

ellisonbg added some commits
@ellisonbg ellisonbg Renaming the special methods of the formatters.
The IPython formatters use special methods to compute the format
of objects. These special methods have names like "__html__", but
with this commit these have been changed to "_repr_html_".

I have also added a Javascript formatter and fixed a bug in
pylab tools in getfigs.
43a27cb
@ellisonbg ellisonbg Renamed __pretty__ to _repr_pretty_ and changed updated pretty.py
* Throughout the codebase, __pretty__ has been changed to
  _repr_pretty_ to match general convention for special method
  names.
* The logic in pretty.py now matches that in formatters.py in that
  formatters that are callables are tried first and then special
  methods are used.
4241df5
@ellisonbg ellisonbg Misc updates the display system.
* PNG base64 encoding is now done at the ZMQ level, not in the
  display formatter itself.
* All formatters are documented as to the exact nature of their
  return value. PNGs are not base64 encoded, LaTeX must include
  $/$$, Javascript should not have the <script> tags, etc.
* Updated the Circle display example in docs/examples/core.
* Renamed the sympy printing extension to sympyprinting.py.
e7df0ac
@ellisonbg ellisonbg Added mdboom's math_to_image from matplotlib. 290c0f4
@ellisonbg ellisonbg merged commit 290c0f4 into from
@damianavila damianavila referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on May 17, 2011
  1. @ellisonbg

    Renaming the special methods of the formatters.

    ellisonbg authored
    The IPython formatters use special methods to compute the format
    of objects. These special methods have names like "__html__", but
    with this commit these have been changed to "_repr_html_".
    
    I have also added a Javascript formatter and fixed a bug in
    pylab tools in getfigs.
  2. @ellisonbg

    Renamed __pretty__ to _repr_pretty_ and changed updated pretty.py

    ellisonbg authored
    * Throughout the codebase, __pretty__ has been changed to
      _repr_pretty_ to match general convention for special method
      names.
    * The logic in pretty.py now matches that in formatters.py in that
      formatters that are callables are tried first and then special
      methods are used.
  3. @ellisonbg

    Misc updates the display system.

    ellisonbg authored
    * PNG base64 encoding is now done at the ZMQ level, not in the
      display formatter itself.
    * All formatters are documented as to the exact nature of their
      return value. PNGs are not base64 encoded, LaTeX must include
      $/$$, Javascript should not have the <script> tags, etc.
    * Updated the Circle display example in docs/examples/core.
    * Renamed the sympy printing extension to sympyprinting.py.
  4. @ellisonbg
This page is out of date. Refresh to see the latest.
View
8 IPython/core/display.py
@@ -119,4 +119,12 @@ def display_json(*objs):
display(*objs, include=['text/plain','application/json'])
+def display_javascript(*objs):
+ """Display the Javascript representation of an object.
+ Parameters
+ ----------
+ objs : tuple of objects
+ The Python objects to display.
+ """
+ display(*objs, include=['text/plain','application/javascript'])
View
70 IPython/core/formatters.py
@@ -52,7 +52,8 @@ def _formatters_default(self):
SVGFormatter,
PNGFormatter,
LatexFormatter,
- JSONFormatter
+ JSONFormatter,
+ JavascriptFormatter
]
d = {}
for cls in formatter_classes:
@@ -220,15 +221,14 @@ def __call__(self, obj):
obj_id = id(obj)
try:
obj_class = getattr(obj, '__class__', None) or type(obj)
- if hasattr(obj_class, self.print_method):
- printer = getattr(obj_class, self.print_method)
- return printer(obj)
+ # First try to find registered singleton printers for the type.
try:
printer = self.singleton_printers[obj_id]
except (TypeError, KeyError):
pass
else:
return printer(obj)
+ # Next look for type_printers.
for cls in pretty._get_mro(obj_class):
if cls in self.type_printers:
return self.type_printers[cls](obj)
@@ -236,6 +236,10 @@ def __call__(self, obj):
printer = self._in_deferred_types(cls)
if printer is not None:
return printer(obj)
+ # Finally look for special method names.
+ if hasattr(obj_class, self.print_method):
+ printer = getattr(obj_class, self.print_method)
+ return printer(obj)
return None
except Exception:
pass
@@ -339,8 +343,8 @@ def dtype_pprinter(obj, p, cycle):
# something.
enabled = Bool(True, config=False)
- # Look for a __pretty__ methods to use for pretty printing.
- print_method = Str('__pretty__')
+ # Look for a _repr_pretty_ methods to use for pretty printing.
+ print_method = Str('_repr_pretty_')
# Whether to pretty-print or not.
pprint = Bool(True, config=True)
@@ -442,66 +446,97 @@ class HTMLFormatter(BaseFormatter):
"""An HTML formatter.
To define the callables that compute the HTML representation of your
- objects, define a :meth:`__html__` method or use the :meth:`for_type`
+ objects, define a :meth:`_repr_html_` method or use the :meth:`for_type`
or :meth:`for_type_by_name` methods to register functions that handle
this.
+
+ The return value of this formatter should be a valid HTML snippet that
+ could be injected into an existing DOM. It should *not* include the
+ ```<html>`` or ```<body>`` tags.
"""
format_type = Str('text/html')
- print_method = Str('__html__')
+ print_method = Str('_repr_html_')
class SVGFormatter(BaseFormatter):
"""An SVG formatter.
To define the callables that compute the SVG representation of your
- objects, define a :meth:`__svg__` method or use the :meth:`for_type`
+ objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type`
or :meth:`for_type_by_name` methods to register functions that handle
this.
+
+ The return value of this formatter should be valid SVG enclosed in
+ ```<svg>``` tags, that could be injected into an existing DOM. It should
+ *not* include the ```<html>`` or ```<body>`` tags.
"""
format_type = Str('image/svg+xml')
- print_method = Str('__svg__')
+ print_method = Str('_repr_svg_')
class PNGFormatter(BaseFormatter):
"""A PNG formatter.
To define the callables that compute the PNG representation of your
- objects, define a :meth:`__png__` method or use the :meth:`for_type`
+ objects, define a :meth:`_repr_png_` method or use the :meth:`for_type`
or :meth:`for_type_by_name` methods to register functions that handle
- this. The raw data should be the base64 encoded raw png data.
+ this.
+
+ The return value of this formatter should be raw PNG data, *not*
+ base64 encoded.
"""
format_type = Str('image/png')
- print_method = Str('__png__')
+ print_method = Str('_repr_png_')
class LatexFormatter(BaseFormatter):
"""A LaTeX formatter.
To define the callables that compute the LaTeX representation of your
- objects, define a :meth:`__latex__` method or use the :meth:`for_type`
+ objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type`
or :meth:`for_type_by_name` methods to register functions that handle
this.
+
+ The return value of this formatter should be a valid LaTeX equation,
+ enclosed in either ```$``` or ```$$```.
"""
format_type = Str('text/latex')
- print_method = Str('__latex__')
+ print_method = Str('_repr_latex_')
class JSONFormatter(BaseFormatter):
"""A JSON string formatter.
To define the callables that compute the JSON string representation of
- your objects, define a :meth:`__json__` method or use the :meth:`for_type`
+ your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type`
or :meth:`for_type_by_name` methods to register functions that handle
this.
+
+ The return value of this formatter should be a valid JSON string.
"""
format_type = Str('application/json')
- print_method = Str('__json__')
+ print_method = Str('_repr_json_')
+
+
+class JavascriptFormatter(BaseFormatter):
+ """A Javascript formatter.
+
+ To define the callables that compute the Javascript representation of
+ your objects, define a :meth:`_repr_javascript_` method or use the
+ :meth:`for_type` or :meth:`for_type_by_name` methods to register functions
+ that handle this.
+
+ The return value of this formatter should be valid Javascript code and
+ should *not* be enclosed in ```<script>``` tags.
+ """
+ format_type = Str('application/javascript')
+ print_method = Str('_repr_javascript_')
FormatterABC.register(BaseFormatter)
FormatterABC.register(PlainTextFormatter)
@@ -510,6 +545,7 @@ class JSONFormatter(BaseFormatter):
FormatterABC.register(PNGFormatter)
FormatterABC.register(LatexFormatter)
FormatterABC.register(JSONFormatter)
+FormatterABC.register(JavascriptFormatter)
def format_display_data(obj, include=None, exclude=None):
View
2  IPython/core/usage.py
@@ -458,7 +458,7 @@
representations will be displayed in the console as long as the objects know
how to compute those representations. The easiest way of teaching objects how
to format themselves in various representations is to define special methods
-such as: ``__html``, ``__svg__`` and ``__png__``. IPython's display formatters
+such as: ``_repr_html_``, ``_repr_svg_`` and ``_repr_png_``. IPython's display formatters
can also be given custom formatter functions for various types::
In [6]: ip = get_ipython()
View
4 IPython/extensions/sympy_printing.py → IPython/extensions/sympyprinting.py
@@ -39,13 +39,13 @@ def print_basic_unicode(o, p, cycle):
def print_png(o):
- """A funciton to display sympy expression using LaTex -> PNG."""
+ """A function to display sympy expression using LaTex -> PNG."""
s = latex(o, mode='inline')
# mathtext does not understand certain latex flags, so we try to replace
# them with suitable subs.
s = s.replace('\\operatorname','')
s = s.replace('\\overline', '\\bar')
- png = latex_to_png(s, encode=True)
+ png = latex_to_png(s)
return png
_loaded = False
View
50 IPython/lib/latextools.py
@@ -25,7 +25,7 @@
#-----------------------------------------------------------------------------
-def latex_to_png(s, encode=True):
+def latex_to_png(s, encode=False):
"""Render a LaTeX string to PNG using matplotlib.mathtext.
Parameters
@@ -45,6 +45,7 @@ def latex_to_png(s, encode=True):
bin_data = encodestring(bin_data)
return bin_data
+
_data_uri_template_png = """<img src="data:image/png;base64,%s" alt=%s />"""
def latex_to_html(s, alt='image'):
@@ -60,3 +61,50 @@ def latex_to_html(s, alt='image'):
base64_data = latex_to_png(s, encode=True)
return _data_uri_template_png % (base64_data, alt)
+
+# From matplotlib, thanks to mdboom. Once this is in matplotlib releases, we
+# will remove.
+def math_to_image(s, filename_or_obj, prop=None, dpi=None, format=None):
+ """
+ Given a math expression, renders it in a closely-clipped bounding
+ box to an image file.
+
+ *s*
+ A math expression. The math portion should be enclosed in
+ dollar signs.
+
+ *filename_or_obj*
+ A filepath or writable file-like object to write the image data
+ to.
+
+ *prop*
+ If provided, a FontProperties() object describing the size and
+ style of the text.
+
+ *dpi*
+ Override the output dpi, otherwise use the default associated
+ with the output format.
+
+ *format*
+ The output format, eg. 'svg', 'pdf', 'ps' or 'png'. If not
+ provided, will be deduced from the filename.
+ """
+ from matplotlib import figure
+ # backend_agg supports all of the core output formats
+ from matplotlib.backends import backend_agg
+ from matplotlib.font_manager import FontProperties
+ from matplotlib.mathtext import MathTextParser
+
+ if prop is None:
+ prop = FontProperties()
+
+ parser = MathTextParser('path')
+ width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop)
+
+ fig = figure.Figure(figsize=(width / 72.0, height / 72.0))
+ fig.text(0, depth/height, s, fontproperties=prop)
+ backend_agg.FigureCanvasAgg(fig)
+ fig.savefig(filename_or_obj, dpi=dpi, format=format)
+
+ return depth
+
View
19 IPython/lib/pretty.py
@@ -29,12 +29,12 @@
The pretty library allows developers to add pretty printing rules for their
own objects. This process is straightforward. All you have to do is to
- add a `__pretty__` method to your object and call the methods on the
+ add a `_repr_pretty_` method to your object and call the methods on the
pretty printer passed::
class MyObject(object):
- def __pretty__(self, p, cycle):
+ def _repr_pretty_(self, p, cycle):
...
Depending on the python version you want to support you have two
@@ -42,13 +42,13 @@ def __pretty__(self, p, cycle):
compatibility one.
- Here the example implementation of a `__pretty__` method for a list
+ Here the example implementation of a `_repr_pretty_` method for a list
subclass for python 2.5 and higher (python 2.5 requires the with statement
__future__ import)::
class MyList(list):
- def __pretty__(self, p, cycle):
+ def _repr_pretty_(self, p, cycle):
if cycle:
p.text('MyList(...)')
else:
@@ -75,7 +75,7 @@ def __pretty__(self, p, cycle):
class MyList(list):
- def __pretty__(self, p, cycle):
+ def _repr_pretty_(self, p, cycle):
if cycle:
p.text('MyList(...)')
else:
@@ -164,7 +164,7 @@ class PrettyPrinter(_PrettyPrinterBase):
"""
Baseclass for the `RepresentationPrinter` prettyprinter that is used to
generate pretty reprs of objects. Contrary to the `RepresentationPrinter`
- this printer knows nothing about the default pprinters or the `__pretty__`
+ this printer knows nothing about the default pprinters or the `_repr_pretty_`
callback method.
"""
@@ -330,14 +330,14 @@ def pretty(self, obj):
self.begin_group()
try:
obj_class = getattr(obj, '__class__', None) or type(obj)
- if hasattr(obj_class, '__pretty__'):
- return obj_class.__pretty__(obj, self, cycle)
+ # First try to find registered singleton printers for the type.
try:
printer = self.singleton_pprinters[obj_id]
except (TypeError, KeyError):
pass
else:
return printer(obj, self, cycle)
+ # Next look for type_printers.
for cls in _get_mro(obj_class):
if cls in self.type_pprinters:
return self.type_pprinters[cls](obj, self, cycle)
@@ -345,6 +345,9 @@ def pretty(self, obj):
printer = self._in_deferred_types(cls)
if printer is not None:
return printer(obj, self, cycle)
+ # Finally look for special method names.
+ if hasattr(obj_class, '_repr_pretty_'):
+ return obj_class._repr_pretty_(obj, self, cycle)
return _default_pprint(obj, self, cycle)
finally:
self.end_group()
View
3  IPython/lib/pylabtools.py
@@ -60,7 +60,8 @@ def getfigs(*fig_nums):
f = Gcf.figs.get(num)
if f is None:
print('Warning: figure %s not available.' % num)
- figs.append(f.canvas.figure)
+ else:
+ figs.append(f.canvas.figure)
return figs
View
4 IPython/zmq/zmqshell.py
@@ -16,6 +16,7 @@
from __future__ import print_function
# Stdlib
+from base64 import encodestring
import inspect
import os
@@ -67,6 +68,9 @@ def write_output_prompt(self):
self.msg['content']['execution_count'] = self.prompt_count
def write_format_data(self, format_dict):
+ pngdata = format_dict.get('image/png')
+ if pngdata is not None:
+ format_dict['image/png'] = encodestring(pngdata)
self.msg['content']['data'] = format_dict
def finish_displayhook(self):
View
27 docs/examples/core/display.py
@@ -0,0 +1,27 @@
+"""Code that shows off the IPython display logic.
+"""
+
+from IPython.lib.latextools import latex_to_png
+from IPython.core.display import (
+ display, display_pretty, display_html,
+ display_svg, display_json, display_png
+)
+
+class Circle(object):
+
+ def __init__(self, radius):
+ self.radius = radius
+
+ def _repr_pretty_(self, p, cycle):
+ p.text(u"\u25CB")
+
+ def _repr_html_(self):
+ return "<h1>Cirle: radius=%s</h1>" % self.radius
+
+ def _repr_svg_(self):
+ return """<svg>
+<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>
+</svg>"""
+
+ def _repr_png_(self):
+ return latex_to_png('$\circle$')
Something went wrong with that request. Please try again.