Two Y-scales in pyqtgraph (twinx-like) - python

How can I generate a plot with two Y-scales in pyqtgraph?
I also need the two in different colors (corresponding to lines' colors).
In matplotlib it can be done using twinx, as in this example.
If there's no way to do it with a single plot object, perhaps there's a way to overlay a plot (with y-axis on right side) on another one (with the y-axis on left)?

See pyqtgraph/examples/MultiplePlotAxes.py.
The solution is just what you described--overlay two PlotItems.

Here is some code that I think shows a practical example of what it is you are after. This is an expansion of two pyqtgraph examples: PlotSpeedTest.py and MultiplePlotAxes.py.
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
pg.setConfigOptions(antialias=True)
pg.setConfigOption('background', '#c7c7c7')
pg.setConfigOption('foreground', '#000000')
from pyqtgraph.ptime import time
app = QtGui.QApplication([])
p = pg.plot()
p.setXRange(0,10)
p.setYRange(-10,10)
p.setWindowTitle('Current-Voltage')
p.setLabel('bottom', 'Bias', units='V', **{'font-size':'20pt'})
p.getAxis('bottom').setPen(pg.mkPen(color='#000000', width=3))
p.setLabel('left', 'Current', units='A',
color='#c4380d', **{'font-size':'20pt'})
p.getAxis('left').setPen(pg.mkPen(color='#c4380d', width=3))
curve = p.plot(x=[], y=[], pen=pg.mkPen(color='#c4380d'))
p.showAxis('right')
p.setLabel('right', 'Dynamic Resistance', units="<font>Ω</font>",
color='#025b94', **{'font-size':'20pt'})
p.getAxis('right').setPen(pg.mkPen(color='#025b94', width=3))
p2 = pg.ViewBox()
p.scene().addItem(p2)
p.getAxis('right').linkToView(p2)
p2.setXLink(p)
p2.setYRange(-10,10)
curve2 = pg.PlotCurveItem(pen=pg.mkPen(color='#025b94', width=1))
p2.addItem(curve2)
def updateViews():
global p2
p2.setGeometry(p.getViewBox().sceneBoundingRect())
p2.linkedViewChanged(p.getViewBox(), p2.XAxis)
updateViews()
p.getViewBox().sigResized.connect(updateViews)
x = np.arange(0, 10.01,0.01)
data = 5+np.sin(30*x)
data2 = -5+np.cos(30*x)
ptr = 0
lastTime = time()
fps = None
def update():
global p, x, curve, data, curve2, data2, ptr, lastTime, fps
if ptr < len(x):
curve.setData(x=x[:ptr], y=data[:ptr])
curve2.setData(x=x[:ptr], y=data2[:ptr])
ptr += 1
now = time()
dt = now - lastTime
lastTime = now
if fps is None:
fps = 1.0/dt
else:
s = np.clip(dt*3., 0, 1)
fps = fps * (1-s) + (1.0/dt) * s
p.setTitle('%0.2f fps' % fps)
else:
ptr = 0
app.processEvents() ## force complete redraw for every plot. Try commenting out to see if a different in speed occurs.
timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(0)
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()

Related

Animating a real time breathing curve in python

I want to create an animation that people can use to align their breathing with. I have made a class with PyQt5 that does exactly this, and has the breathing period as parameter. (See code below).
It works well, apart from the timing. When setting a specific delta_t and window size during the FuncAnimation I can get accurate timings. But when I change the window size, it either speeds up or slows down...
Im probably going to model this in another language, but I am still curious if I can get this right in Python. Can anyone here point me in the right direction?
import os
import time
import sys
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import PyQt5.QtGui
import PyQt5.QtCore
import PyQt5.QtWidgets
class BreathingAnimation(PyQt5.QtWidgets.QMainWindow):
# Could inherit from GenericInterface
# Animate three ways:
# o Fixed sinusoid -> moving ball
# o Fixed ball (up/down) -> moving wave
# o Expanding ball
def __init__(self, period=1, delta_t=0.01, animation_type='ball'):
super().__init__()
self._main = PyQt5.QtWidgets.QWidget()
self.setCentralWidget(self._main)
"""
Plot variables
sin(omega * x)
period = 2 * np.pi * omega
omega = period / (2 * np.pi)
"""
self.delta_t = delta_t
self.frequentie = 1/period
self.max_plot_time = 5
self.max_periods = 20
self.t_range = np.arange(0, self.max_periods * period, delta_t)
self.animation_type = animation_type
self.cool_down = 10
self.prev_time = time.time()
self.prev_time_cor_time = time.time()
"""
Graphical definitions
"""
self.canvas, self.fig, self.axes = self.get_figure(self.max_plot_time)
self.axes.set_axis_off()
self.line_obj, self.scatter_obj = self.get_plot_objects(self.axes)
# Get the animation object
self.anim_obj = self.get_animation()
h_layout = PyQt5.QtWidgets.QHBoxLayout(self._main)
# Create buttons
# self.button_box_layout = self.get_button_box_layout()
# write_button = self.get_push_button(name='Write', shortcut='W', connect=self.write_animation)
# self.edit_button = self.get_line_edit(name=f'{period}', connect=self.update_period, max_length=4)
# self.button_box_layout.addWidget(self.edit_button)
# self.button_box_layout.addWidget(write_button)
# Add canvas to the figure
temp_canvas_layout = PyQt5.QtWidgets.QVBoxLayout()
temp_canvas_layout.addWidget(self.canvas)
h_layout.addLayout(temp_canvas_layout, stretch=1)
# h_layout.addLayout(self.button_box_layout, stretch=0.01)
#staticmethod
def get_figure(max_plot_time, debug=False):
if debug:
fig = plt.figure()
else:
fig = Figure(figsize=(5, 5), dpi=100)
canvas = FigureCanvas(fig)
axes = canvas.figure.subplots()
# self.axes.set_axis_off()
axes.set_ylim(-2, 2)
axes.set_xlim(0, max_plot_time)
return canvas, fig, axes
#staticmethod
def get_plot_objects(axes):
# Create a line object
line_obj = axes.plot([], [], zorder=1)[0]
# Create a scatter object
scatter_obj = axes.scatter([], [], s=40, marker='o', c='r')
return line_obj, scatter_obj
def get_y_value(self, i_t):
omega = 2 * np.pi * self.frequentie
y_value = np.sin(omega * i_t)
return y_value
def animate_moving_ball(self, i, line_obj=None, scatter_obj=None):
i = i % len(self.t_range)
if line_obj is None:
line_obj = self.line_obj
if scatter_obj is None:
scatter_obj = self.scatter_obj
line_obj.set_data(self.t_range, self.get_y_value(self.t_range))
sel_time = self.t_range[i]
scatter_obj.set_offsets(np.c_[sel_time, self.get_y_value(sel_time)])
return scatter_obj
def animate_moving_wave(self, i, line_obj=None, scatter_obj=None):
i = i % len(self.t_range)
if line_obj is None:
line_obj = self.line_obj
if scatter_obj is None:
scatter_obj = self.scatter_obj
line_obj.set_data(self.t_range, np.roll(self.get_y_value(self.t_range), -i))
sel_time = self.t_range[i] + self.max_plot_time/2.
# print(f'max plot time {i}', self.max_plot_time/2, self.get_y_value(sel_time))
scatter_obj.set_offsets(np.c_[self.max_plot_time/2., self.get_y_value(sel_time)])
# self.cool_down -= 1
# # print(self.cool_down)
if ((1 - self.get_y_value(sel_time)) < 0.0001):
time_difference = time.time() - self.prev_time
self.prev_time = time.time()
print('Time interval in seconds ', time_difference)
return scatter_obj
def update_period(self):
new_periode = float(self.edit_button.text())
self.frequentie = 1./new_periode
self.t_range = np.arange(0, self.max_periods * new_periode, self.delta_t)
def get_animation(self):
if self.animation_type == 'ball':
# Return a moving ball..
animation_fun = self.animate_moving_ball
elif self.animation_type == 'wave':
# Return a wave..
animation_fun = self.animate_moving_wave
else:
animation_fun = None
self.animation_obj = animation.FuncAnimation(self.canvas.figure, animation_fun,
blit=False, repeat=True,
interval=self.delta_t, # Delay in ms
frames=len(self.t_range))
self.animation_obj.new_frame_seq()
return self.animation_obj
def write_animation(self):
num_frames = len(self.t_range)
max_time = np.max(self.t_range) # in seconds?
print('frames ', num_frames / max_time)
ffmpeg_writer = animation.FFMpegWriter(fps=num_frames / max_time)
self.animation_obj.save(os.path.expanduser('~/breathing_animation.mp4'), writer=ffmpeg_writer)
print('Written')
if __name__ == "__main__":
qapp = PyQt5.QtWidgets.QApplication(sys.argv)
app = BreathingAnimation(period=3, animation_type='wave', delta_t=0.009)
app.show()
qapp.exec_()

UI plot position in pyqtgraph

The code below is a follow-up example of pyqtgraph.
What I have added is imported txt data and plotted a 2D graph. The problem I'm having is the plot 1 is generated separately. I have replaced one of the plots named p1 from the example. What is happening now the plot is generated separated from the UI. See the below output:
# -*- coding: utf-8 -*-
import initExample ## Add path to library (just for examples; you do not need this)
# -*- coding: utf-8 -*-
"""
This example demonstrates many of the 2D plotting capabilities
in pyqtgraph. All of the plots may be panned/scaled by dragging with
the left/right mouse buttons. Right click on any plot to show a context menu.
"""
import initExample ## Add path to library (just for examples; you do not need this)
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
import csv
#QtGui.QApplication.setGraphicsSystem('raster')
app = QtGui.QApplication([])
#mw = QtGui.QMainWindow()
#mw.resize(800,800)
win = pg.GraphicsLayoutWidget()
#win = pg.GraphicsLayoutWidget(show=True, title="Basic plotting examples")
win.resize(1000,600)
win.setWindowTitle('pyqtgraph example: Plotting')
win.show()
# Enable antialiasing for prettier plots
pg.setConfigOptions(antialias=True)
p1 = win.addPlot(title="Multiple curves")
x = []
y = []
with open('example.txt','r') as csvfile:
plots = csv.reader(csvfile, delimiter=',')
for row in plots:
x.append(int(row[0]))
y.append(int(row[1]))
arrX = np.array(x)
arrY = np.array(y)
yerr = np.sqrt(arrY)*0.1
p1 = pg.plot()
err=pg.ErrorBarItem(x=arrX, y=arrY, top=yerr, bottom=yerr,left=0, right=0, beam=0.0)
p1.addItem(err)
p1.plot(x, y, symbol='o', pen={'color': 0.8, 'width': 2})
p2 = win.addPlot(title="Multiple curves")
p2.plot(np.random.normal(size=100), pen=(255,0,0), name="Red curve")
p2.plot(np.random.normal(size=110)+5, pen=(0,255,0), name="Green curve")
p2.plot(np.random.normal(size=120)+10, pen=(0,0,255), name="Blue curve")
p3 = win.addPlot(title="Drawing with points")
p3.plot(np.random.normal(size=100), pen=(200,200,200), symbolBrush=(255,0,0), symbolPen='w')
win.nextRow()
p4 = win.addPlot(title="Parametric, grid enabled")
x = np.cos(np.linspace(0, 2*np.pi, 1000))
y = np.sin(np.linspace(0, 4*np.pi, 1000))
p4.plot(x, y)
p4.showGrid(x=True, y=True)
p5 = win.addPlot(title="Scatter plot, axis labels, log scale")
x = np.random.normal(size=1000) * 1e-5
y = x*1000 + 0.005 * np.random.normal(size=1000)
y -= y.min()-1.0
mask = x > 1e-15
x = x[mask]
y = y[mask]
p5.plot(x, y, pen=None, symbol='t', symbolPen=None, symbolSize=10, symbolBrush=(100, 100, 255, 50))
p5.setLabel('left', "Y Axis", units='A')
p5.setLabel('bottom', "Y Axis", units='s')
p5.setLogMode(x=True, y=False)
p6 = win.addPlot(title="Updating plot")
curve = p6.plot(pen='y')
data = np.random.normal(size=(100,1000))
ptr = 0
def update():
global curve, data, ptr, p6
curve.setData(data[ptr%10])
if ptr == 0:
p6.enableAutoRange('xy', False) ## stop auto-scaling after the first data set is plotted
ptr += 1
timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(50)
win.nextRow()
p7 = win.addPlot(title="Filled plot, axis disabled")
y = np.sin(np.linspace(0, 10, 1000)) + np.random.normal(size=1000, scale=0.1)
p7.plot(y, fillLevel=-0.3, brush=(50,50,200,100))
p7.showAxis('bottom', False)
x2 = np.linspace(-100, 100, 1000)
data2 = np.sin(x2) / x2
p8 = win.addPlot(title="Region Selection")
p8.plot(data2, pen=(255,255,255,200))
lr = pg.LinearRegionItem([400,700])
lr.setZValue(-10)
p8.addItem(lr)
p9 = win.addPlot(title="Zoom on selected region")
p9.plot(data2)
def updatePlot():
p9.setXRange(*lr.getRegion(), padding=0)
def updateRegion():
lr.setRegion(p9.getViewBox().viewRange()[0])
lr.sigRegionChanged.connect(updatePlot)
p9.sigXRangeChanged.connect(updateRegion)
updatePlot()
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
The txt file is: example.txt
1,5
2,3
3,4
4,7
5,4
6,3
7,5
8,7
9,4
10,4
The syntax is quite straightforward, add plot and add item to plot:
p1 = win.addPlot(title="Error Bar")
arrX = np.array([0,1,2,3,4])
arrY = np.array([0,1,2,3,4])
yerr = np.sqrt(arrY)*0.1
err = pg.ErrorBarItem(x=arrX, y=arrY, top=yerr, bottom=yerr,left=0, right=0, beam=0.0)
p1.addItem(err)

How to set pyqtgraph axis label offset?

I need to implement quite tiny pyqtgraph plots in a GUI. If doing so, by default the axis' label offset is too large. How can I set the offset of the axis label, not the axis ticks.
The following code example creates a basic pyqtgraph plot. I was able to set the offset of the tick text but not the offset of the label text only. I would like to only get the axis labels closer to the axis.
import numpy as np
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
app = QtGui.QApplication([])
x = np.linspace(0, 1, 10000)
y = np.linspace(350, 2500, 10000)
win = pg.GraphicsWindow()
plot = win.addPlot(x=x, y=y, title="Plot")
label_style = {'color': '#EEE', 'font-size': '14pt'}
plot.setLabel('bottom', "some x axis label", **label_style)
plot.setLabel('left', "some y axis label")
plot.getAxis('left').setLabel(**label_style)
font=QtGui.QFont()
font.setPixelSize(14)
plot.getAxis("bottom").tickFont = font
# Here I increased the tickTextOffset of the x axis
plot.getAxis("bottom").setStyle(tickTextOffset=50)
plot.getAxis("left").tickFont = font
plot.getAxis("left").setStyle(tickTextOffset=14)
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Any help is much appreciated!
Update:
I found a pyqtgraph internal solution in pyqtgraph.AxisItem.resizeEvent() but the function does not accept any passed arguments.
def resizeEvent(self, ev=None):
#s = self.size()
## Set the position of the label
nudge = 5
br = self.label.boundingRect()
p = QtCore.QPointF(0, 0)
if self.orientation == 'left':
p.setY(int(self.size().height()/2 + br.width()/2))
p.setX(-nudge)
elif self.orientation == 'right':
p.setY(int(self.size().height()/2 + br.width()/2))
p.setX(int(self.size().width()-br.height()+nudge))
elif self.orientation == 'top':
p.setY(-nudge)
p.setX(int(self.size().width()/2. - br.width()/2.))
elif self.orientation == 'bottom':
p.setX(int(self.size().width()/2. - br.width()/2.))
p.setY(int(self.size().height()-br.height()+nudge))
self.label.setPos(p)
self.picture = None
the corresponding variable is nudge. Unfortunately it is not accessible or is there a way to bypass resizeEvent() without changing the source code of pyqtgraph?
As a proposal to make nudge passable I created a pyqtgraph issue on
github:
https://github.com/pyqtgraph/pyqtgraph/issues/986
One solution is to create a Custom AxisItem and override that method. To call resizeEvent you can make false resizing:
import numpy as np
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
class CustomAxis(pg.AxisItem):
#property
def nudge(self):
if not hasattr(self, "_nudge"):
self._nudge = 5
return self._nudge
#nudge.setter
def nudge(self, nudge):
self._nudge = nudge
s = self.size()
# call resizeEvent indirectly
self.resize(s + QtCore.QSizeF(1, 1))
self.resize(s)
def resizeEvent(self, ev=None):
# s = self.size()
## Set the position of the label
nudge = self.nudge
br = self.label.boundingRect()
p = QtCore.QPointF(0, 0)
if self.orientation == "left":
p.setY(int(self.size().height() / 2 + br.width() / 2))
p.setX(-nudge)
elif self.orientation == "right":
p.setY(int(self.size().height() / 2 + br.width() / 2))
p.setX(int(self.size().width() - br.height() + nudge))
elif self.orientation == "top":
p.setY(-nudge)
p.setX(int(self.size().width() / 2.0 - br.width() / 2.0))
elif self.orientation == "bottom":
p.setX(int(self.size().width() / 2.0 - br.width() / 2.0))
p.setY(int(self.size().height() - br.height() + nudge))
self.label.setPos(p)
self.picture = None
app = QtGui.QApplication([])
x = np.linspace(0, 1, 10000)
y = np.linspace(350, 2500, 10000)
win = pg.GraphicsWindow()
plot = win.addPlot(
x=x, y=y, title="Plot", axisItems={"bottom": CustomAxis(orientation="bottom")}
)
label_style = {"color": "#EEE", "font-size": "14pt"}
plot.setLabel("bottom", "some x axis label", **label_style)
plot.setLabel("left", "some y axis label")
plot.getAxis("left").setLabel(**label_style)
font = QtGui.QFont()
font.setPixelSize(14)
plot.getAxis("bottom").tickFont = font
plot.getAxis("bottom").setStyle(tickTextOffset=50)
plot.getAxis("left").tickFont = font
plot.getAxis("left").setStyle(tickTextOffset=14)
def on_timeout():
plot.getAxis("bottom").nudge += 1
timer = QtCore.QTimer(timeout=on_timeout, interval=500)
timer.start()
if __name__ == "__main__":
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, "PYQT_VERSION"):
QtGui.QApplication.instance().exec_()

