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

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.

Related

QScatterSeries points invisibles when plotted on QCharts

I'm trying to plot a numpy array using QScatterSeries, however only the axis are updated and the points are not displayed. I'm not sure why it is not working.
projectionwindow.py
from PySide2.QtCore import Qt
from PySide2.QtWidgets import QWidget, QHBoxLayout
from PySide2.QtGui import QColor, QPen
from PySide2.QtCharts import QtCharts
class ProjectionWindow(QWidget):
"""
TODO
"""
def __init__(self, parent=None) -> 'None':
super().__init__()
self.setWindowTitle('Projection')
self.resize(800, 800)
self.chart = QtCharts.QChart()
self.chart_view = QtCharts.QChartView(self.chart)
self.layout = QHBoxLayout(self)
self.layout.addWidget(self.chart_view)
self.setLayout(self.layout)
self.show()
def loadCharts(self, data: 'ndarray') -> 'None':
points = QtCharts.QScatterSeries()
points.setMarkerSize(2.0)
for i in range(data.shape[0]):
points.append(data[i, 0], data[i, 1])
self.chart.addSeries(points)
self.chart.createDefaultAxes()
self.chart.show()
This is my current result when calling
main.py
import sys
import numpy as np
from PySide2.QtWidgets import QApplication
from ui.projectionwindow import ProjectionWindow
if __name__ == "__main__":
app = QApplication(sys.argv)
data = np.array([[1,2],
[3,4]])
window = ProjectionWindow(app)
window.loadCharts(data)
sys.exit(app.exec_())
Resulted obtained:
You have 2 errors:
The markerSize is very small that makes it indistinguishable to the eye.
When establishing a series for the first time, the QChart takes the minimum rectangle so that in your case it is in the corners, so the solution is to change the minimum and maximum value of the axes considering an adequate margin.
def loadCharts(self, data: "ndarray") -> "None":
points = QtCharts.QScatterSeries()
points.setMarkerSize(20)
for i in range(data.shape[0]):
points.append(data[i, 0], data[i, 1])
self.chart.addSeries(points)
self.chart.createDefaultAxes()
m_x, M_x = min(data[:, 0]), max(data[:, 0])
m_y, M_y = min(data[:, 1]), max(data[:, 1])
ax = self.chart.axes(Qt.Horizontal, points)[0]
ax.setMin(m_x - 1)
ax.setMax(M_x + 1)
ay = self.chart.axes(Qt.Vertical, points)[0]
ay.setMin(m_y - 1)
ay.setMax(M_y + 1)
Output:

How to effectively redraw multiple matplotlib plots with blit

