Skip to content
This repository

Add support for displaying maptlotlib axes directly. #1152

Closed
wants to merge 1 commit into from

3 participants

Fernando Perez Matthias Bussonnier Min RK
Fernando Perez
Owner

Currently, to display an Axes instance one must grab its .figure
attribute manually. Since there is no other reasonable representation
of an Axis than to show it, and so many matplotlib methods return
Axes, displaying them directly seems to be the most reasonable solution.

Note it's very late in the game and we're ready to release, so I want another pair of eyes on this one. The code is absolutely trivial, and I'm finding as I prepare some lecture notes that the inability to display an axis directly is really a drag. To see what I mean, try a notebook with these two cells before and after:

# cell 1
plot(rand(100))
ax = gca()

# cell 2
ax

It's annoying that matplotlib's various methods are kind of all over the map in what they return (figures, axes, line collections, etc). But since the axis is the basic object that does pretty much all the plotting in the matplotlib object hierarchy, I'm realizing it was an oversight not to display them as well as figures from the start.

As I said above, the code is trivial and I think it fixes an important oversight, but I do want a second look b/c we're more or less ready to go with 0.12.

Fernando Perez fperez Add support for displaying maptlotlib axes directly.
Currently, to display an Axes instance one must grab its .figure
attribute manually.  Since there is no other reasonable representation
of an Axis than to show it, and so many matplotlib methods return
Axes, displaying them directly seems to be the most reasonable solution.
bfa368e
Fernando Perez
Owner

Mmmmh. Scratch this: functions like plt.imshow() do both drawing and returning an Axes. So with this PR, a call to imshow() would show duplicate figures, the first from the drawing and the second from displaying the object.

I'm going to commit this tiny one-line fix which was something I spotted on the way and is an obvious mistake in our bug, but I'll withdraw this PR.

This problem needs more thought and possibly discussion with the core MPL team, so it's definitely not material for right now.

Matthias Bussonnier
Collaborator

Wont this also confuse user when figures have mltiple axes ? Like , If I take an example of matplotlib :

#cell 1
import matplotlib.mlab as mlab
from matplotlib.pyplot import figure, show
import numpy as np

x = np.arange(0.0, 2, 0.01)
y1 = np.sin(2*np.pi*x)
y2 = 1.2*np.sin(4*np.pi*x)

fig = figure()
ax1 = fig.add_subplot(311)
ax2 = fig.add_subplot(312, sharex=ax1)
ax3 = fig.add_subplot(313, sharex=ax1)

ax1.fill_between(x, 0, y1)
ax1.set_ylabel('between y1 and 0')

ax2.fill_between(x, y1, 1)
ax2.set_ylabel('between y1 and 1')

ax3.fill_between(x, y1, y2)
ax3.set_ylabel('between y1 and y2')
ax3.set_xlabel('x')

Now I want to see the whole figure :

#cell2
fig
#show the 3 graph in column 

Hum, what if I only want to see how the first graph look like ?

#cell3
ax1
#same as just above, I would expect only the first of the 3 graph (as a new user)

I don't even know if it is possible to extract one axis from a figure, or get a copy, and display it alone..

Fernando Perez
Owner

Well, since you can only display a figure and not one individual axis, when any axis is displayed it would always show the whole figure. But note that I closed the PR, because certain plot methods return the axis, so in those cases you'd get double figures (imshow does that). So for now this is closed, I'll leave it here just so that we can have the old discussion for reference, but it's not meant to ever be merged like this. At some point we may revisit the mpl api question to see if something like this can be implemented cleanly, but it can't with the current mpl api.

Min RK
Owner

Just some extra commentary, as I've thought about this one as well.

I've actually done this locally a number of times, because it is extremely logical based on the return types of matplotlib. But it is very hard to avoid double plots. I discovered that we are actually helped by the fact that matplotlib methods don't return the Figure.

For example:

def plot_and_return(*args, **kwargs):
    plt.plot(*args, **kwargs)
    return plt.gcf()

plot_and_return(rand(100))

will result in two drawings of the figure - one caused by matplotlib's draw request, and one caused by the displayhook. Any code that adds more commonly returned objects to the display registry is going to have to add some logic for tracking which figures have been drawn in this cell, and avoid duplicates drawn by display, displayhook, and matplotlib directly.

