-->

NagivationToolbar fails when updating in Tkinter c

2019-04-12 23:06发布

问题:

I am trying to update a two-panel plot in Tkinter canvas using matplotlib. Here is the minimum code that shows my current understanding. The main problem is while the navigation toolbar works for the initial plots (y = sin(x), y = cos(x)), however it fails when I press the update button to update it. For example if I zoom in a curve, I cannot use home button to return to its original state. I have been trying different ways, but to no avail. I would appreciate anyone's suggestions. One minor issue I notice is that if I want to kill the plot, I should go to menubar and select python/quit Python, otherwise if I just click the X at the top left of the plot window, the terminal freezes (I have to kill the terminal).
I am using Python 2.7.14 and matplotlob 2.1.0.

from Tkinter import *
import Tkinter as tk
import ttk
from math import exp
import os  # for loading files or exporting files
import tkFileDialog
##loading matplotlib modules
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.gridspec as gridspec
import numpy as np

top = tk.Tk()
top.title("Intermolecular PDFs")

top_frame = ttk.Frame(top, padding = (10, 10))
top_frame.pack()
fig = plt.figure(figsize=(10, 6), dpi=100) ##create a figure; modify the size here

x = np.linspace(0,1)
y = np.sin(x)
z = np.cos(x)

fig.add_subplot(211)

plt.title("Individual PDFs")
plt.xlabel(ur"r (\u00c5)", labelpad = 3, fontsize = 15)
plt.ylabel(ur"PDF, G (\u00c5$^{-2})$", labelpad = 10, fontsize = 15)
plt.plot(x,y, "r-", lw=2)
plt.xticks(fontsize = 11)
plt.yticks(fontsize = 11)

fig.add_subplot(212)


plt.title("Difference PDFs")
plt.xlabel(ur"r (\u00c5)", labelpad = 3, fontsize = 15)
plt.ylabel(ur"PDF, G (\u00c5$^{-2})$", labelpad = 10, fontsize = 15)
plt.plot(x,z,"g-", lw=2)
plt.xticks(fontsize = 11)
plt.yticks(fontsize = 11)

fig.tight_layout()

canvas = FigureCanvasTkAgg(fig, master = top_frame)
canvas.show()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
#self.canvas.draw()

toolbar = NavigationToolbar2TkAgg(canvas, top_frame)
#self.toolbar.pack()
toolbar.update()
canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)


def update():
    fig.clf()

    new_x = np.linspace(1,100)
    new_y = new_x**2
    new_z = new_x**3
    fig.add_subplot(211)

    plt.title("Individual PDFs")
    plt.xlabel(ur"r (\u00c5)", labelpad = 3, fontsize = 15)
    plt.ylabel(ur"PDF, G (\u00c5$^{-2})$", labelpad = 10, fontsize = 15)
    plt.plot(new_x,new_y, "r-", lw=2)
    plt.xticks(fontsize = 11)
    plt.yticks(fontsize = 11)

    fig.add_subplot(212)


    plt.title("Difference PDFs")
    plt.xlabel(ur"r (\u00c5)", labelpad = 3, fontsize = 15)
    plt.ylabel(ur"PDF, G (\u00c5$^{-2})$", labelpad = 10, fontsize = 15)
    plt.plot(new_x,new_z,"g-", lw=2)
    plt.xticks(fontsize = 11)
    plt.yticks(fontsize = 11)

    fig.tight_layout()
    canvas.show()

ttk.Button(top_frame, text = "update",command = update).pack()


top.mainloop()

回答1:

The main problem is that the home button does not know which state it should refer to when being pressed. The original state it would refer to does not even exist any more, because the figure had been cleared in the meantime. The solution to this is to call

toolbar.update()

which will, amongst other things, create a new home state for the button to revert to when being pressed.

There are some other minor issues with the code:

  • Instead of clearing the figure, you may just update the data of the lines drawn in it. This would eliminate a lot of redundant code.
  • I would strongly advise not to use pyplot at all when embedding a figure in Tk. Instead, use the object oriented approach, creating objects like figure and axes and then call their respective methods. (I'm not sure if this is the reason for the freezing though, because even the initial code did not freeze for me when running it.)
  • There were some unnecessary commands floating in the code.

The following is a clean version with all the above being taken care of:

import Tkinter as tk
import ttk
##loading matplotlib modules
import matplotlib
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure

import numpy as np

top = tk.Tk()
top.title("Intermolecular PDFs")

top_frame = ttk.Frame(top, padding = (10, 10))
top_frame.pack()

matplotlib.rcParams["xtick.labelsize"] = 11
matplotlib.rcParams["ytick.labelsize"] = 11

fig = Figure(figsize=(10, 6), dpi=100) ##create a figure; modify the size here

x = np.linspace(0,1)
y = np.sin(x)
z = np.cos(x)

ax = fig.add_subplot(211)

ax.set_title("Individual PDFs")
ax.set_xlabel(ur"r (\u00c5)", labelpad = 3, fontsize = 15)
ax.set_ylabel(ur"PDF, G (\u00c5$^{-2})$", labelpad = 10, fontsize = 15)
line, = ax.plot(x,y, "r-", lw=2)

ax2 = fig.add_subplot(212)

ax2.set_title("Difference PDFs")
ax2.set_xlabel(ur"r (\u00c5)", labelpad = 3, fontsize = 15)
ax2.set_ylabel(ur"PDF, G (\u00c5$^{-2})$", labelpad = 10, fontsize = 15)
line2, = ax2.plot(x,z,"g-", lw=2)

canvas = FigureCanvasTkAgg(fig, master = top_frame)
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)

fig.tight_layout()

toolbar = NavigationToolbar2TkAgg(canvas, top_frame)
toolbar.update()
canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)


def update():
    new_x = np.linspace(1,100)
    new_y = new_x**2
    new_z = new_x**3

    line.set_data(new_x,new_y)
    line2.set_data(new_x,new_z)

    ax.relim()
    ax.autoscale()
    ax2.relim()
    ax2.autoscale()
    fig.tight_layout()
    canvas.draw_idle()
    toolbar.update()

ttk.Button(top_frame, text = "update",command = update).pack()


top.mainloop()

Note: In newer versions of matplotlib you should use NavigationToolbar2Tk instead of NavigationToolbar2TkAgg.