Search code examples
pythonqtqgraphicsscenepyside6qgraphicsitem

Unexpected behaviour of QGraphicsScene when trying to add fixed-size Items


I add Items to a GraphicsScene which should appear in a fix size on screen independent of zooming in and out. I create them in the following manner:

class LocationGraphicsItem(QtWidgets.QGraphicsEllipseItem):
    def __init__(self, coordinate, parent = None):
        super().__init__(parent=parent)
        x = coordinate[0]
        y = -coordinate[1]
        self.setRect(-5, -5, 10, 10)
        self.setPos(x, y)
        self.setFlag(self.GraphicsItemFlag.ItemIgnoresTransformations)

I add them to the scene like this:

coordinate = (3412770.9, 5358376.3)
item = LocationGraphicsItem(coordinate)
my_scene.addItem(item)
item.ensureVisible()

and later call

my_view.fitInView(my_scene.sceneRect(), Qt.KeepAspectRatio)

on my view. As long as I add all of them before the Qt eventloop is started, the view is set to display the correct scope, the scene figures out the correct sceneRect which is something like:

PySide6.QtCore.QRectF(3412765.400000, -5371895.600000, 82420.600000, 13524.800000)

However, if I add items after the eventloop is started and the QGraphicsView is already visible, my region of interest is cramped in the top right corner, as the sceneRect changes to:

PySide6.QtCore.QRectF(-5.500000, -5371895.600000, 3495191.500000, 5371901.100000)

How can I avoid that? Can someone explain to me why this happens? I know that I can calculate the relevant rectangle newly with my_scene.itemsBoundingRect(), which unfortunately is quite slow.

What are alternatives / best practices to implement a fixed-size marker? I would prefer to specify the size in pixels anyway.


Here's some complete code:

import random
import sys
print("Python: ", sys.version)
import PySide6
print("PySide: ", PySide6.__version__)
from PySide6 import QtCore, QtGui, QtWidgets

class LocationGraphicsItem(QtWidgets.QGraphicsEllipseItem):
    def __init__(self, coordinate, parent = None):
        super().__init__(parent=parent)
        x = coordinate[0]
        y = -coordinate[1]
        self.setRect(-5, -5, 10, 10)
        self.setPos(x, y)
        self.setBrush(QtGui.QColor("blue"))
        self.setFlag(self.GraphicsItemFlag.ItemIgnoresTransformations)

def add_something():
    print(scene.sceneRect())
    point =(3.4e6+random.random()*1e5, 5.3e6+random.random()*1e5)
    print("Random point: ", point)
    item = LocationGraphicsItem(point)
    scene.addItem(item)
    item.setVisible(True)
    item.ensureVisible()
    view.fitInView(scene.sceneRect(), QtCore.Qt.KeepAspectRatio)
    QtWidgets.QApplication.processEvents()
    print(scene.sceneRect())

if __name__ == "__main__":
    my_points = [(3412770.9, 5358376.3), (3495180.5, 5371890.1), (3495099.1, 5370624.6), (3485765.4, 5371030.1)]
    app = QtWidgets.QApplication()
    scene = QtWidgets.QGraphicsScene()
    for point in my_points:
        item = LocationGraphicsItem(point)
        scene.addItem(item)
        item.setVisible(True)
        item.ensureVisible()

    window = QtWidgets.QMainWindow(parent = None)
    window.setGeometry(50, 50, 1300, 750)
    basic_widget = QtWidgets.QWidget(parent=window)
    window.setCentralWidget(basic_widget)
    layout = QtWidgets.QHBoxLayout()
    basic_widget.setLayout(layout)
    button = QtWidgets.QPushButton("add something", parent = basic_widget)
    button.clicked.connect(add_something)
    layout.addWidget(button)

    view = QtWidgets.QGraphicsView(scene, parent = basic_widget)
    layout.addWidget(view)
    view.fitInView(scene.sceneRect(), QtCore.Qt.KeepAspectRatio)
    window.setVisible(True)
    app.exec()

Output:

Python:  3.11.0 (main, Oct 24 2022, 18:26:48) [MSC v.1933 64 bit (AMD64)]
PySide:  6.4.0.1
PySide6.QtCore.QRectF(3412765.400000, -5371895.600000, 82420.600000, 13524.800000)
Random point:  (3449076.7327629146, 5323846.465410875)
PySide6.QtCore.QRectF(-5.500000, -5371895.600000, 3495191.500000, 5371901.100000)

Solution

  • It seems to be a Qt bug. Why not report it? (See Report Bugs.)

    You can remedy it by calling the QGraphicsItem.sceneTransform() manually like the following.

    def add_something():
        ...
        item = LocationGraphicsItem(point)
        scene.addItem(item)
        ...
        dummy = item.sceneTransform()
        view.fitInView(scene.sceneRect(), QtCore.Qt.KeepAspectRatio)
        ...