How can I update a matplotlib plot event driven?
Background:
I wanna program a tool that displays me measured values I receive via a serial port.
These values will be send by a serial connected device when it has measured values and not when the host requests.
Current State:
It seems there is no way to update a plot manually.
When I call the plot()-function more than one time the new plots will be added. Similar to MATLAB or Octave, when you use the "hold on"-Option.
This ends after about 10 calls. Then it is freezed.
When I clear the figures before update, the whole plot disappears.
At least, when the plot is embedded in a window.
Neither draw() nor show() provide a remedy.
When I use a single plot figure, there is no easy way to update, because after the call of show(), the program flow sticks at this line, until the Window is closed. So the update has to be done in a separate thread.
Both problems will be solved, when I use an animation:
import sys
from PyQt4.QtGui import QApplication, QMainWindow, QDockWidget, QVBoxLayout,QTabWidget, QWidget
from PyQt4.QtCore import Qt
from matplotlib import pyplot, animation
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
from random import randint
a = QApplication(sys.argv)
w = QMainWindow()
t = QTabWidget(w)
Tab1 = QWidget()
t.addTab(Tab1, '1st Plot')
t.resize(1280, 300)
x = [1, 2, 3]
Fig1 = pyplot.Figure();
Plot = Fig1.add_subplot(111);
Plot.plot(x)
Plot.grid();
layout = QVBoxLayout();
layout.addWidget(FigureCanvasQTAgg(Fig1));
Tab1.setLayout(layout);
def UpdateFunction(i):
x.append(randint(0, 10));
Plot.clear();
Plot.plot(x);
Plot.grid();
Anim = animation.FuncAnimation(Fig1, UpdateFunction, interval=500)
w.showMaximized()
sys.exit(a.exec_())
But such an animation is time triggered.
Is there any solution to update event triggered?
The event should be a finished UART read in.
Thank you very much.
Fabian
#tcaswell
Thank you!
This is my actual code:
import sys
from PyQt4.QtGui import QApplication, QMainWindow, QVBoxLayout,QTabWidget, QWidget
from PyQt4.QtCore import SIGNAL
from matplotlib import pyplot
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
from random import randint
import time
from threading import Thread
a = QApplication(sys.argv)
w = QMainWindow()
t = QTabWidget(w)
Tab1 = QWidget()
t.addTab(Tab1, '1st Plot')
t.resize(1280, 300)
x = [];
x.append(randint(0, 10));
x.append(randint(0, 10));
x.append(randint(0, 10));
Fig1 = pyplot.Figure();
Plot = Fig1.add_subplot(111);
Line, = Plot.plot(x)
Plot.grid();
layout = QVBoxLayout();
layout.addWidget(FigureCanvasQTAgg(Fig1));
Tab1.setLayout(layout);
def triggerUpdate():
while True:
a.emit(SIGNAL('updatePlot()'));
time.sleep(2);
print('update triggered!\n')
def updateFunction():
x.append(randint(0, 10));
#Plot.clear();
Line.set_data(range(len(x)), x);
Fig1.canvas.draw_idle();
print('update done!\n')
a.connect(a, SIGNAL('updatePlot()'), updateFunction)
t = Thread(target=triggerUpdate);
t.start();
w.showMaximized()
sys.exit(a.exec_())
It seems to run, but the content of the plot gets not updated.
The Plot has no canvas. (Although it should have one, otherwise I don't now on what it is plotting when I command it.)
To be honest, I did not understand already the concept of the GUI and plot foo. The missing canvas is covered by its parenting figure. I really don't got it. When there is a figure, what can handle more than one plots, should't each plot have its own canvas?
But I think, here is the problem.
Thank you very much.
Related
I've got a program that on launch will open a matplotlib graph of bitcoin prices and will update every time the price changes. Then when I close it a fullscreen tkinter application opens with a background image and a updating clock in the corner. What I would like to happen is when I launch the program a fullscreen tkinter application opens with a background image, an updating clock in the corner, and the matplotlib graph in the middle. Basically I want to embed the matplotlib graph into my application. I've tried putting the graph code into my class and calling it but that just gives the results mentioned earlier. I tried replacing the plt.show() with this:
canvas = FigureCanvasTkAgg(fig, self)
canvas.show()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
but then the program wasn't launching at all. No matter where I put it in the script it would give errors like ''self' is not defined' when I directly replaced plt.show()with it and ''canvas' is not defined' when I tried to integrate it into the class. Here is the full code below:
from tkinter import *
from PIL import ImageTk, Image
import os
import time
import requests
import tkinter as tk
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
from matplotlib import style
from tkinter import ttk
import urllib
import json
import pandas as pd
import numpy as np
from PyQt5 import QtWidgets
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
class App(tk.Tk):
def __init__(self):
# call the __init__ method of Tk class to create the main window
tk.Tk.__init__(self)
# background image
img = ImageTk.PhotoImage(Image.open("0.png"))
panel = Label(self, image=img)
panel.pack()
# clock
self.label = tk.Label(self, text="", font=('comic',50,'bold'),
bg='#464545', fg='#1681BE')
self.label.place(height=204, width=484, x=1384, y=824)
self.update_clock()
# window geometry
w, h = self.winfo_screenwidth(), self.winfo_screenheight()
self.geometry("%dx%d+0+0" % (w, h))
self.overrideredirect(True)
self.mainloop()
def update_clock(self):
now = time.strftime('%H:%M:%S')
self.label.configure(text=now)
self.after(1000, self.update_clock)
def animate(self):
dataLink = 'https://btc-e.com/api/3/trades/btc_usd?limit=2000d'
data = urllib.request.urlopen(dataLink)
data = data.read().decode("utf-8")
data = json.loads(data)
data = data["btc_usd"]
data = pd.DataFrame(data)
buys = data[(data['type']=="ask")]
buys["datestamp"] = np.array(buys["timestamp"]).astype("datetime64[s]")
buyDates = (buys["datestamp"]).tolist()
plot(buyDates, buys["price"])
xlabel('time')
ylabel('Temperature')
title('Temperature Chart')
grid(True)
ani = animation.FuncAnimation(fig, animate, interval=1000)
plt.show()
app = App()
There are a few dependable's like '0.png' which is the background image and of course the many modules but any feedback would be much appreciated.
plt.show() will open a new dedicated GUI window to host the figure(s) currently present in the matplotlib state machine. If you don't want that, don't use plt.show().
To be on the save side, best don't use pyplot at all as even such innocent commands as plt.xticks() can cause a new window to open.
Also, get rid of from pylab import *. Pylab is a construct which includes several packages like numpy and pyplot in a single namespace. This may be desired for interactive notebook-like applications, but is usually a bad idea for scripts, as it makes debugging very cumbersome. Instead, try to fully stick to matplotlib API commands.
This linked question actually is an example of how to use FigureCanvasTkAgg and NavigationToolbar2Tk to embed a figure into tk.
A better start might be to look at the matplotlib example page where a minimal example for tk embedding is presented.
I am developing a Desktop application using Qt Designer and PyQt4. I need to display a some figure with contour which in python could be done with matplotlib.pyplot.contourf. I want to display the result inside a QGraphicView object.
I am trying to do it by promoting the QGraphicView to a pygtgraph in Qt-Designer. If object name of QGraphicView is CNOPlot I have written
import matplotlib.pyplot as plt
self.CNOPlot.plot(plt.contourf(xx, yy, Z,cmap=plt.cm.autumn, alpha=0.8))
It is giving output in separate window.
I want this is to be ploted inside CNOPlot.
Here is a short example:
import matplotlib.pyplot as plt
from PyQt4 import QtGui
from PyQt4.Qt import Qt
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
class MyView(QtGui.QGraphicsView):
def __init__(self):
QtGui.QGraphicsView.__init__(self)
scene = QtGui.QGraphicsScene(self)
self.scene = scene
figure = Figure()
axes = figure.gca()
axes.set_title("title")
axes.plot(plt.contourf(xx, yy, Z,cmap=plt.cm.autumn, alpha=0.8))
canvas = FigureCanvas(figure)
canvas.setGeometry(0, 0, 500, 500)
scene.addWidget(canvas)
self.setScene(scene)
I'm trying to capture and save to a file whole QWidget window with several matplotlib plots.
I'm adding matplotlib axes to the widget as below:
import sys
from PyQt4 import QtGui
from matplotlib import pyplot as plt
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
app = QtGui.QApplication(sys.argv)
win = QtGui.QWidget()
grid = QtGui.QGridLayout()
fig = plt.figure()
axs = fig.add_subplot(111)
axs.plot([1,2,3,4],[5,6,7,8])
canv = FigureCanvas(fig)
canv.setMaximumHeight(100)
grid.addWidget(canv, 0, 0)
grid.addWidget(QtGui.QLabel('Label'),1,0)
win.setLayout(grid)
win.show()
win.setFixedSize(150,100)
Output window looks ok (output.png - from external print-screen app).
But when I try to capture the output window to the QPixmap object by:
sshot = QtGui.QPixmap.grabWidget(ow)
sshot.save('tmp.png')
tmp.jpg doesn't contain matplotlib figure (tmp.png).
Even when I try to capture whole desktop:
sshot = QtGui.QPixmap.grabWindow(app.desktop().winId())
sshot.save('desktop.png')
the matplotlib figure is empty (desktop.png).
Three screen-shots
How can I capture the whole window without blank matplotlib figures?
Cheers,
Pawel
I wanna embed a Matplotlib plot directly into a window, QMainWindow.
It should be part of my program with a more complex GUI. ;)
The only way I found was to add the figure as widget into a QTabWidget.
See sample code below.
I lost the link to the webpage what inspired me.
Is there any way to embed the figure directly into the windows like other elements (buttons, textfield, textarea, ...)?
import sys
from PyQt4.QtGui import QApplication, QMainWindow, QDockWidget, QVBoxLayout,QTabWidget, QWidget
from matplotlib import pyplot
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
a = QApplication(sys.argv)
w = QMainWindow()
t = QTabWidget(w)
Tab1 = QWidget()
t.addTab(Tab1, '1st Plot')
t.resize(1280, 300)
x = [1, 2, 3]
Fig1 = pyplot.Figure();
Plot = Fig1.add_subplot(111);
Plot.plot(x)
Plot.grid();
layout = QVBoxLayout();
layout.addWidget(FigureCanvasQTAgg(Fig1));
Tab1.setLayout(layout);
w.showMaximized()
sys.exit(a.exec_())
Thank you very much.
FigureCanvasQtAgg is just a QWidget like any of the other controls you mentioned. The main difference being it doesn't allow you to pass a parent in the constructor like when you write
t = QTabWidget(w)
You can achieve the same with FigureCanvasQtAgg by calling setParent
canvas = FigureCanvasQtAgg(Fig1)
canvas.setParent(w)
You can also use QMainWindow's setCentralWidget method to add the matplotlib FigureCanvas directly to your main window. However, if you want a more complex gui with other controls I don't see any real problems with your current approach.
Lastly, you shouldn't really be using pyplot when embedding matplotlib. Stick with the object oriented API. Take a look at this example.
This is what I use for PySide. FigureCanvasQTAgg is a qt widget, so you don't need the PlotWidget class. I just find it useful, because it creates the figure for me. It also makes it useful for other projects, because you don't have to deal with importing the right matplotlib objects. From the PlotWidget just call the axes for all of your plotting needs.
from PySide import QtGui
import matplotlib
matplotlib.use("Qt4Agg")
matplotlib.rcParams["backend.qt4"] = "PySide"
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
class PlotWidget(FigureCanvas):
'''Plotting widget that can be embedded in a PySide GUI.'''
def __init__(self, figure=None):
if figure is None:
figure = Figure(tight_layout=True)
super().__init__(figure)
self.axes = self.figure.add_subplot(111)
self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
# end class
if __name__ == "__main__":
import sys
a = QtGui.QApplication(sys.argv)
w = QtGui.QMainWindow()
w.show()
p = PlotWidget()
p.axes.plot([])
nav = NavigationToolbar(p, w)
w.addToolBar(nav)
w.setCentralWidget(p)
# or
# container = QtGui.QWidget()
# layout = QtGui.QHBoxLayout()
# container.setLayout(layout)
# w.setCentralWidget(container)
# layout.addWidget(p)
sys.exit(a.exec_())
When using matplotlib:
from matplotlib import pyplot as plt
figure = plt.figure()
ax = figure.add_subplot(111)
ax.plot(x,y)
figure.show() # figure is shown in GUI
# How can I view the figure again after I closed the GUI window?
figure.show() # Exception in Tkinter callback... TclError: this isn't a Tk application
figure.show() # nothing happened
So my questions are:
How can I get the previous plot back if I have called figure.show()?
Is there a more convenient alternative to figure.add_suplot(111) if I have multiple figures and thus from pylab import *; plot(..); show() seems not a solution I'm looking for.
And what I eagerly want is
showfunc(stuff) # or
stuff.showfunc()
where stuff is an object containing all the plots arranged in one picture, and showfunc is STATELESS(I mean, each time I call it, I behaves as if it's first time called). Is this possible when working with matplotlib?
I can't find a satisfactory answer, so I handle this problem by writing a custom Figure class extending matplotlib.figure.Figure and providing a new show() method, which create a gtk.Window object each time called.
import gtk
import sys
import os
import threading
from matplotlib.figure import Figure as MPLFigure
from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NaviToolbar
class ThreadFigure(threading.Thread):
def __init__(self, figure, count):
threading.Thread.__init__(self)
self.figure = figure
self.count = count
def run(self):
window = gtk.Window()
# window.connect('destroy', gtk.main_quit)
window.set_default_size(640, 480)
window.set_icon_from_file(...) # provide an icon if you care about the looks
window.set_title('MPL Figure #{}'.format(self.count))
window.set_wmclass('MPL Figure', 'MPL Figure')
vbox = gtk.VBox()
window.add(vbox)
canvas = FigureCanvas(self.figure)
vbox.pack_start(canvas)
toolbar = NaviToolbar(canvas, window)
vbox.pack_start(toolbar, expand = False, fill = False)
window.show_all()
# gtk.main() ... should not be called, otherwise BLOCKING
class Figure(MPLFigure):
display_count = 0
def show(self):
Figure.display_count += 1
thrfig = ThreadFigure(self, Figure.display_count)
thrfig.start()
Make this file as start file of IPython. And
figure = Figure()
ax = figure.add_subplot(211)
... (same story as using standard `matplotlib.pyplot` )
figure.show()
# window closed accidentally or intentionally...
figure.show()
# as if `.show()` is never called
Works! I never touched GUI programming, and don't know if this would have any side-effect. Comment freely if you think something should be done that way.