How to get x-axis value in wx canvas from mouse click? - python

I have a wx plot which is defined like so
import wx
import numpy as np
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import \
FigureCanvasWxAgg as FigCanvas
import pylab
class GraphFrame(wx.Panel):
def __init__(self, parent, chart_name, nsamples, varnames, krefresh=150):
wx.Panel.__init__(self, parent=parent)
self.display_function = self._display_abs
self.need_to_copy_background = False
self.chart_name = chart_name
self.nsamples = nsamples
self.varnames = varnames
self.krefresh = krefresh
self.data = np.zeros((self.nsamples, len(self.varnames)))
self._create_panel(chart_name)
def _create_panel(self, chart_name=None):
self.init_plot()
self.canvas = FigCanvas(self, -1, self.fig)
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.vbox.Add(self.canvas, 1, flag=wx.ALIGN_CENTER)
self.SetSizer(self.vbox)
self.vbox.Fit(self)
def set_title(self, new_title):
self.axes.set_title(new_title, size=10)
self.canvas.draw()
def init_plot(self):
self.yrange = [-1.0, +1.0]
self.yrange2 = [-555.0, +555.0]
t0 = -0.125
t1 = 0.0
self.time = np.linspace(t0, t1, self.nsamples)
self.k = 0
self.dpi = 100
self.fig = Figure(dpi=self.dpi)
self.fig.set_tight_layout(True)
self.axes = self.fig.add_subplot(111)
self.axes2 = self.axes.twinx()
self.axes.set_title(self.chart_name, size=10)
self.axes.grid(True)
pylab.setp(self.axes.get_xticklabels(), fontsize=8)
pylab.setp(self.axes.get_yticklabels(), fontsize=8)
pylab.setp(self.axes2.get_yticklabels(), fontsize=8)
self.plot = []
self.plot2 = []
for i in range(len(self.varnames)):
ax = self.axes.plot([],
[],
"%s-" % ("krgbym"[i]),
linewidth=0.5,
color=(0, 0, 1))[0]
ax2 = self.axes2.plot(
[],
[],
"%s-" % ("krgbym"[i]),
linewidth=2,
color=(1, 0, 0)
)[0]
self.plot.append(ax)
self.plot2.append(ax2)
def draw_plot(self):
data = self.data[:, 0]
occ = self.data[:, 1]
self.need_to_copy_background = True
dmin = data.min()
dmax = data.max()
if dmin == 0.0 and dmax == 0.0:
self.yrange = [-1.0, +1.0]
self.need_to_copy_background= False
elif dmin < self.yrange[0] or dmax > self.yrange[1]:
self.yrange[0] = dmin - 0.1666 * (dmax - dmin)
self.yrange[1] = dmax + 0.1666 * (dmax - dmin)
self.need_to_copy_background= False
elif (dmax - dmin) < 0.25 * (self.yrange[1] - self.yrange[0]):
self.yrange[0] = dmin - 0.1666 * (dmax - dmin)
self.yrange[1] = dmax + 0.1666 * (dmax - dmin)
self.need_to_copy_background= False
ymin = self.yrange[0]
ymax = self.yrange[1]
self.need_to_copy_background = False
self.axes.set_xbound(0, self.nsamples)
self.axes.set_ybound(lower=ymin, upper=ymax)
self.axes2.set_ybound(lower=occ.min(), upper=occ.max())
t0 = -0.125
t1 = 0.0
self.time = np.linspace(t0, t1, self.nsamples)
self.plot[0].set_data(np.arange(self.nsamples), data)
self.plot2[0].set_data(np.arange(self.nsamples), occ)
if self.need_to_copy_background:
self.copy_background()
self.canvas.restore_region(self.background)
for i in range(len(self.varnames)):
self.axes.draw_artist(self.plot[i])
self.axes2.draw_artist(self.plot2[i])
self.canvas.blit(self.fig.bbox)
else:
self.canvas.draw()
def copy_background(self):
self.axes.cla()
self.axes.set_xlim(self.time[0], self.time[-1])
self.axes.set_ylim(self.yrange[0], self.yrange[1])
for i in range(len(self.varnames)):
self.plot[i] = self.axes.plot([], [], "%s-" % ("krgby"[i]), lw=2.0)[0]
self.axes.grid(True)
self.axes2.cla()
self.axes2.set_xlim(self.time[0], self.time[-1])
self.axes2.set_ylim(self.yrange2[0], self.yrange2[1])
for i in range(len(self.varnames)):
self.plot2[i] = self.axes2.plot([], [], "%s-" % ("krgby"[i]), lw=2.0)[0]
self.axes2.grid(True)
self.canvas.draw()
self.background = self.canvas.figure.canvas.copy_from_bbox(self.canvas.figure.bbox)
def _display_abs(self, v):
return np.abs(v)
def add_data(self, val):
self.data[:-1] = self.data[1:]
self.data[-1] = self.display_function(val)
self.k += 1
if (self.k % self.krefresh) == 0:
self.draw_plot()
This works fine.
When add_data is called, the graph is updated and refreshed correctly.
I want to add a feature that would allow the user to mouse click on somewhere on the graph (canvas), and a callback would be issued with an argument that has the x-value of the selected point on the graph.
For example
Y
^
| /\ /\
| / \/ \ X<<< pressing on this point would give 14
| / \ _
| / \/ \_
|/_________________________>x
012345678901234567890123456
Pressing the point where the X is on the beautiful graph above should return 14, as the X value at that point is 14.
Notice, if the X value does not start from 0, is float (and not int), or anything, else, still the data's value should be returned, and not the data's index.
Is this possible with wx? How?

