Clear MatPlotLib figure in Jupyter Python notebook

15,186

What you call overhead is the source of the problem. Or in other words: If in each call to the function, a new figure is created, is it surprising that you will end up with a lot of figures?

The idea is of course to draw a single figure. In order to be able to later update the figure the %matplotlib notebook backend is needed.

The function that gets called when changing the slider will then only need to update the viewing angle and redraw the canvas.

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import ipywidgets as widgets
from IPython.display import display
%matplotlib notebook

# generate test data
x = np.random.rand(100)
y = np.random.rand(100)
z = np.random.rand(100)


fig = plt.figure(figsize=(6,6))
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
ax.set_zlabel('Z axis')
ax.scatter(x, y, z)
ax.view_init(20, 40)
# show plot
plt.show()

def update_plot(angle1 = 20, angle2 = 40):
    # set view angle
    ax.view_init(angle1, angle2)
    fig.canvas.draw_idle()

# prepare widgets
angle1_slider = widgets.IntSlider(20, min = 0, max = 60)
angle1_label = widgets.Label(value = 'Angle 1 value is: ' + str(angle1_slider.value))
display(angle1_slider, angle1_label)

# handle angle 1 update
def update_angle1(value):
    update_plot(angle1 = value['new'])
    angle1_label.value = 'Angle 1 value is: ' + str(value.new)

angle1_slider.observe(update_angle1, names = 'value')

This is how it would look like:

enter image description here

Share:
15,186
Pieter
Author by

Pieter

Neurosurgeon, PhD on intraoperative MRI, research on image guided surgery and brain computer interfaces, developer of mobile apps for clinical decision support

Updated on June 14, 2022

Comments

  • Pieter
    Pieter almost 2 years

    I want a 3D scatter plot in MatPlotLib to be rotated interactively in a Jupyter Python notebook. For that reason I integrated a slider from ipywidgets to update the view angle. The test code below shows what I am trying to achieve. The problem is that a new figure is added below the previous one, instead of the current figure cleared. I tried plt.close(fig), plt.cla() and plt.clf() without success. (further I realize that there is overhead in recreating the figure and axes but that is the lesser part of my current concerns...)

    Here is the (test) code:

    # init
    import numpy as np
    import matplotlib.pyplot as plt
    from mpl_toolkits.mplot3d import Axes3D
    import ipywidgets as widgets
    from IPython.display import display
    
    # generate test data
    x = np.random.rand(100)
    y = np.random.rand(100)
    z = np.random.rand(100)
    
    # prepare plot
    def draw_plot(angle1 = 20, angle2 = 40):
        # create figure
        fig = plt.figure(figsize=(15,10))
        ax = fig.add_subplot(111, projection='3d')
        ax.set_xlabel('X axis')
        ax.set_ylabel('Y axis')
        ax.set_zlabel('Z axis')
        ax.scatter(x, y, z)
    
        # set view angle
        ax.view_init(angle1, angle2)
    
        # show plot
        plt.show()
    
    # prepare widgets
    angle1_slider = widgets.IntSlider(20, min = 0, max = 60)
    angle1_label = widgets.Label(value = 'Angle 1 value is: ' + str(angle1_slider.value))
    display(angle1_slider, angle1_label)
    
    # handle angle 1 update
    def update_angle1(value):
        draw_plot(angle1 = value['new'])
        angle1_label.value = 'Angle 1 value is: ' + str(value.new)
    
    angle1_slider.observe(update_angle1, names = 'value')
    
    # draw initial plot
    draw_plot()
    

    Any suggestions would be appreciated!

  • Pieter
    Pieter about 7 years
    Excellent! I did not know fig.canvas.draw_idle() which solved the issue indeed. And even better, I always used %matplotlib inline but %matplotlib notebook is very useful too! Thanks!