Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IPython Notebook widgets for Matplotlib interactivity

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?

like image 913
user2304916 Avatar asked Jul 25 '14 17:07

user2304916


People also ask

Can you make matplotlib interactive?

But did you know that it is also possible to create interactive plots with matplotlib directly, provided you are using an interactive backend? This article will look at two such backends and how they render interactivity within the notebooks, using only matplotlib.

Can Jupyter notebooks be interactive?

Jupyter Notebook has support for many kinds of interactive outputs, including the ipywidgets ecosystem as well as many interactive visualization libraries.

Is Bqplot interactive?

bqplot is a Grammar of Graphics-based interactive plotting framework for the Jupyter notebook. In bqplot, every single attribute of the plot is an interactive widget. This allows the user to integrate any plot with IPython widgets to create a complex and feature rich GUI from just a few simple lines of Python code.

Can you use matplotlib in Jupyter notebook?

Install MatplotlibMake sure you first have Jupyter notebook installed, then we can add Matplotlib to our virtual environment. To do so, navigate to the command prompt and type pip install matplotlib. Now launch your Jupyter notebook by simply typing jupyter notebook at the command prompt.


2 Answers

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)
like image 55
user2304916 Avatar answered Nov 05 '22 02:11

user2304916


I have a hacky workaround that will only display one figure. The problem seems to be that there are two points in the code that generate a figure and really, we only want the second one, but we can't get away with inhibiting the first. The workaround is to use the first one for the first execution and the second one for all subsequent ones. Here's some code that works by switching between the two depending on the initialized flag:

%matplotlib inline
import matplotlib.pyplot as plt
from IPython.html.widgets import interact, interactive, fixed
from IPython.html import widgets
from IPython.display import clear_output, display, HTML

class InteractiveCursor(object):
    initialized = False
    fig = None
    ax = None
    vline = None
    hline = None

    def initialize(self):
        self.fig, self.ax = plt.subplots()
        self.ax.plot([3,1,2,4,0,5,3,2,0,2,4])
        self.vline = self.ax.axvline(1)
        self.hline = self.ax.axhline(0.5)

    def set_cursor(self, x, y):
        if not self.initialized:
            self.initialize()

        self.vline.set_xdata((x, x))
        self.hline.set_ydata((y, y))

        if self.initialized:
            display(self.fig)
        self.initialized = True

ic = InteractiveCursor()
def set_cursor(x, y):
    ic.set_cursor(x, y)

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

My opinion is that this should be considered a bug. I tried it with the object oriented interface and it has the same problem.

like image 26
shad Avatar answered Nov 05 '22 02:11

shad