Since the OP has not provided an MRE then I have created an example from scratch. The logic is to track the changes of the items and according to that calculate the new geometry and establish the new position of the other items.
from PyQt5 import QtWidgets, QtGui, QtCore
class GripItem(QtWidgets.QGraphicsPathItem):
circle = QtGui.QPainterPath()
circle.addEllipse(QtCore.QRectF(-5, -5, 10, 10))
square = QtGui.QPainterPath()
square.addRect(QtCore.QRectF(-10, -10, 20, 20))
def __init__(self, annotation_item, index):
super(GripItem, self).__init__()
self.m_annotation_item = annotation_item
self.m_index = index
self.setPath(GripItem.circle)
self.setBrush(QtGui.QColor("green"))
self.setPen(QtGui.QPen(QtGui.QColor("green"), 2))
self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)
self.setAcceptHoverEvents(True)
self.setZValue(11)
self.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
def hoverEnterEvent(self, event):
self.setPath(GripItem.square)
self.setBrush(QtGui.QColor("red"))
super(GripItem, self).hoverEnterEvent(event)
def hoverLeaveEvent(self, event):
self.setPath(GripItem.circle)
self.setBrush(QtGui.QColor("green"))
super(GripItem, self).hoverLeaveEvent(event)
def mouseReleaseEvent(self, event):
self.setSelected(False)
super(GripItem, self).mouseReleaseEvent(event)
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self.isEnabled():
self.m_annotation_item.movePoint(self.m_index, value)
return super(GripItem, self).itemChange(change, value)
class DirectionGripItem(GripItem):
def __init__(self, annotation_item, direction=QtCore.Qt.Horizontal, parent=None):
super(DirectionGripItem, self).__init__(annotation_item, parent)
self._direction = direction
@property
def direction(self):
return self._direction
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self.isEnabled():
p = QtCore.QPointF(self.pos())
if self.direction == QtCore.Qt.Horizontal:
p.setX(value.x())
elif self.direction == QtCore.Qt.Vertical:
p.setY(value.y())
self.m_annotation_item.movePoint(self.m_index, p)
return p
return super(DirectionGripItem, self).itemChange(change, value)
class CircleAnnotation(QtWidgets.QGraphicsEllipseItem):
def __init__(self, radius=1, parent=None):
super(CircleAnnotation, self).__init__(parent)
self.setZValue(11)
self.m_items = []
self.setPen(QtGui.QPen(QtGui.QColor("green"), 4))
self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)
self.setAcceptHoverEvents(True)
self.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
self._radius = radius
self.update_rect()
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, r):
if r <= 0:
raise ValueError("radius must be positive")
self._radius = r
self.update_rect()
self.add_grip_items()
self.update_items_positions()
def update_rect(self):
rect = QtCore.QRectF(0, 0, 2 * self.radius, 2 * self.radius)
rect.moveCenter(self.rect().center())
self.setRect(rect)
def add_grip_items(self):
if self.scene() and not self.m_items:
for i, (direction) in enumerate(
(
QtCore.Qt.Vertical,
QtCore.Qt.Horizontal,
QtCore.Qt.Vertical,
QtCore.Qt.Horizontal,
)
):
item = DirectionGripItem(self, direction, i)
self.scene().addItem(item)
self.m_items.append(item)
def movePoint(self, i, p):
if 0 <= i < min(4, len(self.m_items)):
item_selected = self.m_items[i]
lp = self.mapFromScene(p)
self._radius = (lp - self.rect().center()).manhattanLength()
k = self.indexOf(lp)
if k is not None:
self.m_items = [item for item in self.m_items if not item.isSelected()]
self.m_items.insert(k, item_selected)
self.update_items_positions([k])
self.update_rect()
def update_items_positions(self, index_no_updates=None):
index_no_updates = index_no_updates or []
for i, (item, direction) in enumerate(
zip(
self.m_items,
(
QtCore.Qt.Vertical,
QtCore.Qt.Horizontal,
QtCore.Qt.Vertical,
QtCore.Qt.Horizontal,
),
),
):
item.m_index = i
if i not in index_no_updates:
pos = self.mapToScene(self.point(i))
item = self.m_items[i]
item._direction = direction
item.setEnabled(False)
item.setPos(pos)
item.setEnabled(True)
def indexOf(self, p):
for i in range(4):
if p == self.point(i):
return i
def point(self, index):
if 0 <= index < 4:
return [
QtCore.QPointF(0, -self.radius),
QtCore.QPointF(self.radius, 0),
QtCore.QPointF(0, self.radius),
QtCore.QPointF(-self.radius, 0),
][index]
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemPositionHasChanged:
self.update_items_positions()
return
if change == QtWidgets.QGraphicsItem.ItemSceneHasChanged:
self.add_grip_items()
self.update_items_positions()
return
return super(CircleAnnotation, self).itemChange(change, value)
def hoverEnterEvent(self, event):
self.setBrush(QtGui.QColor(255, 0, 0, 100))
super(CircleAnnotation, self).hoverEnterEvent(event)
def hoverLeaveEvent(self, event):
self.setBrush(QtGui.QBrush(QtCore.Qt.NoBrush))
super(CircleAnnotation, self).hoverLeaveEvent(event)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
scene = QtWidgets.QGraphicsScene()
view = QtWidgets.QGraphicsView(scene)
view.setRenderHints(QtGui.QPainter.Antialiasing)
item = CircleAnnotation()
item.radius = 100
scene.addItem(item)
view.showMaximized()
sys.exit(app.exec_())