I'm using matplotlib with pyqt5 to draw data into 3 axes, and than user can make selection in one plot that will be shown in other two plots too. Since I'm working with big data (up to 10 millions of points), drawing selection could be slow, especially when I need to draw to scatterplot.
I am trying to use matplotlib blit function, but have some issues with result. Here is minimum simple example.
import matplotlib
matplotlib.use('Qt5Agg')
import numpy as np
import sys
from matplotlib.backends.qt_compat import QtCore, QtWidgets
from matplotlib.backends.backend_qt5agg import (FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
layout = QtWidgets.QVBoxLayout(self._main)
self.static_canvas = FigureCanvas(Figure(figsize=(10, 10)))
layout.addWidget(self.static_canvas)
layout.addWidget(NavigationToolbar(self.static_canvas, self))
axes = self.static_canvas.figure.subplots(2, 1)
self.ax1 = axes[0]
self.ax2 = axes[1]
self.ax1.cla()
self.ax2.cla()
button = QtWidgets.QPushButton('Click me!')
button.clicked.connect(self.update_canvas_blit)
layout.addWidget(button)
# Fixing random state for reproducibility
np.random.seed(19680801)
# Create random data
N = 50000
x = np.random.rand(N)
y = np.random.rand(N)
self.ax1.scatter(x, y)
self.points = self.ax1.scatter([],[], s=5, color='red')
x = np.linspace(0, 1000, 100000)
self.ax2.plot(x, np.sin(x))
self.lines, = self.ax2.plot([],[], color='red')
self.static_canvas.draw()
self.background1 = self.static_canvas.copy_from_bbox(self.ax1.bbox)
self.background2 = self.static_canvas.copy_from_bbox(self.ax2.bbox)
def update_canvas_blit(self):
N = 50
x = np.random.rand(N)
y = np.random.rand(N)
self.static_canvas.restore_region(self.background1)
self.points.set_offsets(np.c_[x,y])
self.ax1.draw_artist(self.points)
self.ax1.figure.canvas.blit(self.ax1.bbox)
self.static_canvas.restore_region(self.background2)
x = np.linspace(0, np.random.randint(500,1000), 1000)
self.lines.set_data(x, np.sin(x))
self.ax2.draw_artist(self.lines)
self.ax2.figure.canvas.blit(self.ax2.bbox)
if __name__ == "__main__":
qapp = QtWidgets.QApplication(sys.argv)
app = ApplicationWindow()
app.show()
qapp.exec_()
When clicking button, expected output should be still same background with random points/lines redrawing. In a way it is happening but there are some strange artifacts that looks like somehow axes are drawn to each other. But when I try to save it to .png, it will restore to good state.
The problem is that the snapshot of the background is taken at a moment in time where the figure has not yet been shown on screen. At that point the figure is 10 by 10 inches large. Later, it is shown inside the QMainWindow and resized to fit into the widget.
Only once that has happened, it makes sense to take the background snapshot.
One option is to use a timer of 1 second and only then copy the background. This would look as follows.
import numpy as np
import sys
from matplotlib.backends.qt_compat import QtCore, QtWidgets
from matplotlib.backends.backend_qt5agg import (FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
layout = QtWidgets.QVBoxLayout(self._main)
self.static_canvas = FigureCanvas(Figure(figsize=(10, 10)))
layout.addWidget(self.static_canvas)
layout.addWidget(NavigationToolbar(self.static_canvas, self))
axes = self.static_canvas.figure.subplots(2, 1)
self.ax1 = axes[0]
self.ax2 = axes[1]
self.ax1.cla()
self.ax2.cla()
button = QtWidgets.QPushButton('Click me!')
button.clicked.connect(self.update_canvas_blit)
layout.addWidget(button)
# Fixing random state for reproducibility
np.random.seed(19680801)
# Create random data
N = 50000
x = np.random.rand(N)
y = np.random.rand(N)
self.ax1.scatter(x, y)
self.points = self.ax1.scatter([],[], s=5, color='red')
x = np.linspace(0, 1000, 100000)
self.ax2.plot(x, np.sin(x))
self.lines, = self.ax2.plot([],[], color='red')
self.static_canvas.draw()
self._later()
def _later(self, evt=None):
self.timer = self.static_canvas.new_timer(interval=1000)
self.timer.single_shot = True
self.timer.add_callback(self.update_background)
self.timer.start()
def update_background(self, evt=None):
self.background1 = self.static_canvas.copy_from_bbox(self.ax1.bbox)
self.background2 = self.static_canvas.copy_from_bbox(self.ax2.bbox)
def update_canvas_blit(self):
N = 50
x = np.random.rand(N)
y = np.random.rand(N)
self.static_canvas.restore_region(self.background1)
self.points.set_offsets(np.c_[x,y])
self.ax1.draw_artist(self.points)
self.ax1.figure.canvas.blit(self.ax1.bbox)
self.static_canvas.restore_region(self.background2)
x = np.linspace(0, np.random.randint(500,1000), 1000)
self.lines.set_data(x, np.sin(x))
self.ax2.draw_artist(self.lines)
self.ax2.figure.canvas.blit(self.ax2.bbox)
if __name__ == "__main__":
qapp = QtWidgets.QApplication(sys.argv)
app = ApplicationWindow()
app.show()
qapp.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_()

Can I share the crosshair with two graph in pyqtgraph (pyqt5)

I am implemented two graphs in pyqt5 with pyqtgraph, and want to share cursor between two graphs.
I have a program result at the following:
And I want to share the crosshair like:
Can pyqtgraph do it ? Many thanks.
Following is my code (update), it will show 2 graph and the cursor will only allow move in graph2 only. However, I want the cursor can move and get data from graph1 and graph2.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
from pyqtgraph import MultiPlotWidget
class GUI_create_main_window(QWidget):
def __init__(self):
super().__init__()
main_layout = QVBoxLayout(self)
self.plot1 = pg.PlotWidget()
main_layout.addWidget(self.plot1)
self. plot2 = pg.PlotWidget()
main_layout.addWidget(self.plot2)
self.draw_cursor()
self.setLayout(main_layout)
self.show()
#hair cross event
def eventFilter(self, source, event):
try:
if (event.type() == QtCore.QEvent.MouseMove and
source is self.plot2.viewport()):
pos = event.pos()
if self.plot2.sceneBoundingRect().contains(pos):
mousePoint = self.vb.mapSceneToView(pos)
self.vLine.setPos(mousePoint.x())
self.hLine.setPos(mousePoint.y())
return QtGui.QWidget.eventFilter(self, source, event)
except Exception as e:
traceback.print_exc()
err = sys.exc_info()[1]
print(str(err))
def draw_cursor(self):
#cross hair
self.vLine = pg.InfiniteLine(angle=90, movable=False, pen=pg.mkPen('k', width=1))
self.hLine = pg.InfiniteLine(angle=0, movable=False, pen=pg.mkPen('k', width=1), label='{value:0.1f}',
labelOpts={'position':0.98, 'color': (200,0,0), 'movable': True, 'fill': (0, 0, 200, 100)})
self.plot2.addItem(self.vLine, ignoreBounds=True)
self.plot2.addItem(self.hLine, ignoreBounds=True)
self.vb = self.plot2.plotItem.vb
#set mouse event
self.plot2.setMouseTracking(True)
self.plot2.viewport().installEventFilter(self)
if __name__ == '__main__':
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')
app = QApplication(sys.argv)
gui = GUI_create_main_window()
currentExitCode = app.exec_()
I know that it is kind of late but after struggling with similar problem I came up with a solution.
First of all we can capture mouse movements on pyqtgraph plots by installing 'SignalProxy' on their scene.
For example:
self.proxy = pg.SignalProxy(self.p.scene().sigMouseMoved, rateLimit=120, slot=self.mouseMoved)
This will call mouseMoved method on when mouse move signal is emitted.
We can get mouse position converted to scene position with
def mouseMoved(self, evt):
pos = evt[0]
mousePoint = self.p.vb.mapSceneToView(pos)
x, y = int(mousePoint.x()), int(mousePoint.y())
now we can set vLine pos to x and hLine pos to y on if we can have access to other graph's plot we can also set their vLine pos to x (since x axis should match)
we can link their x axis together (same movement on x axis is applied on other graph) by:
plot.setXLink(other_plot)
Side notes:
I set up two graphs by subclassing pyqtgraph's GraphicsLayoutWidget and adding plots to them individually I'm sure there should be a way of doing it in a single subclass but doing something like...
self.addPlot(title="Plot 1")
self.addPlot(title="Plot 2")
...creates two plots next to each other rather than stacked on top of
each other - we want to only link vLines anyways.
we can access other graph by something like this:
self.top_graph = TopGraph()
self.bottom_graph = BottomGraph()
self.top_graph.other_graph = self.bottom_graph # set bottom graph in top graph to access it in bottom graph
self.bottom_graph.other_graph = self.top_graph # set top graph in bottom graph to access it in top graph
maybe we can do global variables as well but I'm not sure that it is a good practice.
So it all adds up to:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import pyqtgraph as pg
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
main_widget = QWidget()
main_layout = QVBoxLayout()
main_widget.setLayout(main_layout)
self.setCentralWidget(main_widget)
self.top_graph = TopGraph()
self.bottom_graph = BottomGraph()
self.top_graph.other_graph = self.bottom_graph # set bottom graph in top graph to access it in bottom graph
self.bottom_graph.other_graph = self.top_graph # set top graph in bottom graph to access it in top graph
self.top_graph.p.setXLink(self.bottom_graph.p)
#example plot
x = range(-50, 51)
y1 = [i**2 for i in x]
y2 = [-i**2 for i in x]
self.top_graph.p.plot(x, y1, pen=pg.mkPen("r", width=3))
self.bottom_graph.p.plot(x, y2, pen=pg.mkPen("g", width=3))
splitter = QSplitter(Qt.Vertical)
splitter.addWidget(self.top_graph)
splitter.addWidget(self.bottom_graph)
main_layout.addWidget(splitter)
class TopGraph(pg.GraphicsLayoutWidget):
def __init__(self):
super().__init__()
self.p = self.addPlot()
self.p.hideAxis("bottom")
self.vLine = pg.InfiniteLine(angle=90, movable=False, pen=pg.mkPen('k', width=1))
self.hLine = pg.InfiniteLine(angle=0, movable=False, pen=pg.mkPen('k', width=1), label='{value:0.1f}',
labelOpts={'position':0.98, 'color': (200,0,0), 'movable': True, 'fill': (0, 0, 200, 100)})
self.p.addItem(self.vLine, ignoreBounds=True)
self.p.addItem(self.hLine, ignoreBounds=True)
self.proxy = pg.SignalProxy(self.p.scene().sigMouseMoved, rateLimit=120, slot=self.mouseMoved)
def mouseMoved(self, evt):
pos = evt[0]
if self.p.sceneBoundingRect().contains(pos):
self.hLine.show() # show this graph's h line since we are now in control
mousePoint = self.p.vb.mapSceneToView(pos)
x, y = int(mousePoint.x()), int(mousePoint.y())
self.vLine.setPos(x)
self.hLine.setPos(y)
self.other_graph.vLine.setPos(x)
self.other_graph.hLine.hide() # hide other graphs h line since we don't controll it here
class BottomGraph(pg.GraphicsLayoutWidget):
def __init__(self):
super().__init__()
self.p = self.addPlot()
self.vLine = pg.InfiniteLine(angle=90, movable=False, pen=pg.mkPen('k', width=1))
self.hLine = pg.InfiniteLine(angle=0, movable=False, pen=pg.mkPen('k', width=1), label='{value:0.1f}',
labelOpts={'position':0.98, 'color': (200,0,0), 'movable': True, 'fill': (0, 0, 200, 100)})
self.p.addItem(self.vLine, ignoreBounds=True)
self.p.addItem(self.hLine, ignoreBounds=True)
self.proxy = pg.SignalProxy(self.p.scene().sigMouseMoved, rateLimit=120, slot=self.mouseMoved)
def mouseMoved(self, evt):
pos = evt[0]
if self.p.sceneBoundingRect().contains(pos):
self.hLine.show() # show this graph's h line since we are now in control
mousePoint = self.p.vb.mapSceneToView(pos)
x, y = int(mousePoint.x()), int(mousePoint.y())
self.vLine.setPos(x)
self.hLine.setPos(y)
self.other_graph.vLine.setPos(x)
self.other_graph.hLine.hide() # hide other graphs h line since we don't controll it here
def main():
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')
app = QApplication([])
window = MainWindow()
window.setMinimumSize(600, 400)
window.show()
app.exec()
if __name__ == "__main__":
main()

Two Y-scales in pyqtgraph (twinx-like)

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_()

Categories

Resources