Reputation: 51
I'm trying to add a readout of the cursor position in a pqytplot plotwidget in PyQt5. I found this code which does what I want, but in a stand-alone window all within one program file:
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore
#generate layout
app = QtGui.QApplication([])
win = pg.GraphicsWindow()
label = pg.LabelItem(justify='right')
win.addItem(label)
p1 = win.addPlot(row=1, col=0)
data1 = [n**2 for n in range(100)]
p1.plot(data1, pen="r")
#cross hair
vLine = pg.InfiniteLine(angle=90, movable=False)
hLine = pg.InfiniteLine(angle=0, movable=False)
p1.addItem(vLine, ignoreBounds=True)
p1.addItem(hLine, ignoreBounds=True)
def mouseMoved(evt):
pos = evt[0] ## using signal proxy turns original arguments into a tuple
if p1.sceneBoundingRect().contains(pos):
mousePoint = p1.vb.mapSceneToView(pos)
index = int(mousePoint.x())
if index > 0 and index < len(data1):
label.setText("<span style='font-size: 12pt'>x=%0.1f, <span style='color: red'>y1=%0.1f</span>" % (mousePoint.x(), data1[index]))
vLine.setPos(mousePoint.x())
hLine.setPos(mousePoint.y())
proxy = pg.SignalProxy(p1.scene().sigMouseMoved, rateLimit=60, slot=mouseMoved)
## 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 problem I'm running in to is figuring out how to implement something like this with my GUI - where I will have to pass reference to the plotwidget to the mouseMoved function. In the example above, the mousemoved function has access to hline, vline and p1, but in my code it won't - I need to be able to pass those through. But I have no idea how to do that.
I've tried to replicate this issue with the smallest amount of code possible. First here's a simple UI file for the GUI, called CursorLayout.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1167</width>
<height>443</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_16">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="startbutton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Plot</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="PlotWidget" name="plotWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>300</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_17">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="exitbutton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Exit</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>PlotWidget</class>
<extends>QWidget</extends>
<header location="global">pyqtgraph</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
The main program is this:
from PyQt5 import uic
from PyQt5.QtWidgets import QApplication, QMainWindow
from initGUI import connecttolayout, setinitialview
class UI(QMainWindow):
def __init__(self):
super(UI, self).__init__()
uic.loadUi("CursorLayout.ui", self) #load GUI layout file created with QtDesigner
connecttolayout(self) # connect code to elements in UI file
setinitialview(self) # set initial view (button/label visibility, default values, etc)
self.show()
def clickedstartButton(self): #action if start button clicked
self.plotWidget.clear()
plotx = range(100)
ploty = [number**2 for number in plotx]
thisline = self.plotWidget.plot(plotx, ploty, pen='r')
QApplication.processEvents()
def clickedexitButton(self):
self.close()
app=QApplication([])
UIWindow=UI()
app.exec()
with file containing code to set up the gui, initGUI.py (not necessarily how you would do this, but this is to mimic the file structure of my larger program):
from PyQt5.QtWidgets import QPushButton
import pyqtgraph as pg
def connecttolayout(self): #connect GUI elements to elements in UI file
self.startButton = self.findChild(QPushButton, "startbutton")
self.exitButton = self.findChild(QPushButton, "exitbutton")
self.startButton.clicked.connect(self.clickedstartButton)
self.exitButton.clicked.connect(self.clickedexitButton)
def mouseMoved(evt):
pos = evt[0] ## using signal proxy turns original arguments into a tuple
if self.plotWidget.sceneBoundingRect().contains(pos):
mousePoint = self.plotWidget.vb.mapSceneToView(pos)
index = int(mousePoint.x())
#if index > 0 and index < len(data1):
if index > 0 and index < self.MFmax:
self.cursorlabel.setText("<span style='font-size: 12pt'>x=%0.1f, <span style='color: red'>y=%0.1f</span>" % (
mousePoint.x(), mousePoint.y()))
self.vLine.setPos(mousePoint.x())
self.hLine.setPos(mousePoint.y())
def setinitialview(self): #set initial view to pvst view and clear plot window
#set plot initial configuration
self.plotWidget.setBackground('w')
self.plotWidget.setLabels(left=('Pressure', 'Torr'))
self.plotWidget.setLabel('left',color='black',size=30)
self.plotWidget.setLabels(bottom=('Time', 's'))
self.plotWidget.setLabel('bottom',color='black',size=30)
self.plotWidget.clear()
# cross hair
self.vLine = pg.InfiniteLine(angle=90, movable=False)
self.hLine = pg.InfiniteLine(angle=0, movable=False)
self.plotWidget.addItem(self.vLine, ignoreBounds=True)
self.plotWidget.addItem(self.hLine, ignoreBounds=True)
self.cursorlabel = pg.LabelItem(justify='right')
proxy = pg.SignalProxy(self.plotWidget.scene().sigMouseMoved, rateLimit=60, slot=mouseMoved)
I'm actually surprised my attempt doesn't cause an error - pressing the plot button does create a plot, but it definitely doesn't create the cursor in the graph in the GUI.
How do I get the necessary info passed to the mouseMoved function?
Upvotes: 2
Views: 3009
Reputation: 21
Performance of mx calculation in mouseMoved can be largly improved in order to get a faster response of the cursor:
def mouseMoved(self, evt):
pos = evt
if self.plotWidget.sceneBoundingRect().contains(pos):
mousePoint = self.plotWidget.plotItem.vb.mapSceneToView(pos)
mx = abs(np.ones(len(self.plotx))*mousePoint.x() - self.plotx)
index = mx.argmin()
if index >= 0 and index < len(self.plotx):
self.cursorlabel.setHtml(
"<span style='font-size: 12pt'>x={:0.1f}, \
<span style='color: red'>y={:0.1f}</span>".format(
self.plotx[index], self.ploty[index])
)
self.vLine.setPos(self.plotx[index])
self.hLine.setPos(self.ploty[index])
Upvotes: 2
Reputation: 864
There are a few little errors that will make your program fail:
The mouseMoved()
function has to be inside your widget class because it needs the evt
argument, which is generated in the widget.
The self.MFmax
variable/constant is not created anywhere
In this line:
mousePoint = self.plotWidget.vb.mapSceneToView(pos)
The PlotWidget
object doesn't have the vb
attribute. It is a PlotItem
's attribute, then you should change that line to this:
mousePoint = self.plotWidget.plotItem.vb.mapSceneToView(pos)
Pyqtgraph recommends here to use TextItem
instead of LabelItem
, to display text inside a scaled view, because of its scaling size.
Now, with that said and reorganizing your code to be more legible, here is my solution to your code (you only need the UI
file and this script):
import sys
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, uic
ui_file = uic.loadUiType("CursorLayout.ui")[0]
class UI(QtGui.QMainWindow, ui_file):
def __init__(self):
## Inherit the QMainWindow and ui_file classes
QtGui.QMainWindow.__init__(self)
ui_file.__init__(self)
self.setupUi(self)
## Create aditional widgets
self.plot_item = self.plotWidget.plot()
self.vLine = pg.InfiniteLine(angle=90, movable=False)
self.hLine = pg.InfiniteLine(angle=0, movable=False)
self.cursorlabel = pg.TextItem(anchor=(-1,10))
## Build the rest of the GUI
self.format_plot()
## data
self.plotx = range(100)
self.ploty = [number**2 for number in self.plotx]
## Connect signals to actions
self.startbutton.clicked.connect(self.clickedstartButton)
self.exitbutton.clicked.connect(self.clickedexitButton)
self.plotWidget.scene().sigMouseMoved.connect(self.mouseMoved)
## OVERWRITE the mouseMoved action:
def mouseMoved(self, evt):
pos = evt
if self.plotWidget.sceneBoundingRect().contains(pos):
mousePoint = self.plotWidget.plotItem.vb.mapSceneToView(pos)
index = int(mousePoint.x())
if index > 0 and index < len(self.plotx):
# if index > 0 and index < self.MFmax:
self.cursorlabel.setHtml(
"<span style='font-size: 12pt'>x={:0.1f}, \
<span style='color: red'>y={:0.1f}</span>".format(
mousePoint.x(), mousePoint.y()))
self.vLine.setPos(mousePoint.x())
self.hLine.setPos(mousePoint.y())
def clickedstartButton(self): #action if start button clicked
self.plot_item.setData(self.plotx, self.ploty, pen='r')
self.plotWidget.addItem(self.cursorlabel)
def clickedexitButton(self):
self.close()
def format_plot(self):
self.plotWidget.setBackground('w')
self.plotWidget.setLabels(left=('Pressure', 'Torr'))
self.plotWidget.setLabel('left',color='black',size=30)
self.plotWidget.setLabels(bottom=('Time', 's'))
self.plotWidget.setLabel('bottom',color='black',size=30)
self.plotWidget.addItem(self.vLine, ignoreBounds=True)
self.plotWidget.addItem(self.hLine, ignoreBounds=True)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = UI()
window.show()
sys.exit(app.exec_())
The code above will make the "crosshair" (the hline
and vline
) to follow your mouse and displaying the coordinates of that position, like this:
If you want the "crosshair" to track the points in the curve based on the x-axis position of your cursor, you can change the mouseMoved()
function to this:
def mouseMoved(self, evt):
pos = evt
if self.plotWidget.sceneBoundingRect().contains(pos):
mousePoint = self.plotWidget.plotItem.vb.mapSceneToView(pos)
mx = np.array([abs(i-mousePoint.x()) for i in self.plotx])
index = mx.argmin()
if index >= 0 and index < len(self.plotx):
self.cursorlabel.setHtml(
"<span style='font-size: 12pt'>x={:0.1f}, \
<span style='color: red'>y={:0.1f}</span>".format(
self.plotx[index], self.ploty[index])
)
self.vLine.setPos(self.plotx[index])
self.hLine.setPos(self.ploty[index])
And this will be the result:
Upvotes: 5