Remove option from NavigationToolbar2TkAgg? - matplotlib

I'm learning how to use matplotlib, and now I have a problem. When I create a Figure in "tkinter project" and give it a subplot, I use NavigationToolbar2TkAgg to create a toolbar. In the current toolbar that appears , i want to remove the configure subplot option but couldn't find a way to do so.
Is there any way to do it?

The solution to this is in principle already given in this question: How to modify the navigation toolbar easily in a matplotlib figure window?
But it may not be obvious how to use it. So we may adapt the code from here with a CustomToolbar. The Toolbars toolitems attribute can be changed as to remove the unwanted "Subplots" button.
import numpy as np
import Tkinter as tk
import matplotlib as mpl
from matplotlib.patches import Rectangle
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
# custom toolbar with lorem ipsum text
class CustomToolbar(NavigationToolbar2TkAgg):
toolitems = filter(lambda x: x[0] != "Subplots", NavigationToolbar2TkAgg.toolitems)
class MyApp(object):
def __init__(self,root):
self.root = root
self._init_app()
# here we embed the a figure in the Tk GUI
def _init_app(self):
self.figure = mpl.figure.Figure()
self.ax = self.figure.add_subplot(111)
self.canvas = FigureCanvasTkAgg(self.figure,self.root)
self.toolbar = CustomToolbar(self.canvas,self.root)
self.toolbar.update()
self.plot_widget = self.canvas.get_tk_widget()
self.plot_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.toolbar.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.canvas.show()
# plot something random
def plot(self):
self.ax.plot([1,3,2])
self.figure.canvas.draw()
def main():
root = tk.Tk()
app = MyApp(root)
app.plot()
root.mainloop()
if __name__ == "__main__":
main()
Note: In newer versions of matplotlib you should use NavigationToolbar2Tk instead of NavigationToolbar2TkAgg

Related

matplotlib and transparency figure