Animate pyqtgraph in class

I'm trying to write a program that gets serial data from an arduino, via serial, and plots it in real time. I wrote code using matplotlib but I want happy with the results so I am trying to get it to work on pyqtgraph (there are much fewer resources to learn how to use it). my problem is that the code shows an empty graph. it seems _update is being called just once, but when I put it in a loop the graph doesn't even show.
I've written some other code that does what I want, which is plot the data in real time and after the data passes a threshold it plots new lines over the data showing a linear regression. I got an example from here (https://github.com/JaFeKl/joystick_real_time_plot_with_pyqtgraph/blob/master/real_time_plot.py) because I wanted my code to be callable (in a function, but I can't get it to work. so far I'm generating data from within python to simplify debugging
import sys
import pyqtgraph as pg
import pyqtgraph.exporters
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import serial
# test
import math
import time
class Graph(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Graph, self).__init__(parent)
self.n = 3
self.mainbox = QtGui.QWidget()
self.setCentralWidget(self.mainbox)
self.mainbox.setLayout(QtGui.QVBoxLayout())
self.canvas = pg.GraphicsLayoutWidget() # create GrpahicsLayoutWidget obejct
self.mainbox.layout().addWidget(self.canvas)
# Set up plot
self.analogPlot = self.canvas.addPlot(title='Signal from serial port')
self.analogPlot.setYRange(-1,1123) # set axis range
self.analogPlot.setXRange(-1,1123)
self.analogPlot.showGrid(x=True, y=True, alpha=0.5) # show Grid
x_axis = self.analogPlot.getAxis('bottom')
y_axis = self.analogPlot.getAxis('left')
font=QtGui.QFont()
font.setPixelSize(20)
x_axis.tickFont = font
y_axis.tickFont = font
x_axis.setLabel(text='Tensão [V]') # set axis labels
y_axis.setLabel(text='Corrente [mA]')
self.plts = []
self.intplts = []
colors = ['r', 'b', 'w', 'y', 'g', 'm', 'c', 'k']
for i in range(self.n):
self.plts.append([])
self.intplts.append([])
for i in range(self.n):
if len(self.plts) <= len(colors):
self.plts[i]=(self.analogPlot.plot(pen= pg.mkPen(colors[i], width=6)))
for i in range(self.n):
if len(self.plts) <= len(colors)*2:
self.intplts.append(self.analogPlot.plot(pen= pg.mkPen(colors[i+3], width=3)))
#Data
self.datay = []
self.datax = []
for i in range(self.n):
self.datax.append([])
self.datay.append([])
# set up image exporter (necessary to be able to export images)
QtGui.QApplication.processEvents()
self.exporter=pg.exporters.ImageExporter(self.canvas.scene())
self.image_counter = 1
# start updating
self.t=0
self._update()
def _update(self):
time.sleep(0.01)
if self.t<= 30:
#line = raw.readline()
#data.append(int(line))
self.datay[0].append(math.sin(self.t+(math.pi/2)))
self.datay[1].append(math.sin(self.t+(5*math.pi/4)))
self.datay[2].append(math.sin(self.t))
self.datax[0].append(self.t)
self.datax[1].append(self.t)
self.datax[2].append(self.t)
self.t+=0.1
self.plts[0].setData(self.datax[0], self.datay[0])
self.plts[1].setData(self.datax[1], self.datay[1])
self.plts[2].setData(self.datax[2], self.datay[2])
app.processEvents()
elif self.t>=30 and self.t<=30.1 :
self.t+=1
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
plot = Graph()
plot.show()
sys.exit(app.exec_())
I expect results similar to this code( only without the linear regression)
import pyqtgraph as pg
import pyqtgraph.exporters
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
# linear regression
from scipy import stats
#Arduino
#import find_arduino
#import find_buad
import serial
import math
import time
#port = find_arduino.FindArduino()
#baud = find_buad.FindBaudRate()
ard=None
def Con():
global ard
ard = serial.Serial(port,baud,timeout=5)
time.sleep(2) # wait for Arduino
ard.close()
# define the data
theTitle = "pyqtgraph plot"
datay = [[],[],[]]
datax = [[],[],[]]
x2 = []
T=[]
t=0
y1L=[]
x1L=[]
# create plot
### START QtApp #####
app = QtGui.QApplication([]) # you MUST do this once (initialize things)
####################
win = pg.GraphicsWindow(title="Signal from serial port") # creates a window
plt = win.addPlot(title="Realtime plot") # creates empty space for the plot in the window
font=QtGui.QFont()
font.setPixelSize(20)
plt.getAxis("bottom").tickFont = font
plt.getAxis("left").tickFont = font
plt1 = plt.plot(pen=pg.mkPen('r', width=6))
plt2= plt.plot(pen=pg.mkPen('b', width=6))
plt3= plt.plot(pen=pg.mkPen('w', width=6))
plt1I = plt.plot(pen=pg.mkPen('y', width=3))
plt2I = plt.plot(pen=pg.mkPen('g', width=3))
plt3I = plt.plot(pen=pg.mkPen('m', width=3))
plt.showGrid(x=True,y=True)
def update():
global plt1,plt2,plt3, t, plt1I, plt2I, plt3I
if t<= 30:
#line = raw.readline()
#data.append(int(line))
datay[0].append(math.sin(t+(math.pi/2)))
datay[1].append(math.sin(t+(5*math.pi/4)))
datay[2].append(math.sin(t))
datax[0].append(t)
datax[1].append(t)
datax[2].append(t)
t+=0.1
plt1.setData(datax[0],datay[0])
plt2.setData(datax[1],datay[1])
plt3.setData(datax[2],datay[2])
app.processEvents()
time.sleep(0.01)
elif t>=30 and t<=30.1 :
#plt1I.setData([0,1,2],[5,3,1])
#app.processEvents()
interp(plt1I, plt2I, plt3I)
t+=1
else:
app.processEvents()
def interp(pt1, pt2, pt3):
slope, intercept, r_value, p_value, std_err = stats.linregress(datax[0][10:],datay[0][10:])
x=[]
y=[]
print(slope)
for i in datax[0][10:]:
x.append(i)
y.append(intercept+slope*i)
pt1.setData(x,y)
slope, intercept, r_value, p_value, std_err = stats.linregress(datax[1][10:],datay[1][10:])
x=[]
y=[]
print(slope)
for i in datax[0][10:]:
x.append(i)
y.append(intercept+slope*i)
pt2.setData(x, y)
slope, intercept, r_value, p_value, std_err = stats.linregress(datax[2][10:],datay[2][10:])
x=[]
y=[]
print(slope)
for i in datax[0][10:]:
x.append(i)
y.append(intercept+slope*i)
pt3.setData(x,y)
app.processEvents()
timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(0)
### MAIN PROGRAM #####
# this is a brutal infinite loop calling your realtime data plot
# make this interpret the incoming data
#Con()
#Communicate(1)
while True: update()
### END QtApp ####
pg.QtGui.QApplication.exec_() # you MUST put this at the end
##################
I don't have an Arduino hooked up to grab data from so for this example I used random data to plot. When plotting data, you want to avoid using time.sleep() since it causes the GUI to freeze. Instead, use a QtGui.QTimer() connected to an update handler to plot data. Also as an optimization, you can use a thread to poll data and then update it in a separate timer.
from pyqtgraph.Qt import QtCore, QtGui
from threading import Thread
import pyqtgraph as pg
import numpy as np
import random
import sys
import time
"""Scrolling Plot Widget Example"""
# Scrolling plot widget with adjustable X-axis and dynamic Y-axis
class ScrollingPlot(QtGui.QWidget):
def __init__(self, parent=None):
super(ScrollingPlot, self).__init__(parent)
# Desired Frequency (Hz) = 1 / self.FREQUENCY
# USE FOR TIME.SLEEP (s)
self.FREQUENCY = .004
# Frequency to update plot (ms)
# USE FOR TIMER.TIMER (ms)
self.TIMER_FREQUENCY = self.FREQUENCY * 1000
# Set X Axis range. If desired is [-10,0] then set LEFT_X = -10 and RIGHT_X = 0
self.LEFT_X = -10
self.RIGHT_X = 0
self.X_Axis = np.arange(self.LEFT_X, self.RIGHT_X, self.FREQUENCY)
self.buffer = int((abs(self.LEFT_X) + abs(self.RIGHT_X))/self.FREQUENCY)
self.data = []
# Create Plot Widget
self.scrolling_plot_widget = pg.PlotWidget()
# Enable/disable plot squeeze (Fixed axis movement)
self.scrolling_plot_widget.plotItem.setMouseEnabled(x=False, y=False)
self.scrolling_plot_widget.setXRange(self.LEFT_X, self.RIGHT_X)
self.scrolling_plot_widget.setTitle('Scrolling Plot Example')
self.scrolling_plot_widget.setLabel('left', 'Value')
self.scrolling_plot_widget.setLabel('bottom', 'Time (s)')
self.scrolling_plot = self.scrolling_plot_widget.plot()
self.scrolling_plot.setPen(197,235,255)
self.layout = QtGui.QGridLayout()
self.layout.addWidget(self.scrolling_plot_widget)
self.read_position_thread()
self.start()
# Update plot
def start(self):
self.position_update_timer = QtCore.QTimer()
self.position_update_timer.timeout.connect(self.plot_updater)
self.position_update_timer.start(self.get_scrolling_plot_timer_frequency())
# Read in data using a thread
def read_position_thread(self):
self.current_position_value = 0
self.old_current_position_value = 0
self.position_update_thread = Thread(target=self.read_position, args=())
self.position_update_thread.daemon = True
self.position_update_thread.start()
def read_position(self):
frequency = self.get_scrolling_plot_frequency()
while True:
try:
# Add data
self.current_position_value = random.randint(1,101)
self.old_current_position_value = self.current_position_value
time.sleep(frequency)
except:
self.current_position_value = self.old_current_position_value
def plot_updater(self):
self.dataPoint = float(self.current_position_value)
if len(self.data) >= self.buffer:
del self.data[:1]
self.data.append(self.dataPoint)
self.scrolling_plot.setData(self.X_Axis[len(self.X_Axis) - len(self.data):], self.data)
def clear_scrolling_plot(self):
self.data[:] = []
def get_scrolling_plot_frequency(self):
return self.FREQUENCY
def get_scrolling_plot_timer_frequency(self):
return self.TIMER_FREQUENCY
def get_scrolling_plot_layout(self):
return self.layout
def get_current_position_value(self):
return self.current_position_value
def get_scrolling_plot_widget(self):
return self.scrolling_plot_widget
if __name__ == '__main__':
# Create main application window
app = QtGui.QApplication([])
app.setStyle(QtGui.QStyleFactory.create("Cleanlooks"))
mw = QtGui.QMainWindow()
mw.setWindowTitle('Scrolling Plot Example')
# Create scrolling plot
scrolling_plot_widget = ScrollingPlot()
# Create and set widget layout
# Main widget container
cw = QtGui.QWidget()
ml = QtGui.QGridLayout()
cw.setLayout(ml)
mw.setCentralWidget(cw)
# Can use either to add plot to main layout
#ml.addWidget(scrolling_plot_widget.get_scrolling_plot_widget(),0,0)
ml.addLayout(scrolling_plot_widget.get_scrolling_plot_layout(),0,0)
mw.show()
# Start Qt event loop unless running in interactive mode or using pyside
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()

