Python – IPython Notebook widgets for Matplotlib interactivity

ipythonipython-notebookmatplotlibpython

I would like to use the ipython notebook widgets to add some degree of interactivity to inline matplotlib plots.

In general the plot can be quite heavy and I want to only update a specific element of the plot. I understand that widgets have a throttling feature built-in that helps to don't flood the kernel, but when the plot takes let say 30s I don't want to wait so long just to update a line.

By reading the example notebooks I was able to create a basic example in which I add a cross cursor (driven by 2 sliders) to a mpl axis.

The problem is that the figure is displayed twice. Here is the code (cell 1):

fig, ax = plt.subplots() 
ax.plot([3,1,2,4,0,5,3,2,0,2,4])

… figure displayed …, cell 2 (edit: thanks Thomas K for the improvement):

vline = ax.axvline(1)
hline = ax.axhline(0.5)

def set_cursor(x, y):
    vline.set_xdata((x, x))
    hline.set_ydata((y, y))
    display(fig)

and finally (cell 3):

interact(set_cursor, x=(1, 9, 0.01), y=(0, 5, 0.01))

shows again the figure with the widgets.

So the question is:

  1. how can I inhibit the first figure display?
  2. is that the right way to do it or is there a better approach?

EDIT

I found an ipython config knob that, according to this notebook, allows inhibiting the figure display

%config InlineBackend.close_figures = False

While the example notebook works, I can't figure out how to use this option by itself (without the context manager class provided in the linked example) to hide a figure display.

EDIT 2

I found some documentation of the InlineBackend.close_figures configurable.

EDIT 3

Triggered by @shadanan answer, I want to clarify that my purpose is to add a cursor to an existing figure without redrawing the plot from scratch at each cursor movement. Merging the 3 cells in a single cell:

fig, ax = plt.subplots()
ax.plot([3,1,2,4,0,5,3,2,0,2,4])

vline = ax.axvline(1)
hline = ax.axhline(0.5)

def set_cursor(x, y):
    vline.set_xdata((x, x))
    hline.set_ydata((y, y))
    display(fig)

interact(set_cursor, x=(1, 9, 0.01), y=(0, 5, 0.01))

it "should" work but it doesn't. The first time the cell is executed it shows 2 figures. After widget interaction only 1 figure is displayed. This is the "strange behavior" that requires a workaround like the one shown in @shadanan answer. Can an ipython dev comment on this? Is it a bug?

Best Answer

The solution turns out to be really simple. To avoid showing the first figure we just need to add a close() call before the interact call.

Recalling the example of the question, a cell like this will correctly show a single interactive figure (instead of two):

fig, ax = plt.subplots()
ax.plot([3,1,2,4,0,5,3,2,0,2,4])
plt.close(fig)

vline = ax.axvline(1)
hline = ax.axhline(0.5)

def set_cursor(x, y):
    vline.set_xdata((x, x))
    hline.set_ydata((y, y))
    display(fig)

interact(set_cursor, x=(1, 9, 0.01), y=(0, 5, 0.01))

A cleaner approach is defining the function add_cursor (in a separate cell or script):

def add_cursor(fig, ax):
    plt.close(fig)

    vline = ax.axvline(1, color='k')
    hline = ax.axhline(0.5, color='k')

    def set_cursor(x, y):
        vline.set_xdata((x, x))
        hline.set_ydata((y, y))
        display(fig)

    interact(set_cursor, x=ax.get_xlim(), y=ax.get_ylim())

and then call it whenever we want to add an interactive cursor:

fig, ax = plt.subplots()
ax.plot([3,1,2,4,0,5,3,2,0,2,4])
add_cursor(fig, ax)