Convert pixel/scene coordinates (from MouseClickEvent) to plot coordinates - python

I have a GraphicsLayoutWidget to which I added a PlotItem. I'm interested in clicking somewhere in the graph (not on a curve or item) and getting the clicked point in graph coordinates. Therefore I connected the signal sigMouseClicked to a custom method something like this:
...
self.graphics_view = pg.GraphicsLayoutWidget()
self.graphics_view.scene().sigMouseClicked.connect(_on_mouse_clicked)
def _on_mouse_clicked(event):
print(f'pos: {event.pos()} scenePos: {event.scenePos()})
...
While this gives me coordinates that look like e.g. Point (697.000000, 882.000000), I struggle to convert them to coordinates in "axes" coordinates of the graph (e.g. (0.3, 0.5) with axes in the range [0.1]). I tried many mapTo/From* methods without success.
Is there any way to archive this with PyQtGraph?

You have to use the viewbox of the plotitem:
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.graphics_view = pg.GraphicsLayoutWidget()
self.setCentralWidget(self.graphics_view)
self.plot_item = self.graphics_view.addPlot()
curve = self.plot_item.plot()
curve.setData([0, 0, 1, 1, 2, 2, 3, 3])
self.graphics_view.scene().sigMouseClicked.connect(self._on_mouse_clicked)
def _on_mouse_clicked(self, event):
p = self.plot_item.vb.mapSceneToView(event.scenePos())
print(f"x: {p.x()}, y: {p.y()}")
def main():
app = QtGui.QApplication([])
w = MainWindow()
w.show()
app.exec_()
if __name__ == "__main__":
main()

Related

How to draw and fill a rectangle in a PyQtGraph plot at position where mouse is clicked

I am using PyQt5 and PyQtGraph. I have simplified the example code below. Then I want to draw in the plot view a small red rectangle each time the mouse is clicked at the position where the mouse is clicked, thus accumulating several red rectangles in the plot view. The code below has a #??? comment where I need some help with code that will draw the red rectangle(s).
import sys
from PyQt5 import QtWidgets
import numpy as np
import pyqtgraph as pg
from pyqtgraph import PlotWidget, plot
# *********************************************************************************************
# *********************************************************************************************
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("My MainWindow")
self.qPlotWidget = pg.PlotWidget(self)
self.qPlotWidget.setLabel("bottom", "X-Axis")
self.qPlotWidget.setLabel("left", "Y-Axis")
self.qPlotWidget.scene().sigMouseClicked.connect(self.mouseClickedEvent)
data1 = np.zeros((2, 2), float) # create the array to hold the data
data1[0] = np.array((1.0, 10.0))
data1[1] = np.array((2.0, 20.0))
pen1 = pg.mkPen(color=(255,0,0), width=1) # red
self.qPlotWidget.plot(data1, pen=pen1, name="data1")
def mouseClickedEvent(self, event):
print("mouseClickedEvent")
pos = event.scenePos()
if (self.qPlotWidget.sceneBoundingRect().contains(pos)):
mousePoint = self.qPlotWidget.plotItem.vb.mapSceneToView(pos)
print("mousePoint=", mousePoint)
# draw and fill a 2-pixel by 2-pixel red rectangle where
# the mouse was clicked at [mousePoint.x(), mousePoint.y()]
# ??? add code here
def resizeEvent(self, event):
size = self.geometry()
self.qPlotWidget.setGeometry(10, 10, size.width()-20, size.height()-20)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
screen = QtWidgets.QDesktopWidget().screenGeometry()
w.setGeometry(100, 100, screen.width()-200, screen.height()-200) # x, y, Width, Height
w.show()
sys.exit(app.exec_())
What you could do is to create an empty scatter plot item and add it to self.qPlotWidget. Then in mousrClickedEvent you could add the mouse position to the list of points of this scatter plot item, i.e.
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
.... as before ....
# add empty scatter plot item with a red brush and a square as the symbol to plot widget
brush = pg.mkBrush(color=(255,0,0))
self.scatterItem = pg.ScatterPlotItem(pen=None, size=10, brush=brush, symbol='s')
self.qPlotWidget.addItem(self.scatterItem)
def mouseClickedEvent(self, event):
pos = event.scenePos()
if (self.qPlotWidget.sceneBoundingRect().contains(pos)):
mousePoint = self.qPlotWidget.plotItem.vb.mapSceneToView(pos)
# add point to scatter item
self.scatterItem.addPoints([mousePoint.x()], [mousePoint.y()])

How can I get black axes, but also a (default) grey grid in a pyqtgraph diagram?

I'm trying to change the color of the axes of a pyqtgraph diagram to black, but if I use self.graphWidget.getAxis('bottom').setPen('k') the grid is also black and I would like to have it in default grey. I didn't find any solution. Is there a way to achieve this and additionally color the tick numbers black?
import sys
from PyQt5 import QtWidgets
import pyqtgraph as pg
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.plot()
def plot(self):
self.graphWidget = pg.PlotWidget()
self.setCentralWidget(self.graphWidget)
self.graphWidget.setBackground('w')
pen = pg.mkPen(color=('b'), width=2)
styles = {'color':'r', 'font-size':'15px'}
self.graphWidget.setLabel('left', 'x-values', **styles)
self.graphWidget.setLabel('bottom', 'y-values', **styles)
self.graphWidget.showGrid(x=True, y=True)
self.graphWidget.getAxis('bottom').setPen('k')
self.graphWidget.getAxis('left').setPen('k')
x_values = [1, 2, 3, 4, 5]
y_values = [2, 1, 2, 4, 3]
self.graphWidget.plot(x_values, y_values, pen=pen, symbol='o',
symbolSize=8, symbolPen='k', symbolBrush='k')
def main():
app = QtWidgets.QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I'm using Python 3.8, PyQt5 5.15.0, PyCharm 2020.2 and Linux Mint 19.3.
PyqtGraph uses default pen to draw all lines, ticks and grid in AxisItem. That's why it draws everything in the same color. Changing tick text color is easy, You can use textPen attribute for pg.AxisItem. Or set textPen for already created axis with self.graphWidget.getAxis('bottom').setTextPen(black_pen).
To make axis line in specific color, we must re-implement drawPicture method of pg.AxisItem. We can introduce new init parameter called axisPen, which will be used only for axis line.
Here is full working code:
import sys
from PyQt5 import QtWidgets
import pyqtgraph as pg
from pyqtgraph import debug as debug
class ColoredAxis(pg.AxisItem):
def __init__(self, orientation, pen=None, textPen=None, axisPen=None, linkView=None, parent=None,
maxTickLength=-5, showValues=True, text='', units='', unitPrefix='', **args):
super().__init__(orientation, pen=pen, textPen=textPen, linkView=linkView, parent=parent,
maxTickLength=maxTickLength,
showValues=showValues, text=text, units=units, unitPrefix=unitPrefix, **args)
self.axisPen = axisPen
if self.axisPen is None:
self.axisPen = self.pen()
def drawPicture(self, p, axisSpec, tickSpecs, textSpecs):
profiler = debug.Profiler()
p.setRenderHint(p.RenderHint.Antialiasing, False)
p.setRenderHint(p.RenderHint.TextAntialiasing, True)
## draw long line along axis
pen, p1, p2 = axisSpec
# Use axis pen to draw axis line
p.setPen(self.axisPen)
p.drawLine(p1, p2)
# Switch back to normal pen
p.setPen(pen)
# p.translate(0.5,0) ## resolves some damn pixel ambiguity
## draw ticks
for pen, p1, p2 in tickSpecs:
p.setPen(pen)
p.drawLine(p1, p2)
profiler('draw ticks')
# Draw all text
if self.style['tickFont'] is not None:
p.setFont(self.style['tickFont'])
p.setPen(self.textPen())
bounding = self.boundingRect().toAlignedRect()
p.setClipRect(bounding)
for rect, flags, text in textSpecs:
p.drawText(rect, int(flags), text)
profiler('draw text')
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.plot()
def plot(self):
black_pen = pg.mkPen(color='black', width=2)
self.axis_x = ColoredAxis(orientation='bottom', axisPen=black_pen)
self.axis_y = ColoredAxis(orientation='left', axisPen=black_pen)
self.graphWidget = pg.PlotWidget(background="w", axisItems={'bottom': self.axis_x, 'left': self.axis_y})
self.setCentralWidget(self.graphWidget)
styles = {'color': 'b', 'font-size': '15px'}
self.graphWidget.setLabel('left', 'x-values', **styles)
self.graphWidget.setLabel('bottom', 'y-values', **styles)
self.graphWidget.showGrid(x=True, y=True)
x_values = [1, 2, 3, 4, 5]
y_values = [2, 1, 2, 4, 3]
pen = pg.mkPen(color='b', width=2)
self.graphWidget.plot(x_values, y_values, pen=pen, symbol='o',
symbolSize=8, symbolPen='k', symbolBrush='k')
self.axis_x.setTextPen(black_pen)
self.axis_y.setTextPen(black_pen)
def main():
app = QtWidgets.QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

link auto-scale on multiple plots in pyqtgraph

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

How to show matplotlib.pyplot in qt widget?

I want to show the pyplot image in widget (QWidget) that I put in a gui designed in QtDesigner:
When I push the Çiz button I want to show the image that I can create in python with that code:
points = np.array([(1, 1), (2, 4), (3, 1), (9, 3)])
x = points[:,0]
y = points[:,1]
# calculate polynomial
z = np.polyfit(x, y, 2)
f = np.poly1d(z)
x_fit = np.linspace(min(x), max(x), 10000)
y_fit = [f(_x) for _x in x_fit]
plt.plot(x, y)
plt.plot(x_fit, y_fit)
plt.show()
EDIT
I made some changes according to the answer but I have new problems.
After I promote it:
I rearrange my code below:
# calculate polynomial and r
self.x_fit = np.linspace(min(self.itemX), max(self.itemY), 10000)
self.y_fit = [f(_x) for _x in self.x_fit]
self._dlg.plotwidget.plot(self.itemX, self.itemY)
self._dlg.plotwidget.plot(self.x_fit, self.y_fit)
self.itemX is x values in a list.
self.itemY is y values in a list.
self._dlg is the MainWindow that you see.
When I try to open that window I get this error message:
I just tested the code below works for me:
import sys
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui, uic
uifilename = 'test.ui'
form_class = uic.loadUiType(uifilename)[0] #dirty reading of the ui file. better to convert it to a '.py'
class MyWindowClass(QtGui.QMainWindow, form_class):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setupUi(self)
self.onInit()
def onInit(self):
#usually a lot of connections here
self.x_fit = np.linspace(1,10000, 10000)
self.y_fit = [f(_x) for _x in self.x_fit]
self.plotwidget.plot(self.x_fit,self.y_fit,symbol='o',pen=None)
self.plotwidget.setLabel('left',text='toto',units='')
self.plotwidget.setLabel('top',text='tata',units='')
def f(x):
return x**2+1
if __name__ == '__main__':
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
app = QtGui.QApplication([])
pg.setConfigOption('background', 'w')
win = MyWindowClass()
win.show()
app.exec_()
with this test.ui : https://github.com/steph2016/profiles/blob/master/test.ui.
Note the plotwidget is encapsulated inside a layout. I thought it works with just the Main Window, but I'm not sure anymore...
Sorry I don't exactly answer the question but I don't use matplotlib.pyplot with pyqt. I recommend using pyqtgraph (http://pyqtgraph.org), which is quite convenient and powerful. With Qt Designer:
you just insert a 'graphics view' in your GUI
you right-click on it and promote it to "plotwidget" using pyqtgraph. I guess the english tab is something like this: base class 'QGraphicsView'; promoted class 'PlotWidget'; header file 'pyqtgraph', then 'add', then 'promote'
then you can make plotwidget.plot(x,y,...), plotwidget.clear(), and co. It will plot everything inside the plotwidget with a bunch of interacting possibilities

Drawing a polygon in PyQt

Background
I would like to draw a simple shape on the screen, and I have selected PyQt as the package to use, as it seems to be the most established. I am not locked to it in any way.
Problem
It seems to be over complicated to just draw a simple shape like for example a polygon on the screen. All examples I find try to do a lot of extra things and I am not sure what is actually relevant.
Question
What is the absolute minimal way in PyQt to draw a polygon on the screen?
I use version 5 of PyQt and version 3 of Python if it makes any difference.
i am not sure, what you mean with
on the screen
you can use QPainter, to paint a lot of shapes on any subclass of QPaintDevice e.g. QWidget and all subclasses.
the minimum is to set a pen for lines and text and a brush for fills. Then create a polygon, set all points of polygon and paint in the paintEvent():
import sys, math
from PyQt5 import QtCore, QtGui, QtWidgets
class MyWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
self.pen = QtGui.QPen(QtGui.QColor(0,0,0)) # set lineColor
self.pen.setWidth(3) # set lineWidth
self.brush = QtGui.QBrush(QtGui.QColor(255,255,255,255)) # set fillColor
self.polygon = self.createPoly(8,150,0) # polygon with n points, radius, angle of the first point
def createPoly(self, n, r, s):
polygon = QtGui.QPolygonF()
w = 360/n # angle per step
for i in range(n): # add the points of polygon
t = w*i + s
x = r*math.cos(math.radians(t))
y = r*math.sin(math.radians(t))
polygon.append(QtCore.QPointF(self.width()/2 +x, self.height()/2 + y))
return polygon
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.setPen(self.pen)
painter.setBrush(self.brush)
painter.drawPolygon(self.polygon)
app = QtWidgets.QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())

Categories