How to refresh AxesSubplot in python matplotlib? - python

In a QMainWindow, I am creating an AxesSubplot. It is created in the function initEventGraph when the window starts up with:
self.canvas = FigureCanvas(plt.Figure(figsize=(8.80,7.30), dpi=80, tight_layout=True))
self.canvas.setParent(parent)
self.canvas.move(10,20)
self.ax = self.canvas.figure.subplots()
In my code, I have indicated FUNCTION CALL # to illustrate the sequence of function calls for creating the plot.
I start off with wanting to plot 1 day of data.
I created some data with varying datetimes that are both less than 1 day old and more than 1 day old.
data = {"Apple" : { "location" : [25,50,10], 'time' : [datetime.datetime.now(), datetime.datetime.now() - datetime.timedelta(days = 5), datetime.datetime.now() - datetime.timedelta(days = 1) ] },
"Orange" : {"location" : [9,12,89] , 'time' : [datetime.datetime.now() - datetime.timedelta(days = 0.1), datetime.datetime.now() - datetime.timedelta(days = 0.15), datetime.datetime.now() - datetime.timedelta(days = 2)]}
}
When I run the GUI and it plots with the plot with num_days=1, it definitely does show the data that is 1 day old or newer.
Now, the problem is that when I activate the function refreshButtonPressed by clicking on the Refresh button, I want to display the past 10 days of data, but the plot does not refresh. This is done by calling the plot function with num_days=10. There should be a difference because the graph was initially created with num_days=1.
I would expect the days older than 1 day old to show up in the plot. But there are no points for these in the plot and the axes do not refresh either.
I've tried many things, such as calling the following within the plot function:
self.ax.cla()
self.ax.clf()
self.ax.show()
self.ax.clear()
self.ax.draw()
plt.show()
However, no matter what I do, I can't get the plot to refresh. I'm really stuck.
Here is my minimal example code. It's just to very simply illustrate the problem:
from __future__ import annotations
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtWidgets import QApplication, QSizePolicy, QGroupBox, QMessageBox, QTableWidgetItem, QLineEdit, QMainWindow, QHeaderView, QDesktopWidget, QPushButton, QLabel, QTableWidget
import datetime
import matplotlib.ticker as ticker
import matplotlib.dates as mdates
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt
import sys
class TestClass(QMainWindow):
def __init__(self, numDays): ##FUNCTION CALL #1
super(TestClass, self).__init__()
width = 1295
height = 700
self.setFixedSize(width, height)
self.setWindowTitle("Test")
self.centralwidget = QtWidgets.QWidget(self)
self.centralwidget.setObjectName("centralwidget")
self.numDays = numDays
self.initUI()
def initUI(self): #FUNCTION CALL #2
current_time = datetime.datetime.now()
self.createEventGraph(current_time=current_time, numDays=self.numDays)
self.createRefreshButton()
def createEventGraph(self, current_time, numDays): #FUNCTION CALL #3
self.eventGraphGroupBox = QGroupBox(self)
self.eventGraphGroupBox.setGeometry(QtCore.QRect(555,20,725,610))
self.initEventGraph(parent=self.eventGraphGroupBox, current_time=current_time, numDays=numDays)
def initEventGraph( self, parent, current_time , numDays ): #FUNCTION CALL #4
self.canvas = FigureCanvas(plt.Figure(figsize=(8.80,7.30), dpi=80, tight_layout=True))
self.canvas.setParent(parent)
self.canvas.move(10,20)
self.ax = self.canvas.figure.subplots()
FigureCanvas.setSizePolicy(self,
QSizePolicy.Expanding,
QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
self.plot(current_time=current_time, num_days=numDays)
def refreshButtonPressed(self ) :
current_time = datetime.datetime.now()
self.plot(current_time=current_time, num_days=10)
def createRefreshButton( self ) :
self.refreshButton = QPushButton(self)
self.refreshButton.setText("Refresh")
self.refreshButton.setGeometry(460,660,80,30)
self.refreshButton.clicked.connect(self.refreshButtonPressed)
def plot(self, current_time, num_days): #FUNCTION CALL #5 and for REFRESH
data = {"Apple" : { "location" : [25,50,10], 'time' : [datetime.datetime.now(), datetime.datetime.now() - datetime.timedelta(days = 5), datetime.datetime.now() - datetime.timedelta(days = 1) ] },
"Orange" : {"location" : [9,12,89] , 'time' : [datetime.datetime.now() - datetime.timedelta(days = 0.1), datetime.datetime.now() - datetime.timedelta(days = 0.15), datetime.datetime.now() - datetime.timedelta(days = 2)]}
}
unique_event_names = dict()
for name, entries in data.items():
if name not in unique_event_names:
unique_event_names[name] = True
for index, time_entry in enumerate(entries['time']):
if time_entry >= current_time - datetime.timedelta(days=num_days):
x_axis = entries['time'][index]
y_axis = entries['location'][index]
self.ax.scatter(x_axis,y_axis, label=name)
self.ax.xaxis.set_major_locator(ticker.LinearLocator(10))
self.ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M:%S %d-%m-%y"))
self.ax.tick_params(labelrotation=15)
self.ax.legend(bbox_to_anchor=(0,1.02,1,0.2), loc="lower left",
mode="expand", ncol=len(unique_event_names))
self.ax.set_ylabel("Location")
self.ax.grid()
def window():
app = QApplication(sys.argv)
theTestClass = TestClass(numDays = 1)
theTestClass.show()
sys.exit(app.exec_())
if __name__ == "__main__":
window()
Edit : I had a typo in my code that was causing every data point to show. Fixed it.

There are a few changes you could make and try.
It is best practice not to import pyplot if you are embedding. Instead use, from matplotlib.figure import Figure
Clear the figure before plotting with self.fig.clear() and create new axes.
Finally, refresh the canvas with self.canvas.draw()
from __future__ import annotations
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtWidgets import QApplication, QSizePolicy, QGroupBox, QMessageBox, QTableWidgetItem, QLineEdit, QMainWindow, QHeaderView, QDesktopWidget, QPushButton, QLabel, QTableWidget
import datetime
import matplotlib.ticker as ticker
import matplotlib.dates as mdates
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import sys
class TestClass(QMainWindow):
def __init__(self, numDays): ##FUNCTION CALL #1
super(TestClass, self).__init__()
width = 1295
height = 700
self.setFixedSize(width, height)
self.setWindowTitle("Test")
self.centralwidget = QtWidgets.QWidget(self)
self.centralwidget.setObjectName("centralwidget")
self.numDays = numDays
self.initUI()
def initUI(self): #FUNCTION CALL #2
current_time = datetime.datetime.now()
self.createEventGraph(current_time=current_time, numDays=self.numDays)
self.createRefreshButton()
def createEventGraph(self, current_time, numDays): #FUNCTION CALL #3
self.eventGraphGroupBox = QGroupBox(self)
self.eventGraphGroupBox.setGeometry(QtCore.QRect(555,20,725,610))
self.initEventGraph(parent=self.eventGraphGroupBox, current_time=current_time, numDays=numDays)
def initEventGraph( self, parent, current_time , numDays ): #FUNCTION CALL #4
self.fig = Figure(figsize=(8.80,7.30), dpi=80, tight_layout=True)
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(parent)
self.canvas.move(10,20)
FigureCanvas.setSizePolicy(self,
QSizePolicy.Expanding,
QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
self.plot(current_time=current_time, num_days=numDays)
def refreshButtonPressed(self ) :
current_time = datetime.datetime.now()
self.plot(current_time=current_time, num_days=10)
def createRefreshButton( self ) :
self.refreshButton = QPushButton(self)
self.refreshButton.setText("Refresh")
self.refreshButton.setGeometry(460,660,80,30)
self.refreshButton.clicked.connect(self.refreshButtonPressed)
def plot(self, current_time, num_days): #FUNCTION CALL #5 and for REFRESH
self.fig.clear()
# Create a 1x1 subplot
self.ax = self.fig.add_subplot(1, 1, 1)
data = {"Apple" : { "location" : [25,50,10], 'time' : [datetime.datetime.now(), datetime.datetime.now() - datetime.timedelta(days = 5), datetime.datetime.now() - datetime.timedelta(days = 1) ] },
"Orange" : {"location" : [9,12,89] , 'time' : [datetime.datetime.now() - datetime.timedelta(days = 0.1), datetime.datetime.now() - datetime.timedelta(days = 0.15), datetime.datetime.now() - datetime.timedelta(days = 2)]}
}
unique_event_names = dict()
for name, entries in data.items():
if name not in unique_event_names:
unique_event_names[name] = True
for index, time_entry in enumerate(entries['time']):
if time_entry >= current_time - datetime.timedelta(days=num_days):
x_axis = entries['time'][index]
y_axis = entries['location'][index]
self.ax.scatter(x_axis,y_axis, label=name)
self.ax.xaxis.set_major_locator(ticker.LinearLocator(10))
self.ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M:%S %d-%m-%y"))
self.ax.tick_params(labelrotation=15)
self.ax.legend(bbox_to_anchor=(0,1.02,1,0.2), loc="lower left",
mode="expand", ncol=len(unique_event_names))
self.ax.set_ylabel("Location")
self.ax.grid()
# refresh canvas
self.canvas.draw()
def window():
app = QApplication(sys.argv)
theTestClass = TestClass(numDays = 1)
theTestClass.show()
sys.exit(app.exec_())
if __name__ == "__main__":
window()

Related

Python qt5 matplotlb canvas animation with manual blit

So I'd like to integrate a matplotlib canvas in qt5 with manual blit.
I've found this thread:
Fast Live Plotting in Matplotlib / PyPlot
and the voted answer seems pretty nice however I need it in a qt5 window...
So I have tried to mash the code above together with the matplotlib qt5 tutorial into one script. https://matplotlib.org/gallery/user_interfaces/embedding_in_qt5_sgskip.html
It kinda works, however the animation only works when using the pan/zoom and the background is black :D and if blit is set to false it doesnt even draw...
If somebody could help me that would be amazing :) Its hilariously broken
from __future__ import unicode_literals
import random
import time
import matplotlib
from PyQt5.QtWidgets import QSizePolicy, QApplication, QWidget, QVBoxLayout
from matplotlib import pyplot as plt
import sys
import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib.animation import FuncAnimation
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import numpy as np
class MyMplCanvas(FigureCanvas):
# Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.).
def __init__(self, parent=None, width=5, height=4, dpi=100):
self.fig = plt.figure()
FigureCanvas.__init__(self, self.fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QSizePolicy.Expanding,
QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
self.x = np.linspace(0, 50., num=100)
self.X, self.Y = np.meshgrid(self.x, self.x)
# self.fig = plt.figure()
self.ax1 = self.fig.add_subplot(2, 1, 1)
self.ax2 = self.fig.add_subplot(2, 1, 2)
self.img = self.ax1.imshow(self.X, vmin=-1, vmax=1, interpolation="None", cmap="RdBu")
self.line, = self.ax2.plot([], lw=3)
self.text = self.ax2.text(0.8, 0.5, "")
self.ax2.set_xlim(self.x.min(), self.x.max())
self.ax2.set_ylim([-1.1, 1.1])
self.t_start = time.time()
self.k = 0.
#self.fig.canvas.draw() # note that the first draw comes before setting data
#self.update(blit=False)
anim = FuncAnimation(self.fig, self.update, interval=20)
def update(self, blit=True):
if blit:
# cache the background
self.axbackground = self.fig.canvas.copy_from_bbox(self.ax1.bbox)
self.ax2background = self.fig.canvas.copy_from_bbox(self.ax2.bbox)
self.img.set_data(np.sin(self.X / 3. + self.k) * np.cos(self.Y / 3. + self.k))
self.line.set_data(self.x, np.sin(self.x / 3. + self.k))
self.k += 0.11
if blit:
# restore background
self.fig.canvas.restore_region(self.axbackground)
self.fig.canvas.restore_region(self.ax2background)
# redraw just the points
self.ax1.draw_artist(self.img)
self.ax2.draw_artist(self.line)
self.ax2.draw_artist(self.text)
# fill in the axes rectangle
self.fig.canvas.blit(self.ax1.bbox)
self.fig.canvas.blit(self.ax2.bbox)
# in this post http://bastibe.de/2013-05-30-speeding-up-matplotlib.html
# it is mentionned that blit causes strong memory leakage.
# however, I did not observe that.
else:
# redraw everything
self.fig.canvas.draw()
# self.fig.canvas.flush_events()
# alternatively you could use
# plt.pause(0.000000000001)
# however plt.pause calls canvas.draw(), as can be read here:
# http://bastibe.de/2013-05-30-speeding-up-matplotlib.html
class PlotDialog(QWidget):
def __init__(self):
QWidget.__init__(self)
self.plot_layout = QVBoxLayout(self)
self.plot_canvas = MyMplCanvas(self, width=5, height=4, dpi=100)
self.navi_toolbar = NavigationToolbar(self.plot_canvas, self)
self.plot_layout.addWidget(self.plot_canvas)
self.plot_layout.addWidget(self.navi_toolbar)
if __name__ == "__main__":
app = QApplication(sys.argv)
dialog0 = PlotDialog()
dialog0.show()
sys.exit(app.exec_())

How to embed Matplotlib plot in PyQT widget?

I want to embed Matplotlib plot in my PyQt app using QWidget. This is the code of the widget script.
from PyQt5.QtWidgets import*
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.figure import Figure
from entropia import entropy
import matplotlib.pyplot as plt
import numpy as np
import random
class MplWidget(QWidget):
def __init__(self, parent = None):
QWidget.__init__(self,parent)
self.canvas = FigureCanvas(Figure())
self.vertical_layout = QVBoxLayout()
self.vertical_layout.addWidget(self.canvas)
self.setLayout(self.vertical_layout)
def draw(self):
QWidget.update(self)
self.canvas.axes = self.canvas.figure.add_subplot(111)
fs = 500
f = random.randint(1, 100)
ts = 1/fs
length_of_signal = 100
t = np.linspace(0,1,length_of_signal)
cosinus_signal = np.cos(2*np.pi*f*t)
sinus_signal = np.sin(2*np.pi*f*t)
self.canvas.axes.clear()
self.canvas.axes.plot(t, cosinus_signal)
self.canvas.axes.plot(t, sinus_signal)
self.canvas.axes.legend(('cosinus', 'sinus'),loc='upper right')
self.canvas.axes.set_title('Cosinus - Sinus Signal')
self.canvas.draw()
I want the plot to be displayed after the pushbutton in another script is clicked. Unfortunately, this is not working. Button is connected to the function, though. If I do something like print(fs) in the "draw" method I see the variable in the python terminal when the button gets clicked.
This is how it looks when the button gets clicked:
When I move the whole thing to the init method the plot is displayed.
class MplWidget(QWidget):
def __init__(self, parent = None):
QWidget.__init__(self,parent)
self.canvas = FigureCanvas(Figure())
self.vertical_layout = QVBoxLayout()
self.vertical_layout.addWidget(self.canvas)
self.canvas.axes = self.canvas.figure.add_subplot(111)
self.setLayout(self.vertical_layout)
fs = 500
f = random.randint(1, 100)
ts = 1/fs
length_of_signal = 100
t = np.linspace(0,1,length_of_signal)
cosinus_signal = np.cos(2*np.pi*f*t)
sinus_signal = np.sin(2*np.pi*f*t)
self.canvas.axes.clear()
self.canvas.axes.plot(t, cosinus_signal)
self.canvas.axes.plot(t, sinus_signal)
self.canvas.axes.legend(('cosinus', 'sinus'),loc='upper right')
self.canvas.axes.set_title('Cosinus - Sinus Signal')
self.canvas.draw()
So, what can I do to display the plot only after calling it from another method?

How to use the Span Selector on a embedded figure of matplotlib widget?

I am working on GUI where I have a system with graphs.
I want to use the spanselector in the graph i do visualize.
I have searched and i can't understand how to use the span selector while calling the matplotlib widget.
This is an example i'm following to plot. it has 3 parts(main,mplwidget,ui file)
the main code file
# ------------------------------------------------------
# ---------------------- main.py -----------------------
# ------------------------------------------------------
from PyQt5.QtWidgets import*
from PyQt5.uic import loadUi
from matplotlib.backends.backend_qt5agg import (NavigationToolbar2QT as NavigationToolbar)
import numpy as np
import random
#from matplotlib.widgets import SpanSelector
class MatplotlibWidget(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
loadUi("qt_designer.ui",self)
self.setWindowTitle("PyQt5 & Matplotlib Example GUI")
self.pushButton_generate_random_signal.clicked.connect(self.update_graph)
self.addToolBar(NavigationToolbar(self.MplWidget.canvas, self))
def update_graph(self):
fs = 500
f = random.randint(1, 100)
ts = 1/fs
length_of_signal = 100
t = np.linspace(0,1,length_of_signal)
cosinus_signal = np.cos(2*np.pi*f*t)
sinus_signal = np.sin(2*np.pi*f*t)
self.MplWidget.canvas.axes.clear()
self.MplWidget.canvas.axes.plot(t, cosinus_signal)
self.MplWidget.canvas.axes.plot(t, sinus_signal)
self.MplWidget.canvas.axes.legend(('cosinus', 'sinus'),loc='upper right')
self.MplWidget.canvas.axes.set_title('Cosinus - Sinus Signal')
self.MplWidget.canvas.draw()
#span = self.MplWidget.canvas.axes.SpanSelector(ax1, onselect, 'horizontal', useblit=True,rectprops=dict(alpha=0.5, facecolor='red'))
def onselect(min_value, max_value):
print(min_value, max_value)
return min_value, max_value
app = QApplication([])
window = MatplotlibWidget()
window.show()
app.exec_()
the mplwidget file
# ------------------------------------------------------
# -------------------- mplwidget.py --------------------
# ------------------------------------------------------
from PyQt5.QtWidgets import*
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.figure import Figure
class MplWidget(QWidget):
def __init__(self, parent = None):
QWidget.__init__(self, parent)
self.canvas = FigureCanvas(Figure())
vertical_layout = QVBoxLayout()
vertical_layout.addWidget(self.canvas)
self.canvas.axes = self.canvas.figure.add_subplot(111)
self.setLayout(vertical_layout)
the ui file is attached to this link with all the codes:
here
in the other way this is the example code to use the span selector:
import matplotlib.pyplot as plt
import matplotlib.widgets as mwidgets
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [10, 50, 100])
def onselect(vmin, vmax):
print(vmin, vmax)
rectprops = dict(facecolor='blue', alpha=0.5)
span = mwidgets.SpanSelector(ax, onselect, 'horizontal',span_stays=True,button=1,,rectprops=rectprops)
fig.show()
//////////////////////////////////////////////////////////////////////////////
i tried a lot of ways to assess the span selector but im a little bit confused in the way it works and how i should connect the the structure of code?
if i run whithin The comented line:
span = self.MplWidget.canvas.axes.SpanSelector(ax1, onselect, 'horizontal', useblit=True,rectprops=dict(alpha=0.5, facecolor='red'))
its shown the following error:
AttributeError:'AxesSubplot' object has no attribute 'SpanSelector'
finally, this is the desire result
You have to pass the self.MplWidget.canvas.axes as ax:
# ...
self.MplWidget.canvas.draw()
self.span = SpanSelector(
self.MplWidget.canvas.axes,
self.onselect,
"horizontal",
useblit=True,
rectprops=dict(alpha=0.5, facecolor="red"),
)
def onselect(self, min_value, max_value):
print(min_value, max_value)
Note: since select is a method of the class, it must have self as the first parameter, and it must be invoked with self.select.

How to draw a large amont of signal faster in matplotlib embedded within Qt environment?

I'm trying to plot a large among of signal in a matplotlib figure which is embedded in a Qt environment.
The plots are updated according to QScrollBar which modify the part of signals that I need to show.
My issue is the update of the figure takes a pretty long time, especially because I have 250 signals to update. So, I'm seeking for a way to optimize the EEG_plot.update function to reduce its draw time.
I don't know how I could use an animate function to speed up the process or something else.
My concern is I need to update the time axis ticks and probably also the y axis label positions.
The other thing is if the last segment that I need to plot does not correspond exactly with the window size chosen I need to plot only a part of the window (for instance the last segment will be 5s but the window size is 10s)
I give the entire script right below
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
import matplotlib
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import numpy as np
class Viewer(QMainWindow):
def __init__(self, parent=None):
super(Viewer, self).__init__()
self.parent = parent
#######################################
self.centralWidget = QWidget()
self.setCentralWidget(self.centralWidget)
self.mainVBOX_param_scene = QVBoxLayout()
self.mascene = plot(self)
self.paramPlotV = QVBoxLayout()
self.horizontalSliders = QScrollBar(Qt.Horizontal)
self.horizontalSliders.setFocusPolicy(Qt.StrongFocus)
self.horizontalSliders.valueChanged.connect(self.update_plot)
self.horizontalSliders.setMinimum(0)
self.horizontalSliders.setMaximum(1)
self.paramPlot = QHBoxLayout()
l_gain = QLabel('Gain')
self.e_gain = QLineEdit('5')
l_win = QLabel('Window')
self.e_win = QLineEdit('10')
l_spacing = QLabel('vertical spacing')
self.e_spacing = QLineEdit('10')
l_linewidth = QLabel('linewidth')
self.e_linewidth = QLineEdit('1')
self.e_gain.returnPressed.connect(self.update_plot)
self.e_win.returnPressed.connect(self.udpate_plot_plus_slider)
self.e_spacing.returnPressed.connect(self.update_plot)
self.e_linewidth.returnPressed.connect(self.update_plot)
self.paramPlot.addWidget(l_gain)
self.paramPlot.addWidget(self.e_gain)
self.paramPlot.addWidget(l_win)
self.paramPlot.addWidget(self.e_win)
self.paramPlot.addWidget(l_spacing)
self.paramPlot.addWidget(self.e_spacing)
self.paramPlot.addWidget(l_linewidth)
self.paramPlot.addWidget(self.e_linewidth)
self.paramPlotV.addWidget(self.horizontalSliders)
self.paramPlotV.addLayout(self.paramPlot)
self.mainVBOX_param_scene.addWidget(self.mascene)
self.mainVBOX_param_scene.addLayout(self.paramPlotV)
self.centralWidget.setLayout(self.mainVBOX_param_scene)
self.Fs = 1024
self.Sigs_dict = np.random.rand(250,105*self.Fs)
self.t = np.arange(self.Sigs_dict.shape[1])/self.Fs
self.parent.processEvents()
self.update()
def updateslider(self):
self.horizontalSliders.setMinimum(0)
self.horizontalSliders.setMaximum(np.ceil(self.t[-1]/int(self.e_win.text()))-1)
self.horizontalSliders.setPageStep(1)
self.horizontalSliders.update()
def udpate_plot_plus_slider(self):
self.updateslider()
self.mascene.update()
def update_plot(self):
self.mascene.update()
def update(self):
self.updateslider()
self.mascene.modify_sigs()
self.mascene.update()
class plot(QGraphicsView):
def __init__(self, parent=None):
super(plot, self).__init__(parent)
self.parent = parent
self.scene = QGraphicsScene(self)
self.setScene(self.scene)
self.figure = plt.figure(facecolor='white')#Figure()
self.canvas = FigureCanvas(self.figure)
self.widget = QWidget()
self.widget.setLayout(QVBoxLayout())
self.widget.layout().setContentsMargins(0, 0, 0, 0)
self.widget.layout().setSpacing(0)
self.scroll = QScrollArea(self.widget)
self.scroll.setWidget(self.canvas)
layout = QVBoxLayout()
layout.addWidget(self.scroll)
self.setLayout(layout)
def modify_sigs(self):
self.Sigs_dict = self.parent.Sigs_dict
self.t = self.parent.t
self.Fs= self.parent.Fs
def update(self):
win_num = self.parent.horizontalSliders.value()
self.figure.clear()
plt.figure(self.figure.number)
plt.subplots_adjust(left=0.1, bottom=0.01, right=1, top=1, wspace=0.0 , hspace=0.0 )
self.axes = plt.subplot(1, 1, 1)
gain = float(self.parent.e_gain.text())
win= float(self.parent.e_win.text())
self.spacing = float(self.parent.e_spacing.text())
linewidth = float(self.parent.e_linewidth.text())
ts = int(win*(win_num) * self.Fs)
te = ts + int(win * self.Fs)
if te > len(self.t):
te=len(self.t)
for i in range(self.Sigs_dict.shape[0]):
line, = plt.plot(self.t[ts:te], gain*(self.Sigs_dict[i,ts:te]-np.mean(self.Sigs_dict[i,ts:te]))+i*self.spacing, linewidth=linewidth )
self.axes.autoscale(enable=True, axis='both', tight=True)
self.axes.set_ylim((-self.spacing,(self.Sigs_dict.shape[0]+1)*self.spacing))
self.axes.set_xlim((ts/ self.Fs, ts / self.Fs + win ))
self.axes.set_yticks(np.arange(self.Sigs_dict.shape[0]) * self.spacing)
self.axes.set_yticklabels([str(n) for n in np.arange(self.Sigs_dict.shape[0])])
self.canvas.setGeometry(0, 0, self.parent.width()-100, (self.parent.height()-100)*self.spacing)
self.canvas.draw_idle()
def main():
app = QApplication(sys.argv)
app.setStyle('Windows')
ex = Viewer(app)
ex.showMaximized()
sys.exit(app.exec())
if __name__ == '__main__':
main()
Update
I made a new implementation where I try to update data instead of reploting all the figure each time (update_set_data function), I don't plot all the point of the curve (for instance if the number of point > 10000 points, I take only 50% of them) I used decimate = len(self.t[ts:te]) // 10000 + 1 to compute the decimation, and last I don't replot the figure when the user is draging the slider.
When I use the old version I get thos time to update the figure:
time old: 4.148899078369141
time old: 4.117990255355835
time old: 4.152893781661987
With the new version I get:
time new: 2.0400094985961914
time new: 2.0248610973358154
time new: 2.0305933952331543
I have to say, I expected more than a 50% time reduction.
Does someone have idea to optimize this more?
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
import matplotlib
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import numpy as np
import time
class Viewer(QMainWindow):
def __init__(self, parent=None):
super(Viewer, self).__init__()
self.parent = parent
#######################################
self.centralWidget = QWidget()
self.setCentralWidget(self.centralWidget)
self.mainVBOX_param_scene = QVBoxLayout()
self.mascene = plot(self)
self.paramPlotV = QVBoxLayout()
self.horizontalSliders = QScrollBar(Qt.Horizontal)
self.horizontalSliders.setFocusPolicy(Qt.StrongFocus)
self.horizontalSliders.valueChanged.connect(self.sliderReleasedfun)
self.horizontalSliders.sliderPressed.connect(self.sliderPressedfun)
self.horizontalSliders.sliderMoved.connect(self.sliderMovedfun)
self.horizontalSliders.sliderReleased.connect(self.sliderReleasedfun)
self.horizontalSliders.setMinimum(0)
self.horizontalSliders.setMaximum(1)
self.paramPlot = QHBoxLayout()
l_gain = QLabel('Gain')
self.e_gain = QLineEdit('5')
l_win = QLabel('Window')
self.e_win = QLineEdit('10')
l_spacing = QLabel('vertical spacing')
self.e_spacing = QLineEdit('10')
l_linewidth = QLabel('linewidth')
self.e_linewidth = QLineEdit('1')
self.e_gain.returnPressed.connect(self.update_plot)
self.e_win.returnPressed.connect(self.udpate_plot_plus_slider)
self.e_spacing.returnPressed.connect(self.update_plot)
self.e_linewidth.returnPressed.connect(self.update_plot)
self.paramPlot.addWidget(l_gain)
self.paramPlot.addWidget(self.e_gain)
self.paramPlot.addWidget(l_win)
self.paramPlot.addWidget(self.e_win)
self.paramPlot.addWidget(l_spacing)
self.paramPlot.addWidget(self.e_spacing)
self.paramPlot.addWidget(l_linewidth)
self.paramPlot.addWidget(self.e_linewidth)
self.paramPlotV.addWidget(self.horizontalSliders)
self.paramPlotV.addLayout(self.paramPlot)
self.mainVBOX_param_scene.addWidget(self.mascene)
self.mainVBOX_param_scene.addLayout(self.paramPlotV)
self.centralWidget.setLayout(self.mainVBOX_param_scene)
self.Fs = 1024
self.Sigs_dict = np.random.rand(250,105*self.Fs)
self.t = np.arange(self.Sigs_dict.shape[1])/self.Fs
self.parent.processEvents()
self.update()
def sliderPressedfun(self):
self.horizontalSliders.valueChanged.disconnect()
def sliderMovedfun(self,e):
self.horizontalSliders.setValue(e)
def sliderReleasedfun(self):
self.horizontalSliders.valueChanged.connect(self.movesliderfun)
self.movesliderfun()
def movesliderfun(self):
t0 = time.time()
self.horizontalSliders.setEnabled(False)
self.update_data()
self.horizontalSliders.setEnabled(True)
print('time new:', time.time()-t0)
def updateslider(self):
self.horizontalSliders.setMinimum(0)
self.horizontalSliders.setMaximum(np.ceil(self.t[-1]/int(self.e_win.text()))-1)
self.horizontalSliders.setPageStep(1)
self.horizontalSliders.update()
def udpate_plot_plus_slider(self):
self.updateslider()
self.mascene.update()
def update_plot(self):
self.mascene.update()
def update_data(self):
self.mascene.update_set_data()
def update(self):
self.updateslider()
self.mascene.modify_sigs()
self.mascene.update()
class plot(QGraphicsView):
def __init__(self, parent=None):
super(plot, self).__init__(parent)
self.parent = parent
self.scene = QGraphicsScene(self)
self.setScene(self.scene)
self.figure = plt.figure(facecolor='white')#Figure()
self.canvas = FigureCanvas(self.figure)
self.widget = QWidget()
self.widget.setLayout(QVBoxLayout())
self.widget.layout().setContentsMargins(0, 0, 0, 0)
self.widget.layout().setSpacing(0)
self.scroll = QScrollArea(self.widget)
self.scroll.setWidget(self.canvas)
layout = QVBoxLayout()
layout.addWidget(self.scroll)
self.setLayout(layout)
self.win=10
def modify_sigs(self):
self.Sigs_dict = self.parent.Sigs_dict
self.t = self.parent.t
self.Fs= self.parent.Fs
def update_set_data(self):
win_num = self.parent.horizontalSliders.value()
gain = float(self.parent.e_gain.text())
win= float(self.parent.e_win.text())
if not self.spacing == float(self.parent.e_spacing.text()):
self.spacing = float(self.parent.e_spacing.text())
spacing = True
else:
spacing = False
self.linewidth = float(self.parent.e_linewidth.text())
ts = int(self.win * (win_num) * self.Fs)
te = ts + int(self.win * self.Fs)
if te > len(self.t):
diff = te - len(self.t)
ts = ts - diff
te = len(self.t)
decimate = len(self.t[ts:te]) // 10000 + 1
for i in range(self.Sigs_dict.shape[0]):
self.Lines[i].set_data(self.t[ts:te:decimate], gain*(self.Sigs_dict[i,ts:te:decimate]-np.mean(self.Sigs_dict[i,ts:te:decimate]))+i*self.spacing )
self.Lines[i].set_linewidth(self.linewidth)
if spacing:
self.axes.set_ylim((-self.spacing,(self.Sigs_dict.shape[0]+1)*self.spacing))
self.axes.set_yticks(np.arange(self.Sigs_dict.shape[0]) * self.spacing)
self.axes.set_yticklabels([str(n) for n in np.arange(self.Sigs_dict.shape[0])])
self.axes.set_xlim((ts/ self.Fs, ts / self.Fs + win ))
# self.canvas.draw_idle()
self.canvas.draw()
def update(self):
win_num = self.parent.horizontalSliders.value()
self.figure.clear()
plt.figure(self.figure.number)
plt.subplots_adjust(left=0.1, bottom=0.01, right=1, top=1, wspace=0.0 , hspace=0.0 )
self.axes = plt.subplot(1, 1, 1)
gain = float(self.parent.e_gain.text())
win= float(self.parent.e_win.text())
self.spacing = float(self.parent.e_spacing.text())
linewidth = float(self.parent.e_linewidth.text())
ts = int(self.win * (win_num) * self.Fs)
te = ts + int(self.win * self.Fs)
if te > len(self.t):
diff = te - len(self.t)
ts = ts - diff
te = len(self.t)
decimate = len(self.t[ts:te]) // 10000 + 1
self.Lines = []
for i in range(self.Sigs_dict.shape[0]):
line, = plt.plot(self.t[ts:te:decimate], gain*(self.Sigs_dict[i,ts:te:decimate]-np.mean(self.Sigs_dict[i,ts:te:decimate]))+i*self.spacing, linewidth=linewidth )
self.Lines.append(line)
self.axes.autoscale(enable=True, axis='both', tight=True)
self.axes.set_ylim((-self.spacing,(self.Sigs_dict.shape[0]+1)*self.spacing))
self.axes.set_xlim((ts/ self.Fs, ts / self.Fs + win ))
self.axes.set_yticks(np.arange(self.Sigs_dict.shape[0]) * self.spacing)
self.axes.set_yticklabels([str(n) for n in np.arange(self.Sigs_dict.shape[0])])
self.canvas.setGeometry(0, 0, self.parent.width()-100, (self.parent.height()-100)*self.spacing)
self.canvas.draw_idle()
def main():
app = QApplication(sys.argv)
app.setStyle('Windows')
ex = Viewer(app)
ex.showMaximized()
sys.exit(app.exec())
if __name__ == '__main__':
main()
You can try this with multi threading,
so that you can break the whole code in many sub code
and your all code will run in same time with multi threading

Matplotlib axes formatting

I want to add the current date and time to the x-axes of a real time plot. I have tried almost everything beginning with custom ticks to custom axes, but I cannot seem to add it. How should I do this?
import sys
import pylab
from pylab import *
from PyQt4 import QtGui,QtCore
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg \
import FigureCanvasQTAgg as FigureCanvas
import MySQLdb as mdb
class CPUMonitor(FigureCanvas):
def __init__(self):
self.date = []
conn = mdb.connect("serv","user","pass","db")
self.cur = conn.cursor()
self.before = self.prepare_cpu_usage()
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
FigureCanvas.__init__(self, self.fig)
self.ax.set_title("Pong | CPU Utilization")
self.ax.set_xlabel("Datetime")
self.ax.set_ylabel("CPU %")
self.ax.set_autoscale_on(False)
self.user =[]
self.l_user, = self.ax.plot([],self.user, label='Total %')
self.ax.legend()
self.fig.canvas.draw()
self.cnt = 0
self.timerEvent(None)
self.timer1 = QtCore.QTimer()
self.timer1.timeout.connect(self.get_database_data)
self.timer1.start(5)
self.timer = self.startTimer(5000)
def get_database_data(self):
self.cur.execute("SELECT cpu,date FROM status WHERE date = (SELECT MAX(date) FROM status);")
self.data_db = self.cur.fetchone()
return self.data_db
def prepare_cpu_usage(self):
t = self.get_database_data()
return [t[0],t[1]]
def get_cpu_usage(self):
now = self.prepare_cpu_usage()
self.before = now
print self.before
return self.before
def datetime(self):
self.dates = self.get_cpu_usage()
self.dates = self.dates[1]
self.date.append(self.dates)
return str(self.dates)
def timerEvent(self, evt):
result = self.get_cpu_usage()
self.user.append(result[0])
self.l_user.set_data(range(len(self.user)), self.user)
self.fig.canvas.draw()
CurrentXAxis=pylab.arange(len(self.user)-1000,len(self.user),1)
self.ax.axis([CurrentXAxis.min(),CurrentXAxis.max(),0,100])
self.cnt += 1
app = QtGui.QApplication(sys.argv)
widget = CPUMonitor()
widget.setWindowTitle("Pong: CPU Usage")
widget.show()
sys.exit(app.exec_())
You can check a simple example first to get an idea of how to make date markers work: http://matplotlib.sourceforge.net/examples/pylab_examples/finance_demo.html
First you will need to import the date plotting classes, for example:
from matplotlib.dates import DateFormatter, WeekdayLocator, MONDAY
The documentation is available at http://matplotlib.sourceforge.net/api/dates_api.html
Then in the definition of the figure, set the locator (for tick marks), and formatter (for tick labels). The code below sets tick marks on every monday:
self.ax.xaxis.set_major_locator(WeekdayLocator(MONDAY))
self.ax.xaxis.set_major_formatter(DateFormatter('%b %d'))
self.ax.xaxis_date()
You should use datetime.datetime now for X-axis values and ranges instead of integers. I expect MySQLdb to return datetime.datetime objects, otherwise you will have to convert the timestamps.
The date formatter will complain if you try to plot an empty graph. Don't forget to set reasonable initial limits.
Here is an example of your code, where I stripped the database code (and some more) and replaced it with generated values:
import sys
from pylab import *
from PyQt4 import QtGui
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.dates import DateFormatter, WeekdayLocator, MONDAY
import datetime
import random
class CPUMonitor(FigureCanvas):
def __init__(self):
# Dummy variable to simulate time.
self.delay = 0
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
FigureCanvas.__init__(self, self.fig)
self.ax.set_title("Pong | CPU Utilization")
self.ax.set_xlabel("Datetime")
self.ax.set_ylabel("CPU %")
self.ax.xaxis.set_major_locator(WeekdayLocator(MONDAY))
self.ax.xaxis.set_major_formatter(DateFormatter('%b %d'))
self.ax.xaxis_date()
# Set resonable initial limits.
td = datetime.timedelta(1)
self.ax.set_xlim(datetime.datetime.now(), datetime.datetime.now() + td)
self.dates = []
self.user =[]
self.l_user, = self.ax.plot([],self.user, label='Total %')
self.ax.legend()
self.timer = self.startTimer(5000)
def get_database_data(self):
self.delay += 1
td = datetime.timedelta(0, self.delay * 5)
return [random.random(), datetime.datetime.now() + td]
def prepare_cpu_usage(self):
t = self.get_database_data()
return [t[0],t[1]]
def get_cpu_usage(self):
return self.prepare_cpu_usage()
def timerEvent(self, evt):
result = self.get_cpu_usage()
self.user.append(result[0])
self.dates.append(result[1])
self.l_user.set_data(self.dates, self.user)
if len(self.dates) >= 2:
self.ax.set_xlim(self.dates[0], self.dates[-1])
self.draw()
app = QtGui.QApplication(sys.argv)
widget = CPUMonitor()
widget.setWindowTitle("Pong: CPU Usage")
widget.show()
sys.exit(app.exec_())

Categories

Resources