Plot to Output in Tab rendering in cell

I would like to create a Jupyter Lab UI with Output Tab(s) that plot interactively to an Output Tab based on user input callback.

Printing to an Output Tab works as expected, plotting does not seem to place the plot in the Output Tab.

This is an example where plot is called in the Output context from a following cell rather than a callback. It is expected the plot would render in the Output, but is rendered in the cell. Printing to Output does not seem to have this issue.

I tried moving the %matplotlib widget magic into the dashboard cell but it does not seem to make a difference.

%matplotlib widget
from datetime import datetime

from   IPython.display import display
import ipywidgets      as     widgets

from pandas import DataFrame
from math import sin, pi

# DataFrame to plot
time = [t for t in range(200)]
df = DataFrame( {'time':time, 'data':[sin(4*pi*t/200) for t in time]} )

# 3 Output widgets in a Tab
dfInput      = widgets.Output()
dfOutput     = widgets.Output()
Plot2DOutput = widgets.Output()

outputTab = widgets.Tab( [ dfInput, dfOutput, Plot2DOutput ] )
outputTab.set_title( 0, 'Input'   )
outputTab.set_title( 1, 'Output'  )
outputTab.set_title( 2, '2D Plot' )

# Controls for UI in HBox
cbox = widgets.Combobox( placeholder='method',
                         options=['Data','Simplex','SMap','CCM'],
                         description='method:', ensure_option=True,
                         disabled=False )

controls = widgets.HBox( [ cbox, widgets.IntSlider() ] )

# dashboard UI in VBox : contains controls & outputTab
dashboard = widgets.VBox( [controls, outputTab] )
display( dashboard )

# Print in dfInput Tab
with dfInput:
    print( f'{datetime.now()}' )
    print( df )

# Plot in Plot2DOutput does not render in Tab
with Plot2DOutput:
    df.plot('time', 'data')

Environment:

Selected Jupyter core packages...
IPython          : 8.31.0
ipykernel        : 6.29.5
ipywidgets       : 8.1.5
jupyter_client   : 8.6.3
jupyter_core     : 5.7.2
jupyter_server   : 2.15.0
jupyterlab       : 4.3.5
nbclient         : 0.10.2
nbconvert        : 7.16.6
nbformat         : 5.10.4
notebook         : not installed
qtconsole        : 5.6.1
traitlets        : 5.14.3

python --version
Python 3.13.1

You mention ‘following cell’, but I see only one code block with no comments in the code or text indicating what would be in different cells? Can you detail where the code below there is broken into cells? Preferably make separate code blocks with clear delineation as to what goes in what cell, please.


This post on StackOverflow shows something similar to what I think you are experiencing. My comments below the highest upvoted answer to that go into more details about controlling things with examples.

Still not knowing about where you different cells are meant to break, this suggested variation for running in a single cell, at least doesn’t show an additional plot in the cell output, outside of the tab:

from datetime import datetime

from IPython.display import display
import ipywidgets as widgets

from pandas import DataFrame
from math import sin, pi
import matplotlib.pyplot as plt

# DataFrame to plot
time = [t for t in range(200)]
df = DataFrame( {'time':time, 'data':[sin(4*pi*t/200) for t in time]} )

# Plot the dataframe
my_plot = df.plot('time', 'data')

# 3 Output widgets in a Tab
dfInput      = widgets.Output()
dfOutput     = widgets.Output()
Plot2DOutput = widgets.Output()

outputTab = widgets.Tab( [ dfInput, dfOutput, Plot2DOutput ] )
outputTab.set_title( 0, 'Input'   )
outputTab.set_title( 1, 'Output'  )
outputTab.set_title( 2, '2D Plot' )

# Controls for UI in HBox
cbox = widgets.Combobox( placeholder='method',
                         options=['Data','Simplex','SMap','CCM'],
                         description='method:', ensure_option=True,
                         disabled=False )

controls = widgets.HBox( [ cbox, widgets.IntSlider() ] )

# dashboard UI in VBox : contains controls & outputTab
dashboard = widgets.VBox( [controls, outputTab] )
display(dashboard)

# Print in dfInput Tab
with dfInput:
    print( f'{datetime.now()}' )
    print( df )

# Plot on the Plot2DOutput Tab
with Plot2DOutput:
    plt.show(my_plot)

You’ll see it doesn’t change much from what you have but adds assigning the plot to handle and a change to how to call it in context manager. Key changes:

  • apply a handle my_plot to the Pandas plot
  • use plt.show() to show that plot in the context of Plot2DOutput

Alternative with matplotlib directly (not relying on Pandas plotting)

Still not knowing about where you different cells are meant to break, this suggested variation for running in a single cell, using matplotlib directly, at least doesn’t show an additional plot in the cell output, outside of the tab:

from datetime import datetime

from IPython.display import display
import ipywidgets as widgets

from pandas import DataFrame
from math import sin, pi
import matplotlib.pyplot as plt