It seems this is built into mpl, see https://matplotlib.org/3.1.1/users/event_handling.html
cid = fig.canvas.mpl_connect('button_press_event', onclick)
I created this sample to try it out and it works perfectly.
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wxagg import NavigationToolbar2WxAgg as NavigationToolbar
from matplotlib.backends.backend_wx import _load_bitmap
from matplotlib.figure import Figure
import numpy as np
import wx
class MyNavigationToolbar(NavigationToolbar):
"""Extend the default wx toolbar with your own event handlers."""
def __init__(self, canvas, cankill):
NavigationToolbar.__init__(self, canvas)
# for simplicity I'm going to reuse a bitmap from wx, you'll
# probably want to add your own.
tool = self.AddTool(wx.ID_ANY, 'Click me', _load_bitmap('back.png'),
'Activate custom contol')
self.Bind(wx.EVT_TOOL, self._on_custom, id=tool.GetId())
def _on_custom(self, evt):
# add some text to the axes in a random location in axes (0,1)
# coords) with a random color
# get the axes
ax = self.canvas.figure.axes[0]
# generate a random location can color
x, y = np.random.rand(2)
rgb = np.random.rand(3)
# add the text and draw
ax.text(x, y, 'You clicked me',
transform=ax.transAxes,
color=rgb)
self.canvas.draw()
evt.Skip()
class CanvasFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1,
'CanvasFrame', size=(550, 350))
self.figure = Figure(figsize=(5, 4), dpi=100)
self.axes = self.figure.add_subplot(111)
t = np.arange(0.0, 3.0, 0.01)
s = np.sin(2 * np.pi * t)
self.axes.plot(t, s)
self.canvas = FigureCanvas(self, -1, self.figure)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.TOP | wx.LEFT | wx.EXPAND)
self.toolbar = MyNavigationToolbar(self.canvas, True)
self.toolbar.Realize()
# By adding toolbar in sizer, we are able to put it at the bottom
# of the frame - so appearance is closer to GTK version.
self.sizer.Add(self.toolbar, 0, wx.LEFT | wx.EXPAND)
# update the axes menu on the toolbar
self.toolbar.update()
self.SetSizer(self.sizer)
self.Fit()
self.canvas.mpl_connect('button_press_event', self.OnClick)
#self.canvas.Bind(wx.MOUSE_BTN_LEFT, self.OnClick)
def OnClick(self, e):
print('%s click: button=%d, x=%d, y=%d, xdata=%f, ydata=%f' %
('double' if e.dblclick else 'single', e.button,
e.x, e.y, e.xdata, e.ydata))
class App(wx.App):
def OnInit(self):
'Create the main window and insert the custom frame'
frame = CanvasFrame()
frame.Show(True)
return True
app = App(0)
app.MainLoop()

Related

Adding a label to a Line2D in matplotlib and PyQt5

