4

I am working on a Tkinter-GUI to interactively generate Matplotlib-plots, depending on user-input. For this end, it needs to re-plot after the user changes the input.

I have gotten it to work in principle, but would like to include the NavigationToolbar. However, I cannot seem to get the updating of the NavigationToolbar to work correctly.

Here is a basic working version of the code (without the user input Entries):

# Import modules
from Tkinter import *
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg

# global variable: do we already have a plot displayed?
show_plot = False

# plotting function
def plot(x, y):
    fig = plt.figure()
    ax1 = fig.add_subplot(1,1,1)
    ax1.plot(x,y)
    return fig

def main():
    x = np.arange(0.0,3.0,0.01)
    y = np.sin(2*np.pi*x)
    fig = plot(x, y)

    canvas = FigureCanvasTkAgg(fig, master=root)
    toolbar = NavigationToolbar2TkAgg(canvas, toolbar_frame)

    global show_plot
    if show_plot:
        #remove existing plot and toolbar widgets
        canvas.get_tk_widget().grid_forget()
        toolbar_frame.grid_forget()

    toolbar_frame.grid(row=1,column=1)
    canvas.get_tk_widget().grid(row=0,column=1)
    show_plot=True

# GUI
root = Tk()

draw_button = Button(root, text="Plot!", command = main)
draw_button.grid(row=0, column=0)

fig = plt.figure()
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().grid(row=0,column=1)
toolbar_frame = Frame(root)
toolbar_frame.grid(row=1,column=1)

root.mainloop()

Pressing "Plot!" once generates the plot and the NavigationToolbar. Pressing it a second time replots, but generates a second NavigationToolbar (and another every time "Plot!" is pressed). Which sounds like grid_forget() is not working. However, when I change

    if show_plot:
        #remove existing plot and toolbar widgets
        canvas.get_tk_widget().grid_forget()
        toolbar_frame.grid_forget()

    toolbar_frame.grid(row=1,column=1)
    canvas.get_tk_widget().grid(row=0,column=1)
    show_plot=True

to

    if show_plot:
        #remove existing plot and toolbar widgets
        canvas.get_tk_widget().grid_forget()
        toolbar_frame.grid_forget()
    else:
        toolbar_frame.grid(row=1,column=1)
    canvas.get_tk_widget().grid(row=0,column=1)
    show_plot=True

then the NavigationToolbar does vanish when "Plot!" is pressed a second time (but then there is, as expected, no new NavigationToolbar to replace the old). So grid_forget() is working, just not as expected.

What am I doing wrong? Is there a better way to update the NavigationToolbar?

Any help greatly appreciated! Lastalda

Edit:

I found that this will work if you destroy the NavigationToolbar instead of forgetting it. But you have to completely re-create the widget again afterwards, of course:

canvas = FigureCanvasTkAgg(fig, master=root)
toolbar_frame = Frame(root)

global show_plot
if show_plot: # if plot already present, remove plot and destroy NavigationToolbar

    canvas.get_tk_widget().grid_forget()
    toolbar_frame.destroy()

toolbar_frame = Frame(root)
toolbar = NavigationToolbar2TkAgg(canvas, toolbar_frame)
toolbar_frame.grid(row=21,column=4,columnspan=3)
canvas.get_tk_widget().grid(row=1,column=4,columnspan=3,rowspan=20)

show_plot = True

However, the updating approach showed by Hans below is much nicer since you don't have to destroy and recreate anything. I just wanted to highlight that the issue with my approach (apart from the inelegance and performance) was probably that I didn't use destroy().

2 Answers 2

4

A slightly different approach might be to reuse the figure for subsequent plots by clearing & redrawing it. That way, you don't have to destroy & regenerate neither the figure nor the toolbar:

from Tkinter import Tk, Button
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg

# plotting function: clear current, plot & redraw
def plot(x, y):
    plt.clf()
    plt.plot(x,y)
    # just plt.draw() won't do it here, strangely
    plt.gcf().canvas.draw()

# just to see the plot change
plotShift = 0
def main():
    global plotShift

    x = np.arange(0.0,3.0,0.01)
    y = np.sin(2*np.pi*x + plotShift)
    plot(x, y)

    plotShift += 1

# GUI
root = Tk()

draw_button = Button(root, text="Plot!", command = main)
draw_button.grid(row=0, column=0)

# init figure
fig = plt.figure()

canvas = FigureCanvasTkAgg(fig, master=root)
toolbar = NavigationToolbar2TkAgg(canvas, root)
canvas.get_tk_widget().grid(row=0,column=1)
toolbar.grid(row=1,column=1)

root.mainloop()
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! I had seen references to plt.draw() at various sources, but it didn't work, which is why I went to the approach of destroying and recreating. But this is much better, of course. Thanks!
0

When you press the "Plot!" button it calls main. main creates the navigation toolbar. So, each time you press the button you get a toolbar. I don't know much about matplot, but it's pretty obvious why you get multiple toolbars.

By the way, grid_forget doesn't destroy the widget, it simply removes it from view. Even if you don't see it, it's still in memory.

Typically in a GUI, you create all the widgets exactly once rather than recreating the same widgets over and over.

1 Comment

I thought as much. However, the NavigationToolbar seems to only work if it is "refreshed" after the re-plot (so it stays connected to the new plot). And I haven't found any update-like function that works yet. (If someone can point me there, please do!) So I assumed that destroying and recreating them should work, which I was trying to do with grid_forget() - is there a better method for this? Also, I do the same with the canvas widget, but there I don't get the same doubling behavior. I do not understand why.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.