# DataFrame to plot
time = [t for t in range(200)]
df = DataFrame( {'time':time, 'data':[sin(4*pi*t/200) for t in time]} )

# Plot the dataframe
my_plot = plt.plot(df['time'], df['data'])

# 3 Output widgets in a Tab
dfInput      = widgets.Output()
dfOutput     = widgets.Output()
Plot2DOutput = widgets.Output()

outputTab = widgets.Tab( [ dfInput, dfOutput, Plot2DOutput ] )
outputTab.set_title( 0, 'Input'   )
outputTab.set_title( 1, 'Output'  )
outputTab.set_title( 2, '2D Plot' )

# Controls for UI in HBox
cbox = widgets.Combobox( placeholder='method',
                         options=['Data','Simplex','SMap','CCM'],
                         description='method:', ensure_option=True,
                         disabled=False )

controls = widgets.HBox( [ cbox, widgets.IntSlider() ] )

# dashboard UI in VBox : contains controls & outputTab
dashboard = widgets.VBox( [controls, outputTab] )
display(dashboard)

# Print in dfInput Tab
with dfInput:
    print( f'{datetime.now()}' )
    print( df )

# Plot on the Plot2DOutput Tab
with Plot2DOutput:
    plt.show(my_plot)

You’ll see it doesn’t change much from what you have but takes relying on Pandas handling of the plot out of there with a few extra lines and a change to one. Key changes:

  • use maplotlib to make a plot and apply a handle my_plot to that
  • use plt.show() to show that plot in the context of Plot2DOutput

You may see other ways from what I reference in order to tweak your code to get what you seek.



Aside:
I don’t think you need it at all here, but since you mention %matplotlib widget, I’ll point out that it would be best these days to use %matplotlib ipympl going forward. In actuality these days. %matplotlib widget means %matplotlib ipympl behind-the-scenes, see here. And to just make it better for everyone, it is best to be explicit in the syntax.

If I had to guess on where you want the ‘following cell’ break, it would be like this with display(dashboard) being around the end of the first cell perhaps.
So assuming that and building on my answer above we have this option…

Cell #1

Try this in the first cell:

from datetime import datetime

from IPython.display import display
import ipywidgets as widgets

from pandas import DataFrame
from math import sin, pi
import matplotlib.pyplot as plt

# DataFrame to plot
time = [t for t in range(200)]
df = DataFrame( {'time':time, 'data':[sin(4*pi*t/200) for t in time]} )

# Plot the dataframe
df.plot('time', 'data')

# make a matplotlib figure from this
my_plot_as_matplotlib_figure_object = plt.gcf()

# 3 Output widgets in a Tab
dfInput      = widgets.Output()
dfOutput     = widgets.Output()
Plot2DOutput = widgets.Output()

outputTab = widgets.Tab( [ dfInput, dfOutput, Plot2DOutput ] )
outputTab.set_title( 0, 'Input'   )
outputTab.set_title( 1, 'Output'  )
outputTab.set_title( 2, '2D Plot' )

# Controls for UI in HBox
cbox = widgets.Combobox( placeholder='method',
                         options=['Data','Simplex','SMap','CCM'],
                         description='method:', ensure_option=True,
                         disabled=False )

controls = widgets.HBox( [ cbox, widgets.IntSlider() ] )

# dashboard UI in VBox : contains controls & outputTab
dashboard = widgets.VBox( [controls, outputTab] )
display(dashboard)
plt.close() # suppress any open active plot object from showing up in cell output for this cell by closing any

