I'm trying to add a label to the top right corner of the plot showing the most recent data value. I've tried using pg.LabelItem and adding this to pg.PlotWidget and updating the label with each new data update but I'm unable to get the label to appear. Here's some pictures of what I'm trying to do:
What I have:
What I'm trying to do:
I can't get the white label to appear on the plot. Here's my code:
from PyQt4 import QtCore, QtGui
from threading import Thread
import pyqtgraph as pg
import numpy as np
import random
import sys
import time
class SimplePlot(QtGui.QWidget):
def __init__(self, parent=None):
super(SimplePlot, 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.simple_plot_widget = pg.PlotWidget()
# Enable/disable plot squeeze (Fixed axis movement)
self.simple_plot_widget.plotItem.setMouseEnabled(x=False, y=False)
self.simple_plot_widget.setXRange(self.LEFT_X, self.RIGHT_X)
self.simple_plot_widget.setTitle('Plot')
self.simple_plot_widget.setLabel('left', 'Value')
self.simple_plot_widget.setLabel('bottom', 'Time (s)')
self.simple_plot = self.simple_plot_widget.plot()
self.simple_plot.setPen(197,235,255)
self.label_value = pg.LabelItem('', **{'color': '#FFF'})
self.simple_plot_widget.addItem(self.label_value)
self.layout = QtGui.QGridLayout()
self.layout.addWidget(self.simple_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_simple_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_simple_plot_frequency()
while True:
try:
# Add data
self.current_position_value = self.current_position_value + random.uniform(-1, -5)
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.simple_plot.setData(self.X_Axis[len(self.X_Axis) - len(self.data):], self.data)
# Update label value
self.label_value.setText(str(self.dataPoint))
def clear_simple_plot(self):
self.data[:] = []
def get_simple_plot_frequency(self):
return self.FREQUENCY
def get_simple_plot_timer_frequency(self):
return self.TIMER_FREQUENCY
def get_simple_plot_layout(self):
return self.layout
def get_current_position_value(self):
return self.current_position_value
def get_simple_plot_widget(self):
return self.simple_plot_widget
if __name__ == '__main__':
app = QtGui.QApplication([])
mw = QtGui.QMainWindow()
mw.setWindowTitle('Plot')
simple_plot_widget = SimplePlot()
cw = QtGui.QWidget()
ml = QtGui.QGridLayout()
cw.setLayout(ml)
mw.setCentralWidget(cw)
ml.addLayout(simple_plot_widget.get_simple_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_()
It may be because your plot is rescaling constantly, and the size of the LabelItem doesn't change with it, also it seems to be positioned on the positive side of the x-axis, so you cant visualize the text.
Pyqtgraph recommends here to use TextItem instead of LabelItem, to display text inside a scaled view.
I tried using the TextItem and it worked fine, but its default position is bad, maybe because your plot is in the negative quadrant. Just use the setPos() method like this:
# Update label value
self.label_value.setPos(QtCore.QPointF(-9, 0.6*min(self.data)))
self.label_value.setText(str(self.dataPoint))
And it should do what you want.
Related
I want to make an application that put a random point on a graph every 5 seconds but it does not update when I append the point to the line series. I also tried the repaint() and QApplication.ProcessEvent() but still does not update.
here's my code
import random
from PySide6 import QtCore, QtWidgets
from PySide6.QtCharts import QChartView, QChart, QLineSeries
class MyGraph(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.points = []
# Set up the graph
self.point_line_series = QLineSeries()
self.chart = QChart()
self.chart.addSeries(self.point_line_series)
self.chart.setTitle("Points Over Time")
self.chart_view = QChartView()
self.chart_view.setChart(self.chart)
self.setCentralWidget(self.chart_view)
# Start a timer to add new points every 5 seconds
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.add_point)
self.timer.start(5000)
def add_point(self):
x = random.randint(0, 100)
y = random.randint(0, 100)
self.points.append((x, y))
self.point_line_series.append(x, y)
print(f"added point {x}, {y}")
self.repaint()
self.chart_view.repaint()
QtWidgets.QApplication.processEvents()
if __name__ == "__main__":
app = QtWidgets.QApplication()
graph = MyGraph()
graph.show()
app.exec_()
I want to add points every 5 seconds and update the graph.
The graph does not update but the points is appended to the line series.
here's the workaround
import random
from PySide6 import QtCore, QtWidgets
from PySide6.QtCharts import QChartView, QChart, QLineSeries
class MyGraph(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.points = []
# Set up the graph
self.point_line_series = QLineSeries()
chart = QChart()
chart.addSeries(self.point_line_series)
chart.setTitle("Points Over Time")
self.chart_view = QChartView()
self.chart_view.setChart(chart)
self.setCentralWidget(self.chart_view)
# Start a timer to add new points every 5 seconds
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.add_point)
self.timer.start(5000)
def add_point(self):
x = random.randint(0, 100)
y = random.randint(0, 100)
self.points.append((x, y))
self.point_line_series.append(x, y)
print(f"added point {x}, {y}")
chart = QChart()
chart.addSeries(self.point_line_series)
self.chart_view.setChart(chart)
if __name__ == "__main__":
app = QtWidgets.QApplication()
graph = MyGraph()
graph.show()
app.exec_()
basically just make a new chart add the line series and set that chart as the chart of chart view using chart.setchart()
I have trouble using pygtgraph scrolling plots
Expected Results
The expected results are quite similar to the pyqtgraph-examples-scrolling plots-plot5
X-values are times, which can be generated by a simple function. Y-Values are random values.
Each 10 seconds samples as one chunk and each plot can have max. 30 seconds samples, which means 3 chunks. The current plot window only shows the latest 10 seconds samples
For example, now there are total 60 seconds samples:
Data between 50s-60s will be viewed in the current window
Data between 30s-50s could be viewed by using the mouse to drag the x-axis backward
Data between 0-30s will not be displayed
My Code
My current code is below, it can only show latest 30s data.
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
import random
win = pg.GraphicsLayoutWidget(show=True)
win.setWindowTitle('Scrolling Plots')
p1 = win.addPlot()
p1.setYRange(0,10)
xVal = [0]
yVal = [0]
def genTime(): # used to generate time
t = 0
while True:
t += np.random.random_sample()
yield t
t = np.ceil(t)
xTime = genTime()
#=====================================================
viewSize = 10 # current window show only latest 10s data
plotSize = 30 # plot 30s data -> 3 chunk
lstCurves = [] # List for Curves
def update():
global p1, xVal, yVal, lstCurves
#for c in lstCurves:
# c.setPos(xVal[-1], 0)
i = np.ceil(xVal[-1]) % viewSize # e.g. when time is 9.2s -> one 10s view size is full, append to curves list as one chunk
if i == 0:
curve = p1.plot()
lstCurves.append(curve)
xValLast = xVal[-1]
yValLast = yVal[-1]
xVal = [xValLast]
yVal = [yValLast]
while len(lstCurves) > 3: # max 3 chunk (30 s)
p1.removeItem(lstCurves.pop(0)) # remove the oldest 10s
else:
curve = lstCurves[-1] # latest 10s curve
xVal.append(next(xTime))
yVal.append(random.randint(0,9))
curve.setData(xVal, yVal)
print(len(lstCurves))
#======================================================
timer = pg.QtCore.QTimer()
timer.timeout.connect(update)
timer.start(1000)
## 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_()
Problem
I have tried using curve.setPos(xx, 0), It looks like the whole curve is moving along the x-axis, but the mapping relationship between X-value and Y-value is broken
I have also tried using setXRange() to dynamically change x-axis display-range in update() func. But in this case, I can't use the mouse to drag the x-axis back to view the old data any more.
My English is not good, I hope you can understand my question. Any suggestions would be sincerely appreciated!
Problem
The reasons your code don't do what you want are:
When you drag to view the other chunks, you disable the automatic auto-range of the plot, after that, you will have to manually drag the plot every time you want to see the new data. Also, by default, the auto-range of the plot will cover all the data that you are plotting.
When you use the setRange() method inside the update function, it will force that range every time you add another value to the data. Then the drag will not work as you want
What can you do
Well, from my perspective using the mouse drag to visualize the other data is not very convenient, I suggest to use an external widget to control the range of the data you want to view, like, a slider, scroll bar, spin box, ...
A QScrollBar can do the job and it will look esthetic in a GUI.
Before my Alternative Solution, I have a suggestion for you:
Use objects to create your widget, generate a class and use the variables as attributes, with this you avoid the use of the keyword global, and you could reuse the widget for other purposes.
Alternative Solution
Try this:
import sys
import random
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
class MyApp(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
## Creating the Widgets and Layouts
self.plot_widget = pg.PlotWidget()
self.layout = QtGui.QVBoxLayout()
self.sbutton = QtGui.QPushButton("Start / Continue")
self.ebutton = QtGui.QPushButton("Stop")
self.timer = pg.QtCore.QTimer()
self.scroll = QtGui.QScrollBar(QtCore.Qt.Horizontal)
## Creating the variables and constants
self.data = [[0], [random.randint(0,9)]] ## [xVal, yVal] to create less variables
self.plot_item = self.plot_widget.plot(*self.data)
self.plot_widget.setYRange(0, 10)
self.xTime = self.genTime()
self.vsize = 10
self.psize = 30
## Building the Widget
self.setLayout(self.layout)
self.layout.addWidget(self.sbutton)
self.layout.addWidget(self.ebutton)
self.layout.addWidget(self.plot_widget)
self.layout.addWidget(self.scroll)
## Changing some properties of the widgets
self.plot_widget.setMouseEnabled(x=False, y=False)
self.ebutton.setEnabled(False)
self.scroll.setEnabled(False)
self.scroll.setMaximum(self.psize-self.vsize)
self.scroll.setValue(self.psize-self.vsize)
## Coneccting the signals
self.sbutton.clicked.connect(self.start)
self.ebutton.clicked.connect(self.stop)
self.timer.timeout.connect(self.update)
self.scroll.valueChanged.connect(self.upd_scroll)
def genTime(self): # used to generate time
t = 0
while True:
t += np.random.random_sample()
yield t
t = np.ceil(t)
def upd_scroll(self):
val = self.scroll.value()
xmax = np.ceil(self.data[0][-1+self.vsize-self.psize+val])-1
xmin = xmax-self.vsize
self.plot_widget.setXRange(xmin, xmax)
def update(self):
num = len(self.data[0])
if num <= self.psize:
self.plot_item.setData(*self.data)
else:
self.plot_item.setData(
self.data[0][-self.psize:],
self.data[1][-self.psize:]
)
if num == self.vsize:
self.scroll.setEnabled(True)
self.data[0].append(next(self.xTime))
self.data[1].append(random.randint(0,9))
if num > self.vsize :
self.upd_scroll()
def start(self):
self.sbutton.setEnabled(False)
self.ebutton.setEnabled(True)
self.timer.start(100)
def stop(self):
self.sbutton.setEnabled(True)
self.ebutton.setEnabled(False)
self.timer.stop()
self.upd_scroll()
def closeEvent(self, event):
self.timer.stop()
event.accept()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MyApp()
window.show()
sys.exit(app.exec_())
It may look like this:
I'm going to make a real-time curve out of a sequence of data. First, I established a quantity dictionary, which has 3 groups of data. The current program can draw a curve dynamically. The X-axis can also show the time, which is also updated in real time. However, the time at different points in the X-axis is always the same value.
UNIX_EPOCH_naive = datetime.datetime(1970, 1, 1, 0, 0) #offset-naive datetime
UNIX_EPOCH_offset_aware = datetime.datetime(1970, 1, 1, 0, 0, tzinfo = pytz.utc) #offset-aware datetime
UNIX_EPOCH = UNIX_EPOCH_naive
TS_MULT_us = 1e6
def now_timestamp(ts_mult=TS_MULT_us, epoch=UNIX_EPOCH):
return(int((datetime.datetime.utcnow() - epoch).total_seconds()*ts_mult))
def int2dt(ts, ts_mult=TS_MULT_us):
tz = pytz.timezone('Asia/Shanghai')
user_ts = int(time.time())
d1 = datetime.datetime.fromtimestamp(float(user_ts))
d1x = tz.localize(d1)
return(d1x)
def dt2int(dt, ts_mult=TS_MULT_us, epoch=UNIX_EPOCH):
delta = dt - epoch
return(int(delta.total_seconds()*ts_mult))
def td2int(td, ts_mult=TS_MULT_us):
return(int(td.total_seconds()*ts_mult))
def int2td(ts, ts_mult=TS_MULT_us):
return(datetime.timedelta(seconds=float(ts)/ts_mult))
class TimeAxisItem(pg.AxisItem):
def __init__(self, *args, **kwargs):
super(TimeAxisItem, self).__init__(*args, **kwargs)
def tickStrings(self, values, scale, spacing):
return [int2dt(value).strftime("%H:%M:%S") for value in values]
p = win.addPlot(title="Data-Time Graph", axisItems={'bottom': TimeAxisItem(orientation='bottom')})
data_dict = {}
p.addLegend()
data_x=[]
def getDate():
......
.....
curve = p.plot(pen = color[len(data_dict)],name=name)
data_dict[name] = [curve] # dictionary: {key:[curve,[dadta1,data2,...]]}
data_dict[name].append([val])
def addToDisplay():
p.plot()
for i in data_dict.items():
data = i[1][1] #
curve = i[1][0] #
if(len(data) > data_frequency):#
data_y=data[- data_frequency:]
else:
data_y = data[:]
curve.setData(data_y)#
if __name__ == "__main__":
th= threading.Thread(target=getDate)#
th.start()
timer = pg.QtCore.QTimer()
timer.timeout.connect(addToDisplay)
timer.start(10)
What I hope is that the X-axis is dynamically refreshed, with the latest time on the right side and the past time is on the left side.
I'm not entirely sure what you're trying to achieve since your code doesn't run but it seems you're trying to create a timestamp plot. Here's a widget that uses TimeAxisItem to keep track of elapsed time on the X-axis.
PyQt5
from PyQt5 import QtCore, QtGui, QtWidgets
from threading import Thread
from collections import deque
import pyqtgraph as pg
import numpy as np
import random
import sys
import time
"""Scrolling Timestamp Plot Widget Example"""
class TimeAxisItem(pg.AxisItem):
"""Internal timestamp for x-axis"""
def __init__(self, *args, **kwargs):
super(TimeAxisItem, self).__init__(*args, **kwargs)
def tickStrings(self, values, scale, spacing):
"""Function overloading the weak default version to provide timestamp"""
return [QtCore.QTime().currentTime().addMSecs(value).toString('mm:ss') for value in values]
class ScrollingTimestampPlot(QtGui.QWidget):
"""Scrolling plot widget with timestamp on x-axis and dynamic y-axis"""
def __init__(self, parent=None):
super(ScrollingTimestampPlot, self).__init__(parent)
# Internal timestamp for x-axis
self.timestamp = QtCore.QTime()
self.timestamp.start()
# Desired Frequency (Hz) = 1 / self.FREQUENCY
# USE FOR TIME.SLEEP (s)
self.FREQUENCY = 0.025
# Screen refresh rate to update plot (ms)
# self.SCROLLING_TIMESTAMP_PLOT_REFRESH_RATE = 1 / Desired Frequency (Hz) * 1000
# USE FOR TIMER.TIMER (ms)
self.SCROLLING_TIMESTAMP_PLOT_REFRESH_RATE = self.FREQUENCY * 1000
self.DATA_POINTS_TO_DISPLAY = 200
# Automatically pops from left if length is full
self.data = deque(maxlen=self.DATA_POINTS_TO_DISPLAY)
# Create Plot Widget
self.scrolling_timestamp_plot_widget = pg.PlotWidget(axisItems={'bottom': TimeAxisItem(orientation='bottom')})
# Enable/disable plot squeeze (Fixed axis movement)
self.scrolling_timestamp_plot_widget.plotItem.setMouseEnabled(x=False, y=False)
self.scrolling_timestamp_plot_widget.setTitle('Scrolling Timestamp Plot Example')
self.scrolling_timestamp_plot_widget.setLabel('left', 'Value')
self.scrolling_timestamp_plot_widget.setLabel('bottom', 'Time (s)')
self.scrolling_timestamp_plot = self.scrolling_timestamp_plot_widget.plot()
self.scrolling_timestamp_plot.setPen(246,212,255)
self.layout = QtGui.QGridLayout()
self.layout.addWidget(self.scrolling_timestamp_plot_widget)
self.read_position_thread()
self.start()
def start(self):
"""Update plot"""
self.position_update_timer = QtCore.QTimer()
self.position_update_timer.timeout.connect(self.plot_updater)
self.position_update_timer.start(self.get_scrolling_timestamp_plot_refresh_rate())
def read_position_thread(self):
"""Read in data using a thread"""
self.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_timestamp_plot_frequency()
while True:
self.current_position_value = random.randint(1,101)
time.sleep(frequency)
def plot_updater(self):
self.data_point = float(self.current_position_value)
self.data.append({'x': self.timestamp.elapsed(), 'y': self.data_point})
self.scrolling_timestamp_plot.setData(x=[item['x'] for item in self.data], y=[item['y'] for item in self.data])
def clear_scrolling_timestamp_plot(self):
self.data.clear()
def get_scrolling_timestamp_plot_frequency(self):
return self.FREQUENCY
def get_scrolling_timestamp_plot_refresh_rate(self):
return self.SCROLLING_TIMESTAMP_PLOT_REFRESH_RATE
def get_scrolling_timestamp_plot_layout(self):
return self.layout
def get_current_position_value(self):
return self.current_position_value
def get_scrolling_timestamp_plot_widget(self):
return self.scrolling_timestamp_plot_widget
# Start Qt event loop unless running in interactive mode or using pyside
if __name__ == '__main__':
# Create main application window
app = QtWidgets.QApplication([])
app.setStyle(QtGui.QStyleFactory.create("Cleanlooks"))
mw = QtGui.QMainWindow()
mw.setWindowTitle('Scrolling Plot Example')
# Create scrolling plot
scrolling_timestamp_plot_widget = ScrollingTimestampPlot()
# 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_timestamp_plot_widget.get_scrolling_timestamp_plot_widget(),0,0)
ml.addLayout(scrolling_timestamp_plot_widget.get_scrolling_timestamp_plot_layout(),0,0)
mw.show()
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
PyQt4
from PyQt4 import QtCore, QtGui
from threading import Thread
from collections import deque
import pyqtgraph as pg
import numpy as np
import random
import sys
import time
"""Scrolling Timestamp Plot Widget Example"""
class TimeAxisItem(pg.AxisItem):
"""Internal timestamp for x-axis"""
def __init__(self, *args, **kwargs):
super(TimeAxisItem, self).__init__(*args, **kwargs)
def tickStrings(self, values, scale, spacing):
"""Function overloading the weak default version to provide timestamp"""
return [QtCore.QTime().addMSecs(value).toString('mm:ss') for value in values]
class ScrollingTimestampPlot(QtGui.QWidget):
"""Scrolling plot widget with timestamp on x-axis and dynamic y-axis"""
def __init__(self, parent=None):
super(ScrollingTimestampPlot, self).__init__(parent)
# Internal timestamp for x-axis
self.timestamp = QtCore.QTime()
self.timestamp.start()
# Desired Frequency (Hz) = 1 / self.FREQUENCY
# USE FOR TIME.SLEEP (s)
self.FREQUENCY = 0.025
# Screen refresh rate to update plot (ms)
# self.SCROLLING_TIMESTAMP_PLOT_REFRESH_RATE = 1 / Desired Frequency (Hz) * 1000
# USE FOR TIMER.TIMER (ms)
self.SCROLLING_TIMESTAMP_PLOT_REFRESH_RATE = self.FREQUENCY * 1000
self.DATA_POINTS_TO_DISPLAY = 200
# Automatically pops from left if length is full
self.data = deque(maxlen=self.DATA_POINTS_TO_DISPLAY)
# Create Plot Widget
self.scrolling_timestamp_plot_widget = pg.PlotWidget(axisItems={'bottom': TimeAxisItem(orientation='bottom')})
# Enable/disable plot squeeze (Fixed axis movement)
self.scrolling_timestamp_plot_widget.plotItem.setMouseEnabled(x=False, y=False)
self.scrolling_timestamp_plot_widget.setTitle('Scrolling Timestamp Plot Example')
self.scrolling_timestamp_plot_widget.setLabel('left', 'Value')
self.scrolling_timestamp_plot_widget.setLabel('bottom', 'Time (s)')
self.scrolling_timestamp_plot = self.scrolling_timestamp_plot_widget.plot()
self.scrolling_timestamp_plot.setPen(246,212,255)
self.layout = QtGui.QGridLayout()
self.layout.addWidget(self.scrolling_timestamp_plot_widget)
self.read_position_thread()
self.start()
def start(self):
"""Update plot"""
self.position_update_timer = QtCore.QTimer()
self.position_update_timer.timeout.connect(self.plot_updater)
self.position_update_timer.start(self.get_scrolling_timestamp_plot_refresh_rate())
def read_position_thread(self):
"""Read in data using a thread"""
self.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_timestamp_plot_frequency()
while True:
self.current_position_value = random.randint(1,101)
time.sleep(frequency)
def plot_updater(self):
self.data_point = float(self.current_position_value)
self.data.append({'x': self.timestamp.elapsed(), 'y': self.data_point})
self.scrolling_timestamp_plot.setData(x=[item['x'] for item in self.data], y=[item['y'] for item in self.data])
def clear_scrolling_timestamp_plot(self):
self.data.clear()
def get_scrolling_timestamp_plot_frequency(self):
return self.FREQUENCY
def get_scrolling_timestamp_plot_refresh_rate(self):
return self.SCROLLING_TIMESTAMP_PLOT_REFRESH_RATE
def get_scrolling_timestamp_plot_layout(self):
return self.layout
def get_current_position_value(self):
return self.current_position_value
def get_scrolling_timestamp_plot_widget(self):
return self.scrolling_timestamp_plot_widget
# Start Qt event loop unless running in interactive mode or using pyside
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_timestamp_plot_widget = ScrollingTimestampPlot()
# 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_timestamp_plot_widget.get_scrolling_timestamp_plot_widget(),0,0)
ml.addLayout(scrolling_timestamp_plot_widget.get_scrolling_timestamp_plot_layout(),0,0)
mw.show()
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Is it possible link the auto-scale of several plots?
I want to scale all the plots with which ever is the biggest range on all curves of all plots.
Is there way to make it with a pyqtgraph function, or should I find the max, min and set the scale with a custom function?
I am using pyqtgraph on PyQt5
Pyqtgraph should automatically scale the y-axis on multiple plots with which ever has the largest range. Here's an example widget with auto-scaling plots in PyQt4 but the concept should be the same for PyQt5.
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
import sys
import random
class AutoScaleMultiplePlotWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(AutoScaleMultiplePlotWidget, self).__init__(parent)
self.NUMBER_OF_PLOTS = 4
self.LEFT_X = 0
self.RIGHT_X = 5
self.SPACING = 1
self.x_axis = np.arange(self.LEFT_X, self.RIGHT_X + 1, self.SPACING)
self.buffer_size = int((abs(self.LEFT_X) + abs(self.RIGHT_X) + 1)/self.SPACING)
self.auto_scale_plot_widget = pg.PlotWidget()
self.auto_scale_plot_widget.setLabel('left', 'left axis')
# Create plots
self.left_plot1 = self.auto_scale_plot_widget.plot()
self.left_plot2 = self.auto_scale_plot_widget.plot()
self.left_plot3 = self.auto_scale_plot_widget.plot()
self.left_plot4 = self.auto_scale_plot_widget.plot()
self.left_plot1.setPen((173,255,129), width=1)
self.left_plot2.setPen((172,187,255), width=1)
self.left_plot3.setPen((255,190,116), width=1)
self.left_plot4.setPen((204,120,255), width=1)
self.initialize_plot_buffers()
self.initialize_data_buffers()
self.layout = QtGui.QGridLayout()
self.layout.addWidget(self.auto_scale_plot_widget)
self.start()
def initialize_data_buffers(self):
"""Create blank data buffers for each curve"""
self.data_buffers = []
for trace in range(self.NUMBER_OF_PLOTS):
self.data_buffers.append([0])
def initialize_plot_buffers(self):
"""Add plots into buffer for each curve"""
self.plots = []
self.plots.append(self.left_plot1)
self.plots.append(self.left_plot2)
self.plots.append(self.left_plot3)
self.plots.append(self.left_plot4)
def update_plot(self):
"""Generates new random value and plots curve onto plot"""
for trace in range(self.NUMBER_OF_PLOTS):
if len(self.data_buffers[trace]) >= self.buffer_size:
self.data_buffers[trace].pop(0)
data_point = self.data_buffers[trace][-1] + random.randint(10,50)
self.data_buffers[trace].append(float(data_point))
self.plots[trace].setData(self.x_axis[len(self.x_axis) - len(self.data_buffers[trace]):], self.data_buffers[trace])
def get_auto_scale_plot_layout(self):
return self.layout
def start(self):
self.multiple_axis_plot_timer = QtCore.QTimer()
self.multiple_axis_plot_timer.timeout.connect(self.update_plot)
self.multiple_axis_plot_timer.start(500)
if __name__ == '__main__':
# Create main application window
app = QtGui.QApplication([])
app.setStyle(QtGui.QStyleFactory.create("Cleanlooks"))
mw = QtGui.QMainWindow()
mw.setWindowTitle('Auto Scale Multiple Plot Example')
# Create plot
auto_scale_plot = AutoScaleMultiplePlotWidget()
# Create and set widget layout
# Main widget container
cw = QtGui.QWidget()
ml = QtGui.QGridLayout()
cw.setLayout(ml)
mw.setCentralWidget(cw)
# Add plot to main layout
ml.addLayout(auto_scale_plot.get_auto_scale_plot_layout(),0,0)
mw.show()
if(sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Today is the first day I have tried using PyQtGraph. I really like it so far except I can't seem to fully comprehend how things work..
I am trying to place two FFT plot widgets into the same window. After much trial and error I found what I thought was the proper way to do it. However now I have two plots which show the correct information but everything on the Y axis is inverted.
Also it seems zooming and panning are not correct either (the whole plot moves, not just the data within it).
This image shows the two real-time audio fft plots both within a single GraphicsWindow. On the left I use addPlot with addItem and on the right I use addViewBox with addItem.
To be thorough I have tried using item.invertY(True) and item.scale(1,-1).
In both cases it will invert the Y axis data but not the text or axes, nor does it address the panning/zooming issues..
This Python script is everything I was able to write.
It was based off of this file: pyqtgraph live running spectrogram from microphone
import numpy as np
import pyqtgraph as pg
import pyaudio
from PyQt4 import QtCore, QtGui
FS = 44100 #Hz
CHUNKSZ = 1024 #samples
class MicrophoneRecorder():
def __init__(self, signal):
self.signal = signal
self.p = pyaudio.PyAudio()
self.stream = self.p.open(format=pyaudio.paInt16,
channels=1,
rate=FS,
input=True,
frames_per_buffer=CHUNKSZ)
def read(self):
data = self.stream.read(CHUNKSZ)
y = np.fromstring(data, 'int16')
self.signal.emit(y)
def close(self):
self.stream.stop_stream()
self.stream.close()
self.p.terminate()
class SpectrogramWidget2(pg.PlotWidget):
read_collected = QtCore.pyqtSignal(np.ndarray)
def __init__(self):
super(SpectrogramWidget2, self).__init__()
self.img = pg.ImageItem()
self.addItem(self.img)
self.img_array = np.zeros((1000, CHUNKSZ/2+1))
# bipolar colormap
pos = np.array([0., 0.5, 1.])
color = np.array([[0,0,0,255], [0,255,0,255], [255,0,0,255]], dtype=np.ubyte)
cmap = pg.ColorMap(pos, color)
pg.colormap
lut = cmap.getLookupTable(0.0, 1.0, 256)
# set colormap
self.img.setLookupTable(lut)
self.img.setLevels([0,100])
# setup the correct scaling for y-axis
freq = np.arange((CHUNKSZ/2)+1)/(float(CHUNKSZ)/FS)
yscale = 1.0/(self.img_array.shape[1]/freq[-1])
self.img.scale((1./FS)*CHUNKSZ, yscale)
self.setLabel('left', 'Frequency', units='Hz')
# prepare window for later use
self.win = np.hanning(CHUNKSZ)
#self.show()
def update(self, chunk):
# normalized, windowed frequencies in data chunk
spec = np.fft.rfft(chunk*self.win) / CHUNKSZ
# get magnitude
psd = abs(spec)
# convert to dB scaleaxis
psd = 20 * np.log10(psd)
# roll down one and replace leading edge with new data
self.img_array = np.roll(self.img_array, -1, 0)
self.img_array[-1:] = psd
self.img.setImage(self.img_array, autoLevels=False)
class SpectrogramWidget(pg.PlotWidget):
read_collected = QtCore.pyqtSignal(np.ndarray)
def __init__(self):
super(SpectrogramWidget, self).__init__()
self.img = pg.ImageItem()
self.addItem(self.img)
self.img_array = np.zeros((1000, CHUNKSZ/2+1))
# bipolar colormap
pos = np.array([0., 0.5, 1.])
color = np.array([[0,0,0,255], [0,255,0,255], [255,0,0,255]], dtype=np.ubyte)
cmap = pg.ColorMap(pos, color)
pg.colormap
lut = cmap.getLookupTable(0.0, 1.0, 256)
# set colormap
self.img.setLookupTable(lut)
self.img.setLevels([0,100])
# setup the correct scaling for y-axis
freq = np.arange((CHUNKSZ/2)+1)/(float(CHUNKSZ)/FS)
yscale = 1.0/(self.img_array.shape[1]/freq[-1])
self.img.scale((1./FS)*CHUNKSZ, yscale)
self.setLabel('left', 'Frequency', units='Hz')
# prepare window for later use
self.win = np.hanning(CHUNKSZ)
#self.show()
def update(self, chunk):
# normalized, windowed frequencies in data chunk
spec = np.fft.rfft(chunk*self.win) / CHUNKSZ
# get magnitude
psd = abs(spec)
# convert to dB scaleaxis
psd = 20 * np.log10(psd)
# roll down one and replace leading edge with new data
self.img_array = np.roll(self.img_array, -1, 0)
self.img_array[-1:] = psd
self.img.setImage(self.img_array, autoLevels=False)
if __name__ == '__main__':
app = QtGui.QApplication([])
win = pg.GraphicsWindow(title="Basic plotting examples")
#win.resize(1000,600)
w = SpectrogramWidget()
w.read_collected.connect(w.update)
spectrum1 = win.addPlot(title="Spectrum 1")#win.addViewBox()
item = w.getPlotItem()
spectrum1.addItem(item)
w2 = SpectrogramWidget2()
w2.read_collected.connect(w2.update)
spectrum2 = win.addViewBox()
spectrum2.addItem(w2.getPlotItem())
mic = MicrophoneRecorder(w.read_collected)
mic2 = MicrophoneRecorder(w2.read_collected)
# time (seconds) between reads
interval = FS/CHUNKSZ
t = QtCore.QTimer()
t.timeout.connect(mic.read)
t.start((1000/interval) ) #QTimer takes ms
t2 = QtCore.QTimer()
t2.timeout.connect(mic2.read)
t2.start((1000/interval) ) #QTimer takes ms
app.exec_()
mic.close()
Thank you for any help!
I have no idea why doing this causes things to be mirrored, but the issue is related to using the plotItem from a plot in another plot (I think that's what you're doing?)
Anyway, PlotWidgets shouldn't be used like that. They are just normal Qt Widgets, so add them to a Qt layout like you would with any other Qt Widget.
if __name__ == '__main__':
app = QtGui.QApplication([])
win = QtGui.QMainWindow()
widget = QtGui.QWidget()
win.setCentralWidget(widget)
layout = QtGui.QHBoxLayout(widget)
win.show()
w = SpectrogramWidget()
w.read_collected.connect(w.update)
layout.addWidget(w)
w2 = SpectrogramWidget2()
w2.read_collected.connect(w2.update)
layout.addWidget(w2)
# .... etc
P.S. Is there a reason you have two identical classes with a different name? You could just instantiate multiple copies of the same class. E.g.
w = SpectrogramWidget()
w2 = SpectrogramWidget()