I was experimenting a bit with the answer code of this stackoverflow question: https://stackoverflow.com/a/61226106/10810208
for my project I am making a live system monitor, but I can't figure out how to put a label on a Line2D, because now it is only 1 line but there will be more.
this is the part I have trouble with:
self.ax1.set_xlabel('time in 1/10 of a second')
self.ax1.set_ylabel('usage in %')
self.line1 = Line2D([], [], color='blue') #here I need to add the label to
self.line1_tail = Line2D([], [], color='blue', linewidth=2)
self.line1_head = Line2D([], [], color='red', marker='o', markeredgecolor='r')
self.ax1.add_line(self.line1)
self.ax1.add_line(self.line1_tail)
self.ax1.add_line(self.line1_head)
self.ax1.set_xlim(0, self.xlim)
self.ax1.set_ylim(0, 100)
I just cant figure out why adding: Line2D([],[], color='blue', label='cpu') doesn't work. Am I just stupid right now?
here is the full code:
###################################################################
# #
# PLOT A LIVE GRAPH (PyQt5) #
# ----------------------------- #
# EMBED A MATPLOTLIB ANIMATION INSIDE YOUR #
# OWN GUI! #
# #
###################################################################
import sys
import os
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import functools
import numpy as np
import random as rd
import matplotlib
matplotlib.use("Qt5Agg")
from matplotlib.figure import Figure
from matplotlib.animation import TimedAnimation
from matplotlib.lines import Line2D
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import time
import threading
import subprocess
import platform
from os import linesep
import psutil
class CustomMainWindow(QMainWindow):
def __init__(self):
super(CustomMainWindow, self).__init__()
# Define the geometry of the main window
self.setGeometry(300, 300, 800, 400)
self.setWindowTitle("my first window")
# Create FRAME_A
self.FRAME_A = QFrame(self)
self.FRAME_A.setStyleSheet("QWidget { background-color: %s }" % QColor(210,210,235,255).name())
self.LAYOUT_A = QGridLayout()
self.FRAME_A.setLayout(self.LAYOUT_A)
self.setCentralWidget(self.FRAME_A)
# Place the matplotlib figure
self.myFig = CustomFigCanvas()
self.LAYOUT_A.addWidget(self.myFig, *(0,1))
# Add the callbackfunc to ..
myDataLoop = threading.Thread(name = 'myDataLoop', target = dataSendLoop, daemon = True, args = (self.addData_callbackFunc,))
myDataLoop.start()
self.show()
return
def addData_callbackFunc(self, value):
# print("Add data: " + str(value))
self.myFig.addData(value)
return
''' End Class '''
class CustomFigCanvas(FigureCanvas, TimedAnimation):
def __init__(self):
self.addedData = []
print(matplotlib.__version__)
# The data
self.xlim = 200
self.n = np.linspace(0, self.xlim - 1, self.xlim)
a = []
b = []
a.append(2.0)
a.append(4.0)
a.append(2.0)
b.append(4.0)
b.append(3.0)
b.append(4.0)
self.y = (self.n * 0.0) + 50
# The window
self.fig = Figure(figsize=(5,5), dpi=100)
self.ax1 = self.fig.add_subplot(111)
# self.ax1 settings
self.ax1.set_xlabel('time in 1/10 of a second')
self.ax1.set_ylabel('usage in %')
self.line1 = Line2D([], [], color='blue')
self.line1_tail = Line2D([], [], color='blue', linewidth=2)
self.line1_head = Line2D([], [], color='red', marker='o', markeredgecolor='r')
self.ax1.add_line(self.line1)
self.ax1.add_line(self.line1_tail)
self.ax1.add_line(self.line1_head)
self.ax1.set_xlim(0, self.xlim)
self.ax1.set_ylim(0, 100)
FigureCanvas.__init__(self, self.fig)
TimedAnimation.__init__(self, self.fig, interval = 50, blit = True)
return
def new_frame_seq(self):
return iter(range(self.n.size))
def _init_draw(self):
lines = [self.line1, self.line1_tail, self.line1_head]
for l in lines:
l.set_data([], [])
return
def addData(self, value):
self.addedData.append(value)
return
def zoomIn(self, value):
bottom = self.ax1.get_ylim()[0]
top = self.ax1.get_ylim()[1]
bottom += value
top -= value
self.ax1.set_ylim(bottom,top)
self.draw()
return
def _step(self, *args):
# Extends the _step() method for the TimedAnimation class.
try:
TimedAnimation._step(self, *args)
except Exception as e:
self.abc += 1
print(str(self.abc))
TimedAnimation._stop(self)
pass
return
def _draw_frame(self, framedata):
margin = 2
while(len(self.addedData) > 0):
self.y = np.roll(self.y, -1)
self.y[-1] = self.addedData[0]
del(self.addedData[0])
self.line1.set_data(self.n[ 0 : self.n.size - margin ], self.y[ 0 : self.n.size - margin ])
self.line1_tail.set_data(np.append(self.n[-10:-1 - margin], self.n[-1 - margin]), np.append(self.y[-10:-1 - margin], self.y[-1 - margin]))
self.line1_head.set_data(self.n[-1 - margin], self.y[-1 - margin])
self._drawn_artists = [self.line1, self.line1_tail, self.line1_head]
return
''' End Class '''
# You need to setup a signal slot mechanism, to
# send data to your GUI in a thread-safe way.
# Believe me, if you don't do this right, things
# go very very wrong..
class Communicate(QObject):
data_signal = pyqtSignal(float)
''' End Class '''
def dataSendLoop(addData_callbackFunc):
# Setup the signal-slot mechanism.
mySrc = Communicate()
mySrc.data_signal.connect(addData_callbackFunc)
# Simulate some data
n = np.linspace(0, 499, 500)
y = 50 + 25*(np.sin(n / 8.3)) + 10*(np.sin(n / 7.5)) - 5*(np.sin(n / 1.5))
i = 0
while(True):
if(i > 499):
i = 0
time.sleep(0.1)
#mySrc.data_signal.emit(y[i]) # <- Here you emit a signal!
mySrc.data_signal.emit(psutil.cpu_percent())
i += 1
###
###
if __name__== '__main__':
app = QApplication(sys.argv)
QApplication.setStyle(QStyleFactory.create('Plastique'))
myGUI = CustomMainWindow()
sys.exit(app.exec_())

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