Running that as the first cell makes the tabs with no content yet.
By running the next cell (cell #2), we can add the content desired after-the-fact i.e., after the tabs widgets already are generated and displayed.

Cell #2

With everything set up in the first cell, we can use the context managers now to send the print() and display of the matplotlib figure made from the Pandas plot object to the specified tab:

# Print in dfInput Tab
with dfInput:
    print( f'{datetime.now()}' )
    print( df )

# Display the plot figure on the Plot2DOutput Tab
with Plot2DOutput:
    display(my_plot_as_matplotlib_figure_object)

Note that to get this to work then with everything not in the same cell, I had to add a few more controls beyond what I did above.

  • you’ll see I add plt.close() to the end of the first cell to close all active plots so Jupyter doesn’t detect an active plot and display it in the output area of the cell. (This is like the extra one you may have been seeing).
  • use plt.gcf() to make a assigned figure for the Pandas plot (Note that I didn’t need to assign a handle for the plot/axes object here, i.e., no my_plot, because we need a matplotlib figure object for display().)
  • display of the matplotlib figure object in the context of Plot2DOutput (Matplotlib figures work with display(). I’ve found these can be reused in other cells and so are handier, see here for more details.)


Alternative implementation with Matpotlib directly (not relying on Pandas plotting)

If I had to guess on where you want the ‘following cell’ break, it would be like this with display(dashboard) being around the end of the first cell perhaps.
So assuming that and building on my answer above we have this option…

Cell #1

Try this in the first cell:

from datetime import datetime

from IPython.display import display
import ipywidgets as widgets

from pandas import DataFrame
from math import sin, pi
import matplotlib.pyplot as plt

# DataFrame to plot
time = [t for t in range(200)]
df = DataFrame( {'time':time, 'data':[sin(4*pi*t/200) for t in time]} )

# Plot the dataframe
plt.plot(df['time'], df['data'])

# make a matplotlib figure from this
my_plot_as_matplotlib_figure_object = plt.gcf()

# 3 Output widgets in a Tab
dfInput      = widgets.Output()
dfOutput     = widgets.Output()
Plot2DOutput = widgets.Output()

outputTab = widgets.Tab( [ dfInput, dfOutput, Plot2DOutput ] )
outputTab.set_title( 0, 'Input'   )
outputTab.set_title( 1, 'Output'  )
outputTab.set_title( 2, '2D Plot' )

# Controls for UI in HBox
cbox = widgets.Combobox( placeholder='method',
                         options=['Data','Simplex','SMap','CCM'],
                         description='method:', ensure_option=True,
                         disabled=False )

controls = widgets.HBox( [ cbox, widgets.IntSlider() ] )

# dashboard UI in VBox : contains controls & outputTab
dashboard = widgets.VBox( [controls, outputTab] )
display(dashboard)
plt.close() # suppress any open active plot object from showing up in cell output for this cell by closing any

Running that as the first cell makes the tabs with no content yet.
By running the next cell (cell #2), we can add the content desired after-the-fact i.e., after the tabs widgets already are generated and displayed.

Cell #2

With everything set up in the first cell, we can use the context managers now to send the print() and display of the matplotlib figure object to the specified tab:

# Print in dfInput Tab
with dfInput:
    print( f'{datetime.now()}' )
    print( df )

# Display the plot figure on the Plot2DOutput Tab
with Plot2DOutput:
    display(my_plot_as_matplotlib_figure_object)

Note that to get this to work then with everything not in the same cell, I had to add a few more controls beyond what I did above.

  • you’ll see I add plt.close() to the end of the first cell to close all active plots so Jupyter doesn’t detect an active plot and display it in the output area of the cell. (This is like the extra one you may have been seeing).
  • use plt.gcf() to make a assigned figure for the plot (Note that I didn’t need to assign a handle for the plot/axes object here, i.e., no my_plot, because we need a matplotlib figure object for display().)
  • display of the matplotlib figure object in the context of Plot2DOutput (Matplotlib figures work with display(). I’ve found these can be reused in other cells and so are handier, see here for more details.)

Thank you!

Apologies for not providing clear cell delineation. I wondered how best to do that without being able to paste in a notebook.

As you suggested, calling matplotlib to show the DataFrame plot in the Output context followed by display() is working:

with Plot2DOutput:
    Plot2DOutput.clear_output()
    df.plot('time', 'data')
    plt.show()
display( Plot2DOutput )

It seems a tad inelegant to rely explicitly on pyplot.plt, but it does make sense to plot in the Output context and display() the context.

Thanks again for the clarity and references.

Hmm…

Note sure I understood that is what you were seeking from the initial post, but I like to think discussing the nuances of how you can use plt.show() and display() got you where you needed to go anyway.


You can always link to a notebook posted in a GitHub gist. nbviewer is fine displaying them even. Example rendering a notebook hosted as a gist via nbviewer here. nbviewer offers more features and so it is always best to share the link to that rendering. In particular for the example provided if you scroll down on nbviewer to about the middle of that notebook, you’ll see it renders the dataframe style where the rendering github provides doesn’t support that feature. Think of the Github rendering as a very basic pre-view not for sharing.

In your particular case though it wouldn’t fully work as nbviewer won’t show widgets as they need an active kernel. Fortunately, most people here are aware of that limitation. Often to share solutions, I’ll link to a mybinder-served session that will open with the notebook active, for example in this comment here where it starts, “see part #1 example at the top…”. A URL follows right after that craftily signals to launch an active session with the example. You don’t need to go that far because the notebook would be enough; however, I thought I’d point out it is an option.

Thanks for the notebook sharing options.

Also for the %matplotlib ipympl guidance.

Just to be pedantic, the same technique works with an Output capture decorator:

@Plot2DOutput.capture()
def PlotTo2DOutput():
    plt.close()
    Plot2DOutput.clear_output()
    df.plot('time', 'data')
    plt.show()

PlotTo2DOutput()

I add plt.close() preemptively as matplotlib seems fine if no plot has been created on the first call, then on subsequent calls the matplotlib Figure X counter remains at Figure 1, otherwise it seems matplotlib keeps previous plots cached and the Figure counter increments. Do you see any issues with this approach?

Interesting. I think I just usually override the default title as a way around it.