pyqtgraph select 2D region of graph as threshold to redraw the graph

I wish to add functionality such that the user can draw a rectangle over a selection of lines and the graph will refresh such that the lines within the rectangle keep their respective colour and any lines outside turn grey?
My code is as follows, and currently zooms on the drawing of a user defined rectangle over the lines, (for 3 lines, my actual code will plot a lot more):
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')
from random import randint
class CustomViewBox(pg.ViewBox):
def __init__(self, *args, **kwds):
pg.ViewBox.__init__(self, *args, **kwds)
self.setMouseMode(self.RectMode)
## reimplement right-click to zoom out
def mouseClickEvent(self, ev):
if ev.button() == QtCore.Qt.RightButton:
#self.autoRange()
self.setXRange(0,5)
self.setYRange(0,10)
def mouseDragEvent(self, ev):
if ev.button() == QtCore.Qt.RightButton:
ev.ignore()
else:
pg.ViewBox.mouseDragEvent(self, ev)
app = pg.mkQApp()
vb = CustomViewBox()
graph = pg.PlotWidget(viewBox=vb, enableMenu=False)
colour = []
for i in range(0,3):
colourvalue = [randint(0,255), randint(0,255), randint(0,255)]
tuple(colourvalue)
colour.append(colourvalue)
y_data = [
[['a',0],['b',1],['c',None],['d',6],['e',7]],
[['a',5],['b',2],['c',1],['d',None],['e',1]],
[['a',3],['b',None],['c',4],['d',9],['e',None]],
]
x_data = [0, 1, 2, 3, 4]
for i in range(3):
xv = []
yv = []
for j, v in enumerate(row[i][1] for row in y_data):
if v is not None:
xv.append(int(j))
yv.append(float(v))
graph.plot(xv, yv, pen = colour[i], name=y_data[0][i][0])
graph.show()
graph.setWindowTitle('Hourly Frequency Graph')
graph.setXRange(0,5)
graph.setYRange(0,10)
graph.setLabel('left', "Frequency", units='%')
graph.setLabel('bottom', "Hour")
graph.showGrid(x=True, y=True)
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Thanks in advance for any help and advice!
As an aside I would also like to know why this code always give a segmentation fault: 11 when I close the window.

Categories

Resources