I am working with the matplotlib library and PyQt5 with Python 3.6. I add a figure in a window I create, and I wish to set transparent the background of this figure because I add an image to the background of the window. But, the figure is not really transparent, it duplicates the background image of the window.
For example, someone deals with the same problem two years ago :
matplotlib and pyqt4 transparent background
Here is a working example (with a background which is black but the figure is not black) :
import sys, os
from PyQt5.QtCore import Qt
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import matplotlib
matplotlib.use('Qt5Agg') # Make sure that we are using QT5
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
class SecondWindow(QWidget):
def __init__(self, parent=None):
super(SecondWindow, self).__init__(parent)
self.setupUi(self)
def setupUi(self, Form):
# WINDOW SETTINGS
Form.setWindowTitle('Hello')
self.p = QPalette()
self.pixmap = QPixmap(os.getcwd() + "/logo.png").scaled(self.size(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
self.p.setBrush(QPalette.Background, QBrush(self.pixmap))
self.setPalette(self.p)
# CREATE FIGURE AND SETTINGS
self.figure = plt.figure()
self.figure.patch.set_facecolor('None')
self.figure.patch.set_alpha(0)
self.canvas = FigureCanvas(self.figure)
self.axes = self.figure.add_subplot(111)
# WINDOW LAYOUT (with H1 and H2)
self.setLayout(QVBoxLayout())
self.layout().addWidget(self.canvas,1)
self.layout().setContentsMargins(50, 50, 50, 50)
if __name__ == '__main__':
app = QApplication(sys.argv)
form = SecondWindow()
form.show()
sys.exit(app.exec_())
I search for answer during long hours but didn't find a solution yet. Thanks for any help you can bring !
Operating System: Windows 7 Pro
Matplotlib Version: 2.0.2 (installed via Anaconda, conda install matplotlib --channel conda-forge)
Python Version: Python 3.6
Anaconda 3
The problem occurs because the background image is set as a palette to the widget. This causes the canvas to inherit the palette and hence the canvas will also have the image as background, somehow overlaying the widget's background.
A solution would be to set the background of the canvas transparent. An easy way to do so are style sheets.
self.canvas.setStyleSheet("background-color:transparent;")
Note that this is not the same as setting the patches' facecolor to none. The figure has a background, which is controlled inside matplotlib, but the canvas, being a PyQt object also has a background.
Complete example:
import sys, os
from PyQt4.QtCore import Qt
from PyQt4.QtGui import *
import matplotlib
matplotlib.use('Qt4Agg')
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
plt.rcParams['xtick.color'] ="w"
plt.rcParams['ytick.color'] ="w"
plt.rcParams['font.size'] = 14
class SecondWindow(QWidget):
def __init__(self, parent=None):
super(SecondWindow, self).__init__(parent)
# CREATE FIGURE AND SETTINGS
self.figure = plt.figure()
self.figure.patch.set_facecolor("None")
self.canvas = FigureCanvas(self.figure)
self.axes = self.figure.add_subplot(111)
self.axes.patch.set_alpha(0.5)
###### Make the background of the canvas transparent
self.canvas.setStyleSheet("background-color:transparent;")
self.p = QPalette()
self.p.setBrush(QPalette.Background, QBrush(QPixmap("house.png")))
self.setPalette(self.p)
self.setLayout(QVBoxLayout())
self.layout().addWidget(self.canvas,1)
self.layout().setContentsMargins(50, 50, 50, 50)
if __name__ == '__main__':
app = QApplication(sys.argv)
form = SecondWindow()
form.show()
sys.exit(app.exec_())
which might then look like

Embedding "Figure Type" Seaborn Plot in PyQt (pyqtgraph)

I am using a wrapper of PyQt (pyqtgraph) to build a GUI application.
I wish to embed a Seaborn plot within it using the MatplotlibWidget. However, my problem is that the Seaborn wrapper method such as FacetGrid do not accept an external figure handle. Moreover, when I try to update the MatplotlibWidget object underlying figure (.fig) with the figure produced by the FacetGrid it doesn't work (no plot after draw). Any suggestion for a workaround?
Seaborn's Facetgrid provides a convenience function to quickly connect pandas dataframes to the matplotlib pyplot interface.
However in GUI applications you rarely want to use pyplot, but rather the matplotlib API.
The problem you are facing here is that Facetgrid already creates its own matplotlib.figure.Figure object (Facetgrid.fig). Also, the MatplotlibWidget
creates its own figure, so you end up with two figures.
Now, let's step back a bit:
In principle it is possible to use a seaborn Facetgrid plot in PyQt, by first creating the plot and then providing the resulting figure to the figure canvas (matplotlib.backends.backend_qt4agg.FigureCanvasQTAgg). The following is an example of how to do that.
from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import sys
import seaborn as sns
import matplotlib.pyplot as plt
tips = sns.load_dataset("tips")
def seabornplot():
g = sns.FacetGrid(tips, col="sex", hue="time", palette="Set1",
hue_order=["Dinner", "Lunch"])
g.map(plt.scatter, "total_bill", "tip", edgecolor="w")
return g.fig
class MainWindow(QtGui.QMainWindow):
send_fig = QtCore.pyqtSignal(str)
def __init__(self):
super(MainWindow, self).__init__()
self.main_widget = QtGui.QWidget(self)
self.fig = seabornplot()
self.canvas = FigureCanvas(self.fig)
self.canvas.setSizePolicy(QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
self.canvas.updateGeometry()
self.button = QtGui.QPushButton("Button")
self.label = QtGui.QLabel("A plot:")
self.layout = QtGui.QGridLayout(self.main_widget)
self.layout.addWidget(self.button)
self.layout.addWidget(self.label)
self.layout.addWidget(self.canvas)
self.setCentralWidget(self.main_widget)
self.show()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
win = MainWindow()
sys.exit(app.exec_())
While this works fine, it is a bit questionable, if it's useful at all. Creating a plot inside a GUI in most cases has the purpose of beeing updated depending on user interactions. In the example case from above, this is pretty inefficient, as it would require to create a new figure instance, create a new canvas with this figure and replace the old canvas instance with the new one, adding it to the layout.
Note that this problematics is specific to those plotting functions in seaborn, which work on a figure level, like lmplot, factorplot, jointplot, FacetGrid and possibly others.
Other functions like regplot, boxplot, kdeplot work on an axes level and accept a matplotlib axes object as argument (sns.regplot(x, y, ax=ax1)).
A possibile solution is to first create the subplot axes and later plot to those axes, for example using the pandas plotting functionality.
df.plot(kind="scatter", x=..., y=..., ax=...)
where ax should be set to the previously created axes.
This allows to update the plot within the GUI. See the example below. Of course normal matplotlib plotting (ax.plot(x,y)) or the use of the seaborn axes level function discussed above work equally well.
from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import sys
import seaborn as sns
tips = sns.load_dataset("tips")
class MainWindow(QtGui.QMainWindow):
send_fig = QtCore.pyqtSignal(str)
def __init__(self):
super(MainWindow, self).__init__()
self.main_widget = QtGui.QWidget(self)
self.fig = Figure()
self.ax1 = self.fig.add_subplot(121)
self.ax2 = self.fig.add_subplot(122, sharex=self.ax1, sharey=self.ax1)
self.axes=[self.ax1, self.ax2]
self.canvas = FigureCanvas(self.fig)
self.canvas.setSizePolicy(QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
self.canvas.updateGeometry()
self.dropdown1 = QtGui.QComboBox()
self.dropdown1.addItems(["sex", "time", "smoker"])
self.dropdown2 = QtGui.QComboBox()
self.dropdown2.addItems(["sex", "time", "smoker", "day"])
self.dropdown2.setCurrentIndex(2)
self.dropdown1.currentIndexChanged.connect(self.update)
self.dropdown2.currentIndexChanged.connect(self.update)
self.label = QtGui.QLabel("A plot:")
self.layout = QtGui.QGridLayout(self.main_widget)
self.layout.addWidget(QtGui.QLabel("Select category for subplots"))
self.layout.addWidget(self.dropdown1)
self.layout.addWidget(QtGui.QLabel("Select category for markers"))
self.layout.addWidget(self.dropdown2)
self.layout.addWidget(self.canvas)
self.setCentralWidget(self.main_widget)
self.show()
self.update()
def update(self):
colors=["b", "r", "g", "y", "k", "c"]
self.ax1.clear()
self.ax2.clear()
cat1 = self.dropdown1.currentText()
cat2 = self.dropdown2.currentText()
print cat1, cat2
for i, value in enumerate(tips[cat1].unique().get_values()):
print "value ", value
df = tips.loc[tips[cat1] == value]
self.axes[i].set_title(cat1 + ": " + value)
for j, value2 in enumerate(df[cat2].unique().get_values()):
print "value2 ", value2
df.loc[ tips[cat2] == value2 ].plot(kind="scatter", x="total_bill", y="tip",
ax=self.axes[i], c=colors[j], label=value2)
self.axes[i].legend()
self.fig.canvas.draw_idle()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
win = MainWindow()
sys.exit(app.exec_())
A final word about pyqtgraph: I wouldn't call pyqtgraph a wrapper for PyQt but more an extention. Although pyqtgraph ships with its own Qt (which makes it portable and work out of the box), it is also a package one can use from within PyQt. You can therefore add a GraphicsLayoutWidget to a PyQt layout simply by
self.pgcanvas = pg.GraphicsLayoutWidget()
self.layout().addWidget(self.pgcanvas)
The same holds for a MatplotlibWidget (mw = pg.MatplotlibWidget()). While you can use this kind of widget, it's merely a convenience wrapper, since all it's doing is finding the correct matplotlib imports and creating a Figure and a FigureCanvas instance. Unless you are using other pyqtgraph functionality, importing the complete pyqtgraph package just to save 5 lines of code seems a bit overkill to me.
Here is exact copy of the accepted answer but using PYQT5:
from PyQt5 import QtCore, QtGui, QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import sys
import seaborn as sns
tips = sns.load_dataset("tips")
class MainWindow(QtWidgets.QMainWindow):
send_fig = QtCore.pyqtSignal(str)
def __init__(self):
super(MainWindow, self).__init__()
self.main_widget = QtWidgets.QWidget(self)
self.fig = Figure()
self.ax1 = self.fig.add_subplot(121)
self.ax2 = self.fig.add_subplot(122, sharex=self.ax1, sharey=self.ax1)
self.axes=[self.ax1, self.ax2]
self.canvas = FigureCanvas(self.fig)
self.canvas.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
self.canvas.updateGeometry()
self.dropdown1 = QtWidgets.QComboBox()
self.dropdown1.addItems(["sex", "time", "smoker"])
self.dropdown2 = QtWidgets.QComboBox()
self.dropdown2.addItems(["sex", "time", "smoker", "day"])
self.dropdown2.setCurrentIndex(2)
self.dropdown1.currentIndexChanged.connect(self.update)
self.dropdown2.currentIndexChanged.connect(self.update)
self.label = QtWidgets.QLabel("A plot:")
self.layout = QtWidgets.QGridLayout(self.main_widget)
self.layout.addWidget(QtWidgets.QLabel("Select category for subplots"))
self.layout.addWidget(self.dropdown1)
self.layout.addWidget(QtWidgets.QLabel("Select category for markers"))
self.layout.addWidget(self.dropdown2)
self.layout.addWidget(self.canvas)
self.setCentralWidget(self.main_widget)
self.show()
self.update()
def update(self):
colors=["b", "r", "g", "y", "k", "c"]
self.ax1.clear()
self.ax2.clear()
cat1 = self.dropdown1.currentText()
cat2 = self.dropdown2.currentText()
print (cat1, cat2)
for i, value in enumerate(tips[cat1].unique().get_values()):
print ("value ", value)
df = tips.loc[tips[cat1] == value]
self.axes[i].set_title(cat1 + ": " + value)
for j, value2 in enumerate(df[cat2].unique().get_values()):
print ("value2 ", value2)
df.loc[ tips[cat2] == value2 ].plot(kind="scatter", x="total_bill", y="tip",
ax=self.axes[i], c=colors[j], label=value2)
self.axes[i].legend()
self.fig.canvas.draw_idle()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
ex = MainWindow()
sys.exit(app.exec_())
While any matplotlib plots can be embedded in pyqt5 the same way, it's important to note that the UI could get slow as the sizeof the dataset grows. But I found such approaches handy to parse and plot log files by employing regex functionalities.

using ginput in embedded matplotlib figure in PyQt4

I'm trying to use the 'ginput' to measure distance in a matplotlib figure by allowing the user to mouse click the locations. I am able to do this independently in the matplotlib figure, but I'm having problems when I tried to set the figure onto a matplotlib canvas and then embed it into PyQt4 widget. Below is my code, most of which were taken from the matplotlib examples. My solution will be to click a set of locations, and pass the (x,y) coordinates to the 'dist_calc' function to get the distance.
import sys
from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
import random
import numpy as np
class Window(QtGui.QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.fig = Figure((6.5, 5.0), tight_layout=True)
self.ax = self.fig.add_subplot(111)
self.canvas = FigureCanvas(self.fig)
self.toolbar = NavigationToolbar(self.canvas, self)
self.button = QtGui.QPushButton('Plot')
self.button.clicked.connect(self.plot)
self.ndist = QtGui.QPushButton('Measure')
self.ndist.clicked.connect(self.draw_line)
self.toolbar.addWidget(self.button)
self.toolbar.addWidget(self.ndist)
self.fig.tight_layout()
layout = QtGui.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
self.setLayout(layout)
def plot(self):
data = [random.random() for i in range(20)]
self.ax.hold(False)
self.ax.plot(data, '*-')
self.canvas.draw()
def draw_line(self):
self.xy = plt.ginput(0)
x = [p[0] for p in self.xy]
y = [p[1] for p in self.xy]
self.ax.plot(x,y)
self.ax.figure.canvas.draw()
self.get_dist(x, y)
def get_dist(self, xpts, ypts):
npts = len(xpts)
distArr = []
for i in range(npts-1):
apt = [xpts[i], ypts[i]]
bpt = [xpts[i+1], ypts[i+1]]
dist =self.calc_dist(apt,bpt)
distArr.append(dist)
tdist = np.sum(distArr)
print(tdist)
def calc_dist(self,apt, bpt):
apt = np.asarray(apt)
dist = np.sum((apt - bpt)**2)
dist = np.sqrt(dist)
return dist
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
According to this comment by one of the lead Matplotlib developers, you must not import pyplot when you're embedding Matplotlib in Qt. Pyplot sets up its own gui, mainloop and canvas, which interfere with the Qt event loop.
Changing the line self.xy = plt.ginput(0) into self.xy = self.fig.ginput(0) did not help but gave an insightful error:
AttributeError: 'FigureCanvasQTAgg' object has no attribute 'manager'
Figure.show works only for figures managed by pyplot, normally created by pyplot.figure().
In short, I don't think this is possible. ginput is a blocking function and seems only to be implemented for a Matplotlib event loop. I'm afraid that you will have to build the functionality you want using Matplotlib mouse events, which do work when embedding in PyQt. Just be sure not to use pyplot!
Edit: I just remembered, perhaps the LassoSelector is what you need.

pyplot example with QT Designer

I have seen many examples of integrating matplotlib with python2.6 and QT4 Designer using mplwidget and they work fine. I now need to integrate a pyplot with QT4 Designer but cannot find any examples. All of my attempts to integrate a pyplot graphic have ended in a seg fault. Can someone please direct me to a working example using Designer and pyplot?
Follow up:
Okay, so I tried your solution but I'm still having issues. Unfortunately the machine I use for this code is not hooked up to the internet, so below is a fat finger of the pertinent parts of the code I am using:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import MatplotlibWidget # This is the code snippet you gave in your answer
.
.
.
def drawMap(self) :
fig = plt.figure()
map = Basemap(projection='cyl', llcrnrlat = -90.0, urcrnrlat = 90.0, llcrnrlon = -180.0, urcrnrlon = 180.0, resolution = 'c')
map.drawcoastlines()
map.drawcountries()
plt.show()
def drawMap_Qt(self) :
self.ui.PlotWidget.figure = plt.figure() # This is the Qt widget promoted to MatplotlibWidget
map = Basemap(projection='cyl', llcrnrlat = -90.0, urcrnrlat = 90.0, llcrnrlon = -180.0, urcrnrlon = 180.0, resolution = 'c')
map.drawcoastlines()
map.drawcountries()
self.ui.PlotWidget.show()
The function drawMap() works fine. It creates a separate window and plots the map. The drawMap_Qt() function results in a segmentation fault with no other errors. The end goal is to plot a contour on top of the map. I can do this with the drawMap() function. Of course, I can't even get to the contour part with the drawMap_Qt() function. Any insights as to why it is seg faulting would be greatly appreciated.
If you're referring to the mplwidget from Python(x,y) and WinPython, I think it does use pyplot, but I had trouble putting it in my python install so I just used this class:
from PyQt4 import QtGui
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
class MatplotlibWidget(QtGui.QWidget):
def __init__(self, parent=None, *args, **kwargs):
super(MatplotlibWidget, self).__init__(parent)
self.figure = Figure(*args, **kwargs)
self.canvas = FigureCanvas(self.figure)
self.toolbar = NavigationToolbar(self.canvas, self)
layout = QtGui.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
self.setLayout(layout)
See also How to embed matplotib in pyqt - for Dummies.
Then in Qt Designer you need to create a promoted class, with base class: QWidget, promoted class name: MatplotlibWidget, and header file: the python script containing the MatplotlibWidget class (without the .py). You can add things like ax = self.figure.add_subplot(111), line = ax.plt(...) within the class or by calling methods of the figure attribute of an instance of the class.
Edit:
So I was a bit wrong before, in general with embedded matplotlib widgets you need to use the object oriented methods and not the functions in pyplot. (This sort of explains what the difference is.) Using my snippet above as mymatplotlibwidget.py, try something like this. I don't have basemap installed, so this is somewhat of a guess, but from the examples you need to tell Basemap which axes to use.
import sys
from PyQt4 import QtGui
from mpl_toolkits.basemap import Basemap
from mymatplotlibwidget import MatplotlibWidget
app = QtGui.QApplication(sys.argv)
widget = MatplotlibWidget()
fig = widget.figure
ax = fig.add_subplot(111)
map = Basemap(..., ax=ax)
fig.canvas.draw()
widget.show()
app.exec_()

Matplotlib plot in Tkinter - every update adds new NavigationToolbar?

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().
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()
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.