Hello,
I'm trying to make a little project with PyQtGraph.
Briefly, my goal is to draw a sort of ruler that measures the distance
between three points.
One point is the origin of the measure, and the other two are independent
points.
Something like this:
[image: Screenshot (36).png]
I've tried everything and the best result I managed to obtain is this.
The problem I'm facing is that when I put one line on the common point and
the other under it, like this:
[image: Immagine 2021-05-26 162818.png]
If I click one of the two lines, the design brokes, and I can't understand
why this happens and how to prevent it.
[image: Immagine 2021-05-26 162846.png]
I'd like to understand more precisely how PyQtGraph handles the drawing of
the objects because at the moment I can't comprehend how I could change my
code to make it work as I want.
--
You received this message because you are subscribed to the Google Groups
"pyqtgraph" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/pyqtgraph/43e59f9c-5faf-46d5-8684-8b7e53d3d6f1n%40googlegroups.com.
# import the necessary packages
from PySide2.QtCore import QLineF, Qt, Signal, Slot, QObject, QPointF, QRectF, QSizeF
from PySide2.QtGui import QRegion, QFont
from PySide2.QtWidgets import QGraphicsItem, QLabel, QWidget
from pyqtgraph.graphicsItems.ImageItem import ImageItem
from pyqtgraph.graphicsItems.LinearRegionItem import LinearRegionItem
# import requests
from functools import partial
import numpy as np
import cv2
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui, QtWidgets
from pyqtgraph.graphicsItems.ViewBox.ViewBox import ViewBox
import random
pg.setConfigOptions(useOpenGL=True)
image = np.zeros((1280, 960, 3))
app = QtGui.QApplication([])
## Create window with GraphicsView widget
w = pg.GraphicsView()
w.show()
w.setWindowTitle('Seam Test')
view = pg.ViewBox()
view.setLimits(xMin=0, xMax=image.shape[0],
minXRange=100, maxXRange=2000,
yMin=0, yMax=image.shape[1],
minYRange=100, maxYRange=2000)
w.setCentralItem(view)
## lock the aspect ratio
view.setAspectLocked(True)
## Add image item
item = ImageItem(image)
view.addItem(item)
## Add line item
class PointItem(QObject):
pos_changed = Signal(object)
def __init__(self, x, y, parent=None):
super().__init__(parent)
self._point = QPointF(x, y)
self._delta = QPointF(0, 0)
@property
def pos(self):
return self._point
@pos.setter
def pos(self, new_pos: tuple[float, float]):
self._point.setX(new_pos[0])
self._point.setY(new_pos[1])
@property
def delta(self):
return self._delta
def setDelta(self, d: QPointF, caller):
self._delta.setX(d.x())
self._delta.setY(d.y())
self.pos_changed.emit(caller)
# def copy(self, parent=None):
# return PointItem(self._point.x(), self._point.y(), parent)
class RectItem(pg.UIGraphicsItem):
def __init__(self, rect, parent=None):
super().__init__(parent)
self._rect = rect
self.picture = QtGui.QPicture()
self._generate_picture()
self.setFlag(QGraphicsItem.ItemIsSelectable)
self.setFlag(QGraphicsItem.ItemIsMovable)
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges)
@property
def rect(self):
return self._rect
def _generate_picture(self):
painter = QtGui.QPainter(self.picture)
painter.setPen(pg.mkPen("w"))
painter.setBrush(pg.mkBrush("g"))
painter.drawRect(self.rect)
painter.end()
def paint(self, painter, option, widget=None):
painter.drawPicture(0, 0, self.picture)
def boundingRect(self):
return QRectF(self.picture.boundingRect())
# Non usare perché dà troppe informazioni
def mouseMoveEvent(self, event):
print(event)
super().mouseMoveEvent(event)
class LineItem(pg.UIGraphicsItem):
moved = Signal(QPointF)
def __init__(self, line, extend=0, horizontal=False, parent=None):
super().__init__(parent)
self.initialPos = QLineF(line)
self._line = line
self.extend = extend
self.horizontal = horizontal
self.delta = QPointF(0,0)
self._extendLine()
self.picture = QtGui.QPicture()
self._generate_picture()
self.setFlag(QGraphicsItem.ItemIsSelectable)
self.setFlag(QGraphicsItem.ItemIsMovable)
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges)
@property
def line(self):
return self._line
def _extendLine(self):
# if (self.extend != 0 and not self.horizontal):
# self._line.setP1( QPointF(self._line.x1(), self._line.y1() - abs(self.extend)) )
# elif (self.horizontal):
# #self.extend = 0
# self._line.setP1( QPointF(self._line.x1(), self._line.y1() - abs(self.extend)) )
# else:
# self._line.setP1(QPointF(self._line.x1(), self._line.y1()))
pass
def _generate_picture(self):
painter = QtGui.QPainter(self.picture)
painter.setPen(pg.mkPen(color="y", width=2))
painter.drawLine(self.getP1(), self.getP2())
painter.end()
def paint(self, painter, option, widget=None):
painter.drawPicture(0, 0, self.picture)
def boundingRect(self):
lineShape = self.picture.boundingRect()
lineShape.adjust(-10, -10, 10, 10)
return QtCore.QRectF(lineShape)
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemPositionChange:
# value is the new position.
if self.horizontal:
if value.x() != 0:
value = QPointF(0, value.y())
self.moved.emit(value)
print(f"value: {value}")
return pg.UIGraphicsItem.itemChange(self, change, value)
def getP1(self):
return QPointF(self._line.x1(), self._line.y1())
def getP2(self):
return QPointF(self._line.x2(), self._line.y2())
def setDelta(self, delta: QPointF):
self.delta = delta
def getDelta(self):
return self.delta
class TerminatedLineItem(pg.UIGraphicsItem):
moved = Signal(QPointF)
def __init__(self, line, extend=0, horizontal=False, parent=None):
super().__init__(parent)
self.initialPos = QLineF(line)
self._line = line
self.extend = extend
self.horizontal = horizontal
self.delta = QPointF(0, 0)
self._extendLine()
self.picture = QtGui.QPicture()
self._generate_picture()
self.setFlag(QGraphicsItem.ItemIsSelectable)
self.setFlag(QGraphicsItem.ItemIsMovable)
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges)
@property
def line(self):
return self._line
def _extendLine(self):
if (self.extend != 0 and not self.horizontal):
self._line.setP1(QPointF(self._line.x1(), self._line.y1() - abs(self.extend)))
elif (self.horizontal):
# self.extend = 0
self._line.setP1(QPointF(self._line.x1(), self._line.y1() - abs(self.extend)))
else:
self._line.setP1(QPointF(self._line.x1(), self._line.y1()))
def _generate_picture(self):
painter = QtGui.QPainter(self.picture)
painter.setPen(pg.mkPen(color="y", width=2))
painter.drawLine(QPointF(self.line.x1(), self.line.y1()), QPointF(self.line.x2(), self.line.y2()))
painter.drawRect(self.line.x1() - 4, self.line.y1() - 4, 8, 8)
painter.end()
def paint(self, painter, option, widget=None):
painter.drawPicture(0, 0, self.picture)
def boundingRect(self):
lineShape = self.picture.boundingRect()
lineShape.adjust(-10, -10, 10, 10)
return QtCore.QRectF(lineShape)
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemPositionChange:
# value is the new position.
if self.horizontal:
if value.x() != 0:
value = QPointF(0, value.y())
# else:
# if value.y() != 0:
# value = QPointF(value.x(), 0)
self.moved.emit(value)
return pg.UIGraphicsItem.itemChange(self, change, value)
def getP1(self):
return QPointF(self._line.x1(), self._line.y1())
def setDelta(self, delta: QPointF):
self.delta = delta
def getDelta(self):
return self.delta
class Distance(QObject):
def __init__(self, A: PointItem, B: PointItem, view: ViewBox, inity: float, name, parent: QWidget=None):
super().__init__(parent)
self.name = name
# self._id = str(id(self))
if A.pos.x() > B.pos.x():
self.A, self.B = B, A
else:
self.A, self.B = A, B
# print(f"ID A: {id(self.A)}, ID B: {id(self.B)}, Distance {self.name}")
self.distance = abs(B.pos.x() - A.pos.x())
self.picture = QtGui.QPicture()
self.Achanged = True
self.Bchanged = True
self.extend = 0
top = inity
self.left = TerminatedLineItem(QtCore.QLineF(0, 0, 0, 0), self.extend)
self.right = TerminatedLineItem(QtCore.QLineF(0, 0, 0, 0), self.extend)
self.top = LineItem(QtCore.QLineF(0, 0, 0, 0), horizontal=True)
self.left.line.setP1(QPointF(self.A.pos.x(), self.A.pos.y()))
self.left.line.setP2(QPointF(self.A.pos.x(), top))
self.right.line.setP1(QPointF(self.B.pos.x(), self.B.pos.y()))
self.right.line.setP2(QPointF(self.B.pos.x(), top))
self.top.line.setP1(QPointF(self.A.pos.x(), top))
self.top.line.setP2(QPointF(self.B.pos.x(), top))
self.left._generate_picture()
self.right._generate_picture()
self.top._generate_picture()
self.top.setPos(0, 0)
self.left.moved.connect(self.onLeftSegmentMoved)
self.right.moved.connect(self.onRightSegmentMoved)
self.top.moved.connect(self.onTopSegmentMoved)
self.A.pos_changed.connect(self._onAMoved)
self.B.pos_changed.connect(self._onBMoved)
font = QFont()
font.setPixelSize(12)
html = f'<div style="text-align: center; font-size: 16px; color: yellow;">{str(round(self.B.pos.x() - self.A.pos.x(), 2))}</div>'
self.label = pg.TextItem(
html=html,
anchor=(0.5, 1)
# border='w',
# fill=(0, 0, 255, 100)
)
self.label.setParentItem(self.top)
self.label.setPos(min(self.top.line.x1(), self.top.line.x2()) + ((self.top.boundingRect().width()) / 2) - (self.label.boundingRect().width() / 2), self.top.line.y1() + self.top.getDelta().y() + 5)
view.addItem(self.label)
view.addItem(self.left)
view.addItem(self.right)
view.addItem(self.top)
def __del__(self):
view.removeItem(self.label)
view.removeItem(self.left)
view.removeItem(self.top)
view.removeItem(self.right)
@Slot(QPointF)
def onLeftSegmentMoved(self, delta: QPointF):
# print(f"{self.name} in olsm")
self.A.pos = (self.left.getP1().x(), self.left.getP1().y())
# self.A.setDelta(delta, self._id)
self.A.setDelta(delta, self)
# self._drawlines()
@Slot(QPointF)
def onTopSegmentMoved(self, delta: QPointF):
self.top.setDelta(delta)
self._drawlines()
@Slot(QPointF)
def onRightSegmentMoved(self, delta: QPointF):
# print(f"{self.name} in orsm")
self.B.pos = (self.right.getP1().x(), self.right.getP1().y())
# self.B.setDelta(delta, self._id)
self.B.setDelta(delta, self)
# self._drawlines()
@Slot(object)
def _onAMoved(self, caller):
# #print(f"caller : {caller_id}, me : {self._id}")
# print(f"caller_id {caller_id}, my_id{self._id}")
self.Achanged = (self is caller)
self._drawlines()
@Slot(object)
def _onBMoved(self, caller):
self.Bchanged = (self is caller)
self._drawlines()
def _drawlines(self):
# print(f"func: {self.name}, A: {self.A.pos}, B: {self.B.pos}")
# print(f"ID A: {id(self.A)}, ID B: {id(self.B)}, Distance {self.name}")
if self.Achanged:
self.left.line.setP1(QPointF(self.A.pos.x(), self.A.pos.y()))
self.left.line.setP2(QPointF(self.A.pos.x(), self.top.getP1().y() + self.top.getDelta().y() - self.A.delta.y()))
else:
self.left.line.setP1(QPointF(self.A.pos.x() + self.A.delta.x(), self.A.pos.y() + self.A.delta.y()))
self.left.line.setP2(QPointF(self.A.pos.x() + self.A.delta.x(), self.top.getP1().y() + self.top.getDelta().y()))
if self.Bchanged:
self.right.line.setP1(QPointF(self.B.pos.x(), self.B.pos.y()))
self.right.line.setP2(QPointF(self.B.pos.x(), self.top.getP1().y() + self.top.getDelta().y() - self.B.delta.y()))
else:
self.right.line.setP1(QPointF(self.B.pos.x() + self.B.delta.x(), self.B.pos.y() + self.B.delta.y(),))
self.right.line.setP2(QPointF(self.B.pos.x() + self.B.delta.x(), self.top.getP1().y() + self.top.getDelta().y()))
self.top.line.setP1(QPointF(self.A.pos.x() + self.A.delta.x(), self.top.getP1().y()))
self.top.line.setP2(QPointF(self.B.pos.x() + self.B.delta.x(), self.top.getP1().y()))
self.distance = (abs((self.A.pos.x() + self.A.delta.x()) - (self.B.pos.x() + self.B.delta.x())))
self.label.setAnchor((0.5, 1))
self.label.setPos(min(self.top.line.x1(), self.top.line.x2()) + ((self.top.boundingRect().width()) / 2) - (self.label.boundingRect().width() / 2), self.top.line.y1() + self.top.getDelta().y() + 5)
self.label.setText(str(round(abs(self.distance), 2)))
html = f'<div style="text-align: center; font-size: 16px; color: yellow;">{str(round(abs(self.distance), 2))}</div>'
self.label.setHtml(html)
self.left._generate_picture()
self.top._generate_picture()
self.right._generate_picture()
painter = QtGui.QPainter(self.picture)
painter.setPen(pg.mkPen(color="y", width=2))
painter.drawLines([self.left.line, self.right.line, self.top.line])
painter.end()
print(f"AC : {self.Achanged}, BC : {self.Bchanged}, name: {self.name}")
def mouseClicked(evt):
pos = evt[0]
print(pos)
couples = 3
points: list[PointItem] = []
for i in range(0, couples*2):
x = random.randint(100, 600)
y = random.randint(100, 600)
points.append(PointItem(x, y))
distance = []
distance.append(Distance(points[0], points[3], view, 500, "1"))
distance.append(Distance(points[0], points[2], view, 600, "2"))
# for i in range(0, couples):
# distance.append(Distance(points[(2*i)], points[(2*i)+1], view, 600+(50*i)))
# print(i)
proxyClicked = pg.SignalProxy(w.scene().sigMouseClicked, rateLimit=60, slot=mouseClicked)
## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()