QGraphicsItem not updating the position (always at 0,0)



  • Hello,

    I'm trying to understand how, when or based on what is updated the position of a QGraphicsItem.
    I have found that in the Qt Elastic Nodes if I print the QScenePos on every repaint of the nodes I can see the position changing. But If I do the same thing for the edges it always return 0,0 . I would love to understand it because the next thing I want to do is to add a child QGraphicsTextItem to the edge and it appears at (0,0) too.

    Thank you in advance.


  • Moderators

    @orensbruli

    Hi and welcome to devnet forum

    You have certainly chosen a good start with trying to analyze one of the examples.

    Please post the section of the code where and how you are outputing the coordinates.



  • @koahnig said in QGraphicsItem not updating the position (always at 0,0):

    Please post the section of the code where and how you are outputing the coordinates.

    Thank you.

    I just added the last line the the paint() method in edge.cpp

    void Edge::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
    {
        if (!source || !dest)
            return;
    
        QLineF line(sourcePoint, destPoint);
        if (qFuzzyCompare(line.length(), qreal(0.)))
            return;
    
        // Draw the line itself
        painter->setPen(QPen(Qt::black, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
        painter->drawLine(line);
    
        // Draw the arrows
        double angle = ::acos(line.dx() / line.length());
        if (line.dy() >= 0)
            angle = TwoPi - angle;
    
        QPointF sourceArrowP1 = sourcePoint + QPointF(sin(angle + Pi / 3) * arrowSize,
                                                      cos(angle + Pi / 3) * arrowSize);
        QPointF sourceArrowP2 = sourcePoint + QPointF(sin(angle + Pi - Pi / 3) * arrowSize,
                                                      cos(angle + Pi - Pi / 3) * arrowSize);
        QPointF destArrowP1 = destPoint + QPointF(sin(angle - Pi / 3) * arrowSize,
                                                  cos(angle - Pi / 3) * arrowSize);
        QPointF destArrowP2 = destPoint + QPointF(sin(angle - Pi + Pi / 3) * arrowSize,
                                                  cos(angle - Pi + Pi / 3) * arrowSize);
    
        painter->setBrush(Qt::black);
        painter->drawPolygon(QPolygonF() << line.p1() << sourceArrowP1 << sourceArrowP2);
        painter->drawPolygon(QPolygonF() << line.p2() << destArrowP1 << destArrowP2);
        qDebug() << "Node: " << this->scenePos().x() << " - " << this->scenePos().y();
    }
    

    And the same in the node.cpp paint() method:

    void Node::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *)
    {
        painter->setPen(Qt::NoPen);
        painter->setBrush(Qt::darkGray);
        painter->drawEllipse(-7, -7, 20, 20);
    
        QRadialGradient gradient(-3, -3, 10);
        if (option->state & QStyle::State_Sunken) {
            gradient.setCenter(3, 3);
            gradient.setFocalPoint(3, 3);
            gradient.setColorAt(1, QColor(Qt::yellow).light(120));
            gradient.setColorAt(0, QColor(Qt::darkYellow).light(120));
        } else {
            gradient.setColorAt(0, Qt::yellow);
            gradient.setColorAt(1, Qt::darkYellow);
        }
        painter->setBrush(gradient);
    
        painter->setPen(QPen(Qt::black, 0));
        painter->drawEllipse(-10, -10, 20, 20);
        qDebug() << "Node: " << this->scenePos().x() << " - " << this->scenePos().y();
    }
    

    Also tried just with pos() instead of scenePos()



  • What you are observing is due to the way the Elastic Nodes example is implemented. But let's first go back two steps and look at the bigger picture:

    The GraphicsView framework offers multiple ways to manipulate item position and orientation. Let's start with the scene: A plane of (theoretically) infinite size, which has a point of origin (0/0) somewhere. The QGraphicsView attaches to a scene, and decides which part of the scene it shows.

    The scene is populated by items (anything derived from QGraphicsItem). An item can be added to a scene directly, or it can be added as a child item to another item.
    The pos() of an item serves as a kind of "anchor point" for that item, always relative to its parent (or to the scene, if it has no parent). However, there is no rule that forces you to use this pos() when painting your item. In fact, when you paint an item, you can completely ignore your own position, and paint based on other information.
    The only thing you must make sure: The area you paint in must be covered by the boundingRect() of the item.

    To sum up, an item has two pieces of information: The position relative to it's parent (or scene), and the area where painting happens (the boundingRect()). In most cases, these two pieces of information are tied together, so it is rather unexpected when they are not.

    In case of the Edge in the Elastic Nodes example, the edge exists to connect two nodes. This is done by storing the position of two respective nodes, and simply painting a line in between. The pos() of the edge itself is never used, because it actually doesn't matter where that edge lies - what it paints is always defined by two nodes.

    If, instead of pos(), you would output boundingRect(), you would see how the painting area of the edges change as you move the nodes.



  • Thank you for clarifying explanation. I still have a doubt. How could I change the behavior or the way the edge is create to get its position updated. As far as I understand I need it to be able to draw a child item for the Edge in the right position (center of the edge). I tried to add a QGraphicsTextItem as a child of the edge with setItemParent and it's drawn at the 0,0 of the scene, not in the center of the boundingRect (I guess it would be the right position for an Edge item).



  • Yes, since the pos() of the edge item is never changed, it would default to 0/0.

    If you want to position an item at the center of the edge, I can see two possible way:

    • You add code to the edge so it does update it's position. That could be done in the adjust() method
    • You position your text item based on the boundingRect of the edge, instead of the position. However, in this case you cannot rely on the parent-child relationship. Instead, much like the nodes now call adjust() on the edges, you edges would have to call a method of your text item, telling them to update their position.


  • think that the first one would be the best for me. That way, as you say, I could rely on the parent-child relation.
    The problem for this is that I still don't know what to use in the adjust method () to set the right position for the Edge. I thought to use setPos () but I believed it was intended to "move" the item. Wouldn't this change the place where the edge is drawn when paint() is called? (maybe not because it's done with relative coordinates, right?). If it's the problem I think that the visualization (paint) and the real position would always be synchronized.
    I'm going to test it, but if you could clarify what I the setPos() is really intended to be used for,I would be very grateful.



  • You are right, you will run into some issues with the first solution, because the painter is drawing in local item coordinates. Therefore, if you change the position, the line will be drawn elsewhere.

    The example really does things quick'n dirty, because it implicitly relies that the item will never be moved from position 0/0, which is bad style in my book.

    But that can be improved in the adjust method:
    I would try something like

    QLineF sceneLine(source->mapToScene(0,0), dest->mapToScene(0,0));
    // now sceneLine contains a line between the nodes in scene coordinates
    setPos(sceneLine.center());
    // Now my edge is positioned at the center between the two nodes
    QLineF line(mapFromItem(source, 0, 0), mapFromItem(dest, 0, 0));
    // Because I have already updated my position before, the mapFromItem should yield the coordinates in item coordinates - just the way we need them for painting, and for calculating the boundingRect
    

    I haven't tried it, though.



  • @Asperamanca said in QGraphicsItem not updating the position (always at 0,0):

    apFromItem should yield the coordinates in item coordinates - just

    I will do! And I will come an write the results. It looks much better (or I understand it better) than the example solution. Thank you so much!



  • @Asperamanca said in QGraphicsItem not updating the position (always at 0,0):

    QLineF sceneLine(source->mapToScene(0,0), dest->mapToScene(0,0));
    // now sceneLine contains a line between the nodes in scene coordinates
    setPos(sceneLine.center());
    // Now my edge is positioned at the center between the two nodes
    QLineF line(mapFromItem(source, 0, 0), mapFromItem(dest, 0, 0))

    It works. I just had to calculate the sceneLine center because thereis no method center() on my qt version. Before reading your solution I was trying this:

    this->setPos(this->mapToParent(this->boundingRect().center()))
    

    I think it also works ok. But your solution looks more clear. I don't know it it's ok to use boundingRect in my solution.

    @Asperamanca , thank you so much again.
    Best regards.

    Edit: changed from mapToScene to mapToParent just in case it's used as a child of other item.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.