Clear a matplotlib plot in PyQt

My code opens a PyQt window with a matplotlib plot inside of it. At the top there are 10 buttons, each with their own data values to plot. I want to know how can I clear the previous plot that is currently showing and then plot the new one when I click on the buttons.
I use PyQt4 and Python 3.
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import scipy.io
import sys
import os
from matplotlib.backends.backend_qt4agg import (FigureCanvasQTAgg as FigureCanvas)
from matplotlib.figure import Figure
from PyQt4.QtGui import *
from PyQt4 import QtCore
plt.style.use('ggplot')
def Extract_Data(name):
#setting up lists
Stim_trig = []
Stim_order = []
Sch_wav = []
data = scipy.io.loadmat(name)
for k,v in data.items():
#Sends Sch_wav data to a list
if "Sch_wav" in k:
for d in (((v[0])[0])[4]):
Sch_wav.append(d[0])
#Sends StimTrig to a list
if k=="StimTrig":
for g in (((v[0])[0])[4]):
Stim_trig.append(g[0])
Stim_trig.append(Stim_trig[-1]+1)
#Sends Stim order to a list
for w in (((v[0])[0])[5]):
Stim_order.append(w[0])
superdata = []
#Prepares grouping stimuli and trigger
for i in range(len(Stim_trig)-1):
fire = []
for p in Sch_wav:
if p > Stim_trig[i] and p < Stim_trig[i+1]:
fire.append(p - Stim_trig[i])
superdata.append([Stim_order[i],fire])
#sorts all the data
superdata.sort()
alladdedup = [[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[62]]
count = 0
for d in superdata:
if d[0] == (alladdedup[count])[0]:
for j in d[1]:
((alladdedup)[count]).append(j)
else:
count += 1
#places time stamps of triggers in lists for each trigger
for l in alladdedup:
l.pop(0)
l.sort()
#removes title and sorts data
ffmsb = []
#finds number of firings for each milisecond bin
for v in alladdedup:
fmsb = []
for b in range(1000):
msbc = b/1000
msb = []
for t in v:
if t > msbc and t < msbc + 0.001:
msb.append(t)
fmsb.append(len(msb))
ffmsb.append(fmsb)
#returns list of stimuli firings per milisecond bin
return(ffmsb)
class DisplayWidget(QMainWindow):
def __init__(self, parent=None):
super(DisplayWidget, self).__init__( parent )
self.initUI()
def initUI(self):
self.main_frame = QWidget()
self.canvas = PlotCanvas(self)
self.canvas.setParent(self.main_frame)
self.one = QPushButton('Intensity 1')
self.two = QPushButton('Intensity 2')
self.three = QPushButton('Intensity 3')
self.four = QPushButton('Intensity 4')
self.five = QPushButton('Intensity 5')
self.six = QPushButton('Intensity 6')
self.seven = QPushButton('Intensity 7')
self.eight = QPushButton('Intensity 8')
self.nine = QPushButton('Intensity 9')
self.ten = QPushButton('Intensity 10')
self.one.connect(self.one, QtCore.SIGNAL('clicked()'), self.setIntensity_one)
self.two.connect(self.two, QtCore.SIGNAL('clicked()'), self.setIntensity_two)
self.three.connect(self.three, QtCore.SIGNAL('clicked()'), self.setIntensity_three)
self.four.connect(self.four, QtCore.SIGNAL('clicked()'), self.setIntensity_four)
self.five.connect(self.five, QtCore.SIGNAL('clicked()'), self.setIntensity_five)
self.six.connect(self.six, QtCore.SIGNAL('clicked()'), self.setIntensity_six)
self.seven.connect(self.seven, QtCore.SIGNAL('clicked()'), self.setIntensity_seven)
self.eight.connect(self.eight, QtCore.SIGNAL('clicked()'), self.setIntensity_eight)
self.nine.connect(self.nine, QtCore.SIGNAL('clicked()'), self.setIntensity_nine)
self.ten.connect(self.ten, QtCore.SIGNAL('clicked()'), self.setIntensity_ten)
grid = QGridLayout()
grid.addWidget(self.canvas, 1, 0, 10, 10) # the matplotlib canvas
grid.addWidget(self.one, 0, 0)
grid.addWidget(self.two, 0, 1)
grid.addWidget(self.three, 0, 2)
grid.addWidget(self.four, 0, 3)
grid.addWidget(self.five, 0, 4)
grid.addWidget(self.six, 0, 5)
grid.addWidget(self.seven, 0, 6)
grid.addWidget(self.eight, 0, 7)
grid.addWidget(self.nine, 0, 8)
grid.addWidget(self.ten, 0, 9)
self.main_frame.setLayout(grid)
self.setCentralWidget(self.main_frame)
self.setWindowTitle('Neurons')
self.showMaximized()
def setIntensity_one(self):
self.data(intensity = 1)
def setIntensity_two(self):
self.data(intensity = 2)
def setIntensity_three(self):
self.data(intensity = 3)
def setIntensity_four(self):
self.data(intensity = 4)
def setIntensity_five(self):
self.data(intensity = 5)
def setIntensity_six(self):
self.data(intensity = 6)
def setIntensity_seven(self):
self.data(intensity = 7)
def setIntensity_eight(self):
self.data(intensity = 8)
def setIntensity_nine(self):
self.data(intensity = 9)
def setIntensity_ten(self):
self.data(intensity = 10)
def data(self, intensity):
stimuli = (Extract_Data("654508_rec02_all.mat")[intensity])
numberlist = []
for i in range(1000):
numberlist.append(i/1000)
d = pd.Series(stimuli, index = numberlist)
self.df = pd.DataFrame(d)
self.canvas.plot_data_frame(self.df)
class PlotCanvas(FigureCanvas, DisplayWidget):
def __init__(self, parent = None, width = 12, height = 9):
self.fig = Figure(figsize = (width, height))
self.axes = self.fig.add_subplot(111)
FigureCanvas.__init__(self, self.fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
def plot_data_frame(self, df):
df.plot( ax = self.axes,
kind = 'line',
title = 'Number of neurons firing',
legend = False,
xlim = (0, 1))
self.draw()
if __name__ == "__main__":
app = QApplication( [] )
widget = DisplayWidget()
widget.show()
app.exec_()
Just call self.axes.cla() before each plot at your plot_data_frame function. This will clear the axes.

wxpython:Dynamic plot - can't show second line in plot

I found this very good article:
http://eli.thegreenplace.net/2008/08/01/matplotlib-with-wxpython-guis/
I have successfully edited and put it in my code and works fine,but my problem is that I can't show multiple lines of data in the plot, I want to show up to 10 lines in my graph but I'm getting a lot of errors when I try to add them.
Here I'm giving you the eli's sample code but with my changes. In this code I try to show only 2 lines, I don't know why I'm geting this errors.
(RuntimeError: xdata and ydata must be the same length)
import os
import pprint
import random
import sys
import wx
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import \
FigureCanvasWxAgg as FigCanvas, \
NavigationToolbar2WxAgg as NavigationToolbar
import numpy as np
import pylab
class DataGen(object):
def __init__(self, init = 50):
self.data = self.init = init
self.data2 = self.init = init
def next(self):
self._recalc_data()
return self.data , self.data2,
def _recalc_data(self):
delta = random.uniform(-0.5, 0.5)
r = random.random()
if r > 0.9:
self.data += delta * 15
self.data2 += delta * 15
elif r > 0.8:
# attraction to the initial value
delta += (0.5 if self.init > self.data else -0.5)
self.data += delta
delta += (0.5 if self.init > self.data2 else -0.5)
self.data2 += delta
else:
self.data += delta
self.data2 += delta
class BoundControlBox(wx.Panel):
def __init__(self, parent, ID, label, initval):
wx.Panel.__init__(self, parent, ID)
self.value = initval
box = wx.StaticBox(self, -1, label)
sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
self.radio_auto = wx.RadioButton(self, -1,
label="Auto", style=wx.RB_GROUP)
self.radio_manual = wx.RadioButton(self, -1,
label="Manual")
self.manual_text = wx.TextCtrl(self, -1,
size=(35,-1),
value=str(initval),
style=wx.TE_PROCESS_ENTER)
self.Bind(wx.EVT_UPDATE_UI, self.on_update_manual_text, self.manual_text)
self.Bind(wx.EVT_TEXT_ENTER, self.on_text_enter, self.manual_text)
manual_box = wx.BoxSizer(wx.HORIZONTAL)
manual_box.Add(self.radio_manual, flag=wx.ALIGN_CENTER_VERTICAL)
manual_box.Add(self.manual_text, flag=wx.ALIGN_CENTER_VERTICAL)
sizer.Add(self.radio_auto, 0, wx.ALL, 10)
sizer.Add(manual_box, 0, wx.ALL, 10)
self.SetSizer(sizer)
sizer.Fit(self)
def on_update_manual_text(self, event):
self.manual_text.Enable(self.radio_manual.GetValue())
def on_text_enter(self, event):
self.value = self.manual_text.GetValue()
def is_auto(self):
return self.radio_auto.GetValue()
def manual_value(self):
return self.value
class GraphFrame(wx.Frame):
title = 'Monitor'
def __init__(self):
wx.Frame.__init__(self, None, -1, self.title)
self.datagen = DataGen()
self.data = [self.datagen.next()]
self.data2 = [self.datagen.next()]
self.paused = False
self.create_menu()
self.create_status_bar()
self.create_main_panel()
self.redraw_timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.on_redraw_timer, self.redraw_timer)
self.redraw_timer.Start(100)
def create_menu(self):
self.menubar = wx.MenuBar()
menu_file = wx.Menu()
m_expt = menu_file.Append(-1, "&Save plot\tCtrl-S", "Save plot to file")
self.Bind(wx.EVT_MENU, self.on_save_plot, m_expt)
menu_file.AppendSeparator()
m_exit = menu_file.Append(-1, "E&xit\tCtrl-X", "Exit")
self.Bind(wx.EVT_MENU, self.on_exit, m_exit)
self.menubar.Append(menu_file, "&File")
self.SetMenuBar(self.menubar)
def create_main_panel(self):
self.panel = wx.Panel(self)
self.init_plot()
self.canvas = FigCanvas(self.panel, -1, self.fig)
self.xmin_control = BoundControlBox(self.panel, -1, "X min", 0)
self.xmax_control = BoundControlBox(self.panel, -1, "X max", 50)
self.ymin_control = BoundControlBox(self.panel, -1, "Y min", 0)
self.ymax_control = BoundControlBox(self.panel, -1, "Y max", 100)
self.pause_button = wx.Button(self.panel, -1, "Pause")
self.Bind(wx.EVT_BUTTON, self.on_pause_button, self.pause_button)
self.Bind(wx.EVT_UPDATE_UI, self.on_update_pause_button, self.pause_button)
self.cb_grid = wx.CheckBox(self.panel, -1,
"Show Grid",
style=wx.ALIGN_RIGHT)
self.Bind(wx.EVT_CHECKBOX, self.on_cb_grid, self.cb_grid)
self.cb_grid.SetValue(True)
self.cb_xlab = wx.CheckBox(self.panel, -1,
"Show X labels",
style=wx.ALIGN_RIGHT)
self.Bind(wx.EVT_CHECKBOX, self.on_cb_xlab, self.cb_xlab)
self.cb_xlab.SetValue(True)
self.hbox1 = wx.BoxSizer(wx.HORIZONTAL)
self.hbox1.Add(self.pause_button, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
self.hbox1.AddSpacer(20)
self.hbox1.Add(self.cb_grid, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
self.hbox1.AddSpacer(10)
self.hbox1.Add(self.cb_xlab, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
self.hbox2 = wx.BoxSizer(wx.HORIZONTAL)
self.hbox2.Add(self.xmin_control, border=5, flag=wx.ALL)
self.hbox2.Add(self.xmax_control, border=5, flag=wx.ALL)
self.hbox2.AddSpacer(24)
self.hbox2.Add(self.ymin_control, border=5, flag=wx.ALL)
self.hbox2.Add(self.ymax_control, border=5, flag=wx.ALL)
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.vbox.Add(self.canvas, 1, flag=wx.LEFT | wx.TOP | wx.GROW)
self.vbox.Add(self.hbox1, 0, flag=wx.ALIGN_LEFT | wx.TOP)
self.vbox.Add(self.hbox2, 0, flag=wx.ALIGN_LEFT | wx.TOP)
self.panel.SetSizer(self.vbox)
self.vbox.Fit(self)
def create_status_bar(self):
self.statusbar = self.CreateStatusBar()
def init_plot(self):
self.dpi = 100
self.fig = Figure((3.0, 3.0), dpi=self.dpi)
self.axes = self.fig.add_subplot(111)
self.axes.set_axis_bgcolor('black')
self.axes.set_title('Random Data', size=12)
pylab.setp(self.axes.get_xticklabels(), fontsize=8)
pylab.setp(self.axes.get_yticklabels(), fontsize=8)
self.plot_data = self.axes.plot(self.data, linewidth=1,color = 'red',)[0]
self.plot_data2 = self.axes.plot(self.data2, linewidth=1,color = 'white',)[0]
self.axes.legend( (self.data, self.data2),
(u'Data 1', u'Data 2',),
'upper right', fontsize=6)
def draw_plot(self):
""" Redraws the plot
"""
# when xmin is on auto, it "follows" xmax to produce a
# sliding window effect. therefore, xmin is assigned after
# xmax.
#
if self.xmax_control.is_auto():
xmax = len(self.data) if len(self.data) > 50 else 50
else:
xmax = int(self.xmax_control.manual_value())
if self.xmin_control.is_auto():
xmin = xmax - 50
else:
xmin = int(self.xmin_control.manual_value())
# for ymin and ymax, find the minimal and maximal values
# in the data set and add a mininal margin.
#
# note that it's easy to change this scheme to the
# minimal/maximal value in the current display, and not
# the whole data set.
#
if self.ymin_control.is_auto():
#ymin = round(min(self.data), 0) - 1
ymin = 0
else:
ymin = int(self.ymin_control.manual_value())
if self.ymax_control.is_auto():
#ymax = round(max(self.data), 0) + 1
ymax = 100
else:
ymax = int(self.ymax_control.manual_value())
self.axes.set_xbound(lower=xmin, upper=xmax)
self.axes.set_ybound(lower=ymin, upper=ymax)
#
if self.cb_grid.IsChecked():
self.axes.grid(True, color='gray')
else:
self.axes.grid(False)
# Using setp here is convenient, because get_xticklabels
# returns a list over which one needs to explicitly
# iterate, and setp already handles this.
#
pylab.setp(self.axes.get_xticklabels(),
visible=self.cb_xlab.IsChecked())
pylab.setp(self.axes.get_legend(),
visible=self.cb_xlab.IsChecked())
self.plot_data.set_xdata(np.arange(len(self.data)))
self.plot_data.set_ydata(np.array(self.data))
self.plot_data.set_xdata(np.arange(len(self.data)))
self.plot_data.set_ydata(np.array(self.data2))
self.canvas.draw()
def on_pause_button(self, event):
self.paused = not self.paused
def on_update_pause_button(self, event):
label = "Resume" if self.paused else "Pause"
self.pause_button.SetLabel(label)
def on_cb_grid(self, event):
self.draw_plot()
def on_cb_xlab(self, event):
self.draw_plot()
def on_save_plot(self, event):
file_choices = "PNG (*.png)|*.png"
dlg = wx.FileDialog(
self,
message="Save plot as...",
defaultDir=os.getcwd(),
defaultFile="plot.png",
wildcard=file_choices,
style=wx.SAVE)
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
self.canvas.print_figure(path, dpi=self.dpi)
self.flash_status_message("Saved to %s" % path)
def on_redraw_timer(self, event):
#
if not self.paused:
self.data.append(self.datagen.next())
self.data2.append(self.datagen.next())
self.draw_plot()
def on_exit(self, event):
self.Destroy()
def flash_status_message(self, msg, flash_len_ms=1500):
self.statusbar.SetStatusText(msg)
self.timeroff = wx.Timer(self)
self.Bind(
wx.EVT_TIMER,
self.on_flash_status_off,
self.timeroff)
self.timeroff.Start(flash_len_ms, oneShot=True)
def on_flash_status_off(self, event):
self.statusbar.SetStatusText('')
if __name__ == '__main__':
app = wx.PySimpleApp()
app.frame = GraphFrame()
app.frame.Show()
app.MainLoop()
If I delete my changes the code works fine!
Traceback (most recent call last):
File "C:\..\..\..\Copy.py", line 285, in on_redraw_timer
self.draw_plot()
File "C:\..\..\..\Copy.py", line 246, in draw_plot
self.canvas.draw()
File "C:\Python27\lib\site-packages\matplotlib\backends\backend_wxagg.py", line 44, in draw
FigureCanvasAgg.draw(self)
File "C:\Python27\lib\site-packages\matplotlib\backends\backend_agg.py", line 451, in draw
self.figure.draw(self.renderer)
File "C:\Python27\lib\site-packages\matplotlib\artist.py", line 55, in draw_wrapper
draw(artist, renderer, *args, **kwargs)
File "C:\Python27\lib\site-packages\matplotlib\figure.py", line 1034, in draw
func(*args)
File "C:\Python27\lib\site-packages\matplotlib\artist.py", line 55, in draw_wrapper
draw(artist, renderer, *args, **kwargs)
File "C:\Python27\lib\site-packages\matplotlib\axes.py", line 2086, in draw
a.draw(renderer)
File "C:\Python27\lib\site-packages\matplotlib\artist.py", line 55, in draw_wrapper
draw(artist, renderer, *args, **kwargs)
File "C:\Python27\lib\site-packages\matplotlib\lines.py", line 512, in draw
self.recache()
File "C:\Python27\lib\site-packages\matplotlib\lines.py", line 440, in recache
raise RuntimeError('xdata and ydata must be the same length')
RuntimeError: xdata and ydata must be the same length

wxPython - graphing data

I have an issue with graphing data within wxPython - this code below works but it's not exactly right: For now I plot random numbers which are multiples of an entry box, this is done every 100ms.
My issue is that the entire history of numbers is shown as a pose is a (say) running window of 25 samples. I initially tried redrawing the graph on each collection of 100 samples, something like, if( length(data)%100 ): drawGraph but again this didn't look correct.
Thoughts and suggestions welcome.
My code:
print( "\n- Please Wait -- Importing Matplotlib and Related Modules...\n" )
import random
import matplotlib
import numpy
import wx
import u3
import numpy as np
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wx import NavigationToolbar2Wx
from matplotlib.figure import Figure
class TemperaturePanel( wx.Panel ) :
def __init__( self, parent, position ) :
wx.Panel.__init__( self, parent, pos=position, size=(800,320) )
# initialize matplotlib
self.figure = matplotlib.figure.Figure( None, facecolor="white" )
self.canvas = matplotlib.backends.backend_wxagg.FigureCanvasWxAgg( self, -1, self.figure )
self.axes = self.figure.add_subplot(111)
self.axes.grid(True, color="gray")
self.axes.set_xbound( (0,5) )
self.axes.set_ybound( (3,80) )
self.axes.set_xlabel( "Minutes" )
self.axes.set_ylabel( "Temperature ($^\circ$C)" )
self.axes = self.figure.add_subplot(111)
self.axes.grid(True, color="gray")
self._SetSize()
self.Bind( wx.EVT_SIZE, self._SetSize )
self.TemperatureData = []
def updateTemperature(self, value):
self.TemperatureData.append( value )
length = len(self.TemperatureData)
x = np.arange( length )
y = np.array(self.TemperatureData)
yMin = round(min(y)) - 2
yMax = round(max(y)) + 2
self.axes.plot(x,y, "-k")
self.axes.set_ybound( (yMin,yMax) )
self.canvas = FigureCanvas(self, -1, self.figure)
#-----------------------------------------------------------------------------------
def _SetSize( self, event=None ):
pixels = self.GetSize()
self.SetSize( pixels )
self.canvas.SetSize( pixels )
dpi = self.figure.get_dpi()
self.figure.set_size_inches( float( pixels[0] ) / dpi,float( pixels[1] ) / dpi )
#------------------------------------------------------------------------------------
class MainWindow(wx.Frame):
def __init__(self, parent):
#wx.Frame.__init__(self, *args, **kwargs)
wx.Frame.__init__(self, parent, title="Graph Issue", size=(1000,600))
self.panel = wx.Panel(self)
self.spin = wx.SpinCtrl(self.panel)
self.button = wx.Button(self.panel, label="Update")
self.stop = wx.Button(self.panel, label="Stop")
self.sizer = wx.BoxSizer()
self.sizer.Add(self.spin)
self.sizer.Add(self.button)
self.sizer.Add(self.stop)
self.TemperatureGraph = TemperaturePanel( self, position=(20, 50) )
self.panel.SetSizerAndFit(self.sizer)
self.Show()
# Use EVT_CHAR_HOOK on Frame insted of wx.EVT_KEY_UP on SpinCtrl
# to disable "on Enter go to next widget" functionality
self.Bind(wx.EVT_CHAR_HOOK, self.OnKey)
self.button.Bind(wx.EVT_BUTTON, self.OnUpdate)
self.stop.Bind(wx.EVT_BUTTON, self.OnStop)
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
self.Bind(wx.EVT_TIMER, self.updateTemperature, self.timer)
self.timer.Start(100)
self.value = 0
def OnKey(self, e):
if e.GetKeyCode() == wx.WXK_RETURN: # Is the key ENTER?
self.value = self.spin.GetValue() # Read SpinCtrl and set internal value
else: # Else let the event out of the handler
e.Skip()
def OnUpdate(self, e):
self.value = self.spin.GetValue() # Read SpinCtrl and set internal value
def OnTimer(self, e):
# Show internal value
print(self.value)
def updateTemperature(self, e):
Temperature = self.value*random.uniform(-1,1) # obtain currnt temperature
self.TemperatureGraph.updateTemperature(Temperature) # add temperature to graph
def OnStop(self, e):
self.timer.Stop()
self.Destroy()
app = wx.App(False)
win = MainWindow(None)
app.MainLoop()
If I understood the question correctly, you need 25 last temperature values only to be shown in your graph instead of all history of values. If that's what you want, then in the updateTemperature subroutine only the last 25 values should be plotted:
if length < 25:
x = np.arange(length)
y = np.array(self.TemperatureData)
else:
x = np.arange(length-25, length)
y = np.array(self.TemperatureData)[-25:]
To make the plot look better, x axis can be adjusted the same way you do it with y axis:
xMin = 0 if length < 25 else length-25
xMax = 25 if length < 25 else length
self.axes.set_xbound( (xMin,xMax) )
If the plot looks OK to you, and the issue is about the memory leak that causes the graph to freeze after ~200 iterations, that's due to the creation of FigureCanvas on every temperature update. Instead, you can re-use your existing FigureCanvas, changing the last line of updateTemperature to
self.canvas.draw()

Categories

Resources