Note that this issue of duplicate drawing only affects the inline backend, where there is more than one mechanism for invoking a display action. If you are using a regular matplotlb backend, then it's appropriate to register every matplotlib Artist, since there's no ambiguity about who is responsible for drawing inline figures.

I also think it's actually that our notion of what to draw is a single object - the Figure. It's true that matplotlib return objects make it slightly less convenient to hold onto a figure than an Axes object, but the corresponding fig is rarely more than an obj.get_figure() away. And there's the fact that it is what we actually draw, which avoids potential ambiguity of the display result for subplots, etc.

Fernando Perez
Owner

Agreed with the above. If we were designing both the pylab support and matplotlib de novo, we might go with a different approach and not have any automatic showing of objects at all, instead relying on either the user calling explicitly display(...) or using a more aggressive displayhook like the one in this PR.

I actually like that idea: it would make pylab parallel how you print normal objects: you can either call print x to see x's value, or you can put a naked x in the last cell to trigger its display via the displayhook. This would be easy to explain to people, and I like the uniformity of it.

Unfortunately it's hard to implement, because things like the simple plot([1,2,3]) in mpl return a plain python list of Line2D objects; the introspection to detect mpl objects inside containers for display could get very ugly in a hurry.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 1 unique commit by 1 author.

Dec 14, 2011
Fernando Perez fperez Add support for displaying maptlotlib axes directly.
Currently, to display an Axes instance one must grab its .figure
attribute manually.  Since there is no other reasonable representation
of an Axis than to show it, and so many matplotlib methods return
Axes, displaying them directly seems to be the most reasonable solution.
bfa368e
This page is out of date. Refresh to see the latest.

Showing 1 changed file with 16 additions and 1 deletion. Show diff stats Hide diff stats

  1. +16 1 IPython/core/pylabtools.py
17 IPython/core/pylabtools.py
@@ -92,7 +92,7 @@ def print_figure(fig, fmt='png'):
92 92 """Convert a figure to svg or png for inline display."""
93 93 # When there's an empty figure, we shouldn't return anything, otherwise we
94 94 # get big blank areas in the qt console.
95   - if not fig.axes:
  95 + if not fig.axes and not fig.lines:
96 96 return
97 97
98 98 fc = fig.get_facecolor()
@@ -109,6 +109,11 @@ def print_figure(fig, fmt='png'):
109 109 return data
110 110
111 111
  112 +def print_axes(ax, fmt='png'):
  113 + """Print an Axes object via its associated figure"""
  114 + return print_figure(ax.figure, fmt)
  115 +
  116 +
112 117 # We need a little factory function here to create the closure where
113 118 # safe_execfile can live.
114 119 def mpl_runner(safe_execfile):
@@ -156,7 +161,9 @@ def select_figure_format(shell, fmt):
156 161
157 162 Using this method ensures only one figure format is active at a time.
158 163 """
  164 + from matplotlib.axes import Axes
159 165 from matplotlib.figure import Figure
  166 + from matplotlib.image import AxesImage
160 167 from IPython.zmq.pylab import backend_inline
161 168
162 169 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
@@ -165,9 +172,17 @@ def select_figure_format(shell, fmt):
165 172 if fmt=='png':
166 173 svg_formatter.type_printers.pop(Figure, None)
167 174 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
  175 + svg_formatter.type_printers.pop(Axes, None)
  176 + png_formatter.for_type(Axes, lambda ax: print_axes(ax, 'png'))
  177 + svg_formatter.type_printers.pop(AxesImage, None)
  178 + png_formatter.for_type(AxesImage, lambda ax: print_axes(ax, 'png'))
168 179 elif fmt=='svg':
169 180 png_formatter.type_printers.pop(Figure, None)
170 181 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
  182 + png_formatter.type_printers.pop(Axes, None)
  183 + svg_formatter.for_type(Axes, lambda ax: print_axes(ax, 'svg'))
  184 + png_formatter.type_printers.pop(AxesImage, None)
  185 + svg_formatter.for_type(AxesImage, lambda ax: print_axes(ax, 'svg'))
171 186 else:
172 187 raise ValueError("supported formats are: 'png', 'svg', not %r"%fmt)
173 188

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.