How to create a figure with show() via the matplotlib API? - matplotlib

I need to create a figure object which has can be show()'ed on the screen (when using some interactive backend) or savefig()'ed, but I need to avoid using the pylab/pyplot API as it sets default backend and messes other things up. I create the figure as
import matplotlib.figure
import matplotlib.backends.backend_qt4agg # or agg for headless backends
figure=matplotlig.figure.Figure()
canvas=matplotlib.backends.backend_qt4agg.FigureCanvasQTAgg(figure)
but I am still missing something. The documentation of Figure.show says
If the figure was not created using figure(), it will lack a FigureManagerBase, and will raise an AttributeError.
How do I do that then?

The 'standard' window you get in interactive sessions is run via the figure_manager family of classes which are tightly to pyplot.
Fortunately, all of the zoom/pan functionality is wrapped up in the NavigationToolbar family of classes.
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
def create_main_frame(self):
# host widget
self.main_frame = QtGui.QWidget()
# set up the mpl side of things
self.fig = Figure((24, 24), tight_layout=True)
self.canvas = FigureCanvas(self.fig)
# attach canvas to host widget
self.canvas.setParent(self.main_frame)
# make axes
self.axes = self.fig.add_subplot(111, adjustable='datalim', aspect='equal')
# make the tool bar
self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame)
# set up layout
vbox = QtGui.QVBoxLayout()
vbox.addWidget(self.mpl_toolbar)
vbox.addWidget(self.canvas)
# set the layout to the host widget
self.main_frame.setLayout(vbox)
# make the host widget the central widget in the main frame of the class
# this code is ripped from
self.setCentralWidget(self.main_frame)

Related

matplotlib animation embedded in tkinter : update function never called

I've been trying to embed a matplotlib animation into tkinter.
The goal of this app is to simulate some differentials equations with rk4 method and show a real time graph as the simulation goes.
In fact the plot is rightly embedded into the tkinter frame.
However, the animation never run, I've noticed that the update function is never called.
I've been searching everywhere but I didn't find anything.
Thanks for the help.
Here is a code sample of the GUI class showing where I execute the animation
# called when I click on a button "start simulation"
def plot_neutrons_flow(self):
# getting parameters from the graphical interface
if not self._started:
I0 = float(self._field_I0.get())
X0 = float(self._field_X0.get())
flow0 = float(self._field_flow0.get())
time_interval = float(self._field_time_interval.get())
stop = int(self._field_stop.get())
FLOW_CI = [I0, X0, flow0] # [I(T_0), X(T_0), PHI[T_0]]
self._simulation = NeutronsFlow(
edo=neutrons_flow_edo,
t0=0,
ci=FLOW_CI,
time_interval=time_interval,
stop=hour_to_seconds(stop)
)
# launch the animation
self._neutrons_flow_plot.animate(self._simulation)
self._started = True
Here is the code for the matplotlib animation :
import matplotlib
import tkinter as tk
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from matplotlib import style
matplotlib.use("TkAgg")
style.use('seaborn-whitegrid')
class PlotAnimation(FigureCanvasTkAgg):
def __init__(self, tk_root):
self._figure = Figure(dpi=100)
# bind plot to tkinter frame
super().__init__(self._figure, tk_root)
x_label = "Temps (h)"
y_label = "Flux / Abondance"
self._axes = self._figure.add_subplot(111, xlabel=x_label, ylabel=y_label, yscale="log")
self.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
def update(self, interval):
# this is never called
# get data from rk4 simulation
time_set = self._simulation.get_time_set()
y_set = self._simulation.get_y_set()
self._axes.clear()
self._axes.plot(time_set, y_set, visible=True, linewidth=1)
self._axes.legend(fancybox=True)
# redraw canvas
self.draw_idle()
def animate(self, simulation):
# this is called
self._simulation = simulation
# simulate differential equations with rk4 method
self._simulation.resolve()
# https://github.com/matplotlib/matplotlib/issues/1656
anim = animation.FuncAnimation(
self._figure,
self.update,
interval=1000
)
EDIT :
The solution was to instantiate the FuncAnimation function directly in the init method
As indicated in the documentation of the animation module (emphasis mine)
(...) it is critical to keep a reference to the instance object. The
animation is advanced by a timer (typically from the host GUI
framework) which the Animation object holds the only reference to. If
you do not hold a reference to the Animation object, it (and hence the
timers), will be garbage collected which will stop the animation.
You need to return the anim object from your animate() function, and store it somewhere in your code so that it is not garbage-collected

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

Remove option from NavigationToolbar2TkAgg?

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

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.