Code to create a Arrow graphics Item in Qt



  • Hi All,

    I need to create Arrow graphics Item in Qt.I believe it should be a subclass of QGraphicsLineItem. Can somebody please help me with the code for Arrow creation?

    My requirement is that I am currently working on a QGraphicsScene on which I drop a QGraphicsLineItem from another scene. My Arrow item should be displayed perpendicular to the QGraphicsLineItem on the graphics scene.
    The Arrow item should be able to displayed on either side of the line item.



  • Hi jeevan,

    you could use "QGraphicsSvgItem ...":http://qt-project.org/doc/qt-5/qgraphicssvgitem.html e.g.
    @
    item = new QGraphicsSvgItem(":/arrow.svg");
    @

    and use svg edit or inkscape to create arrow.svg.



  • @Bela - Thanks bela. My requirement is that I am currently working on a QGraphicsScene on which I drop a QGraphicsLineItem from another scene. My Arrow item should be displayed perpendicular to the QGraphicsLineItem on the graphics scene.

    So My question is will QGraphicsSvgItem suit my requirement? I am very new to graphics concepts.



  • I believe QGraphicsPathItem is better suited. You can either draw the whole arrow using a QPainterPath, or you can just use it to create an arrow head, and use it together with QGraphicsLineItem to make an arrow.



  • @Asperamanca - Thanks Asper. Could you please demonstrate using the sample code for my understanding since I am new to Graphics. My requirement is to display an Arrow perpendicular to a QGraphicsLineItem, whenever a QGraphicsLineItem is dropped onto a QGraphicsScene.

    Basically once the Line item is dropped on to the scene, the Arrow item should appear perpendicular to the line item at any position above the line item.



    • Get the QLineF from the QGraphicsLineItem using line()
    • Use QLineF::normalVector() to get a perpendicular line
    • Create a QPainterPath
    • Use addPolygon to add your arrow shape. In the simplest case, it might be just a triangle consisting of the endpoint of your normalVector, and two points on the line you originally got from the QGraphicsLineItem
    • Create a QGraphicsPathItem and feed it the path (setPath)
    • Position it where you want it


  • you should check diagram scene example there is a class called Arrow
    "link":http://qt-project.org/doc/qt-4.8/graphicsview-diagramscene.html



  • @karim24 - Thank you very much for pointing the Arrow example to me. It was really helpful although I do not understand the algorithm for rendering the triangle portion of the Arrow item. But I am able to create arrow.



  • @Asperamanca - Hi, I tried writing the code and this is how it is, the code is inspired by the diagram scene example of Qt.

    @QPainterPath CustomGraphicsArrow::shape()

    const
    {

    QPainterPath path = QGraphicsLineItem::shape();

    path.addPolygon(arrowHead);

    return path;
    }

    void

    CustomGraphicsArrow::paint(QPainter *painter, const QStyleOptionGraphicsItem *,
    QWidget *)

    {

    QPen myPen = pen();

    myPen.setColor(myColor);

    qreal arrowSize = 10;

    painter->setPen(myPen);

    painter->setBrush(myColor);

    QGraphicsLineItem *parent_line_item =

    dynamic_cast<QGraphicsLineItem *>(parent_graphics_item);
    QLineF normal_vector_line;

    QLineF parent_line;

    if(parent_line_item)
    {
    parent_line = parent_line_item->line();
    normal_vector_line = parent_line.normalVector();
    }

    qDebug() <<"The normal vector line point P1" << normal_vector_line.p1();
    qDebug() <<"The normal vector line point P2" << normal_vector_line.p2();

    normal_vector_line.setLength(40.0);
    setLine(normal_vector_line);

    // QLineF arrow_line = this->line();
    // arrow_line.setLength(35.0);
    double angle = ::acos(line().dx() / line().length());
    if (line().dy() >= 0)
    {

    angle = (Pi * 2) - angle;

    }

    QPointF arrowP1 = line().p2() - QPointF(sin(angle + Pi / 3) * arrowSize,
    cos(angle + Pi / 3) * arrowSize);

    QPointF arrowP2 = line().p2() - QPointF(sin(angle + Pi - Pi / 3) * arrowSize,
    cos(angle + Pi - Pi / 3) * arrowSize);

    arrowHead.clear();
    arrowHead << arrowP1 << arrowP2 << line().p2();
    painter->drawLine(line());
    painter->drawPolygon(arrowHead);

    }@

    In the paint(), the direction of the arrow (upwards/downwards) can be
    determined by chaging the arrowP1, arrowP2 values.In the sample code above the arrow is displayed in the upward direction. If the arrow needs to be displayed in downward direction :

    QPointF arrowP1 = line().p1() + QPointF(sin(angle + Pi / 3) * arrowSize,
    cos(angle + Pi / 3) * arrowSize);

    QPointF arrowP2 = line().p1() + QPointF(sin(angle + Pi - Pi / 3) * arrowSize,
    cos(angle + Pi - Pi / 3) * arrowSize);



  • @Asperamanca - I did not understand your below lines in the explanantion:

    bq. Use addPolygon to add your arrow shape. In the simplest case, it might be just a triangle consisting of the endpoint of your normalVector, and two points on the line you originally got from the QGraphicsLineItem

    I believe ine end point of triangle will be the end point of normal vector but the other two points of triangle will never be on the line which I got from QGrpahicsLineItem.

    My big challenge in your code explanation comes from the fact that how do we determine the other two points of the triangle apart from the one which is the end point of normal vector?



  • QLineF offers a few helpful methods.
    You can use pointAt to get any point along the line, e.g. pointAt(0.5) would be the middle of the line. If you say you want your arrow centered on the line, you could get the required points for the triangle by using:

    @QPointF pt1 = line.pointAt((0.4);
    QPointF pt2 = line.pointAt((0.6);@

    However, now the width of the triangle would depend on the width of the line. You can correct this by starting from the middle:
    @QPointF ptMiddle = line.pointAt(0.5); // line is the line you got from the QGraphicsLineItem - the base line for your triangle
    QLineF lineAdd;
    lineAdd.setP1(ptMiddle); // Start from the middle
    lineAdd.setLength(3.0); // Length for half the width of the triangle
    lineAdd.setAngle(line.angle()); // Bring to same direction as line
    QPointF pt1 = lineAdd.p2(); //pt1 is now exactly 3 pixels along the base line of the triangle
    // Do the same thing in the other direction
    lineAdd.setAngle(lineAdd.angle() - 180.0)
    QPointF pt2 = lineAdd.p2(); //pt2 is now exactly 3 pixels along the base line, but in the other direction@



  • @Asperamanca - . I am able to display the arrow item perpendicular to the dropped QGraphicsLineItem on the scene. The Arrow item used itself is a custom Arrow which is derived from QGraphicsLineItem. The issue I am facing now is that, the Arrow item should not only be perpendicular to the dropped line item but should also intersect the line item at some point. this gives the impression of direction flow from one side to other.

    since I used normal vector as reference to display the starting point of arrow item (point P1), I am unable to position it in such a way that it intersects the line item. I tried doing it using some random transformation after first moving the Point P1 to middle of parent line item. But the whole thing blows away once I start rotating the line item. The intent is to see that Arrow item remains in the same position and at same angle(90 degrees) when the parent line item starts rotating. Currently the Arrow item remains at 90 degrees but the position gets displaced as we rotate the line item. how do we fix this issue?

    Please find my current code below:

    @QPainterPath CustomGraphicsArrow::shape() const
    {

    QPainterPath path = QGraphicsLineItem::shape();
     path.addPolygon(arrowHead);
    return path;
    

    }

    void CustomGraphicsArrow::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
    {

    QPen myPen = pen();
    myPen.setColor(myColor);
    qreal arrowSize = 10;
    painter->setPen(myPen);
    painter->setBrush(myColor);

    QGraphicsLineItem *parent_line_item = dynamic_cast<QGraphicsLineItem *>(parent_graphics_item);
    QLineF normal_vector_line;

    QLineF parent_line;
    if(parent_line_item)
    {

      parent_line = parent_line_item->line();
      normal_vector_line = parent_line.normalVector();
    

    }

    QPointF parent_mid_point = parent_line.pointAt(0.5);

    QPointF arrow_line_start_point = this->mapFromParent(parent_mid_point);
     normal_vector_line.setLength(50.0);
    
    setLine(normal_vector_line);
    QLineF arrow_line = line();
    

    QPointF translate_point = QPointF(-5.0, arrow_line_start_point.y());

    arrow_line.translate(translate_point);

    double angle = ::acos(line().dx() / line().length());
    if (line().dy() >= 0)
    {
    angle = (Pi * 2) - angle;
    }

    QPointF arrowP1 = arrow_line.p2() - QPointF(sin(angle + Pi / 3) * arrowSize,
    

    cos(angle + Pi / 3) * arrowSize);
    QPointF arrowP2 = arrow_line.p2() - QPointF(sin(angle + Pi - Pi / 3) * arrowSize,
    cos(angle + Pi - Pi / 3) * arrowSize);

     arrowHead.clear();
     arrowHead << arrowP1 << arrowP2 << arrow_line.p2();
     painter->drawLine(arrow_line);
     painter->drawPolygon(arrowHead);
    

    }@

    [QUOTE]Basically, how do i position an arrow item such that it intersects the dropped line item and when the line item is rotated, the Arrow item maintains its position and angle too? Could you please suggest me a solution to the issue? [/QUOTE]



  • You can probably make your life easier by using the boundingRect to your advantage. Note that a boundingRect is a QRectF, not a QSizeF. It has a position as well as a size.
    The position you assign to an item using setPos corresponds to the 0/0-Position on your boundingRect. So, e.g. when your boundingRect is -10/-10 to 10/10, setPos will work with the center point of your item. That should make rotation much easier.



  • I do have a bounding rect for my arrow item :

    @QRectF CustomGraphicsArrow::boundingRect() const
    {
    qreal extra = (pen().width() + 20) / 2.0;

    return QRectF(line().p1(), QSizeF(line().p2().x() - line().p1().x(),
                                      line().p2().y() - line().p1().y()))
        .normalized()
        .adjusted(-extra, -extra, extra, extra);
    

    }@

    I'm not sure how having a bounding rect for my arrow item helps me in rotation. I'm not sure if you got the picture of the problem I am facing:

    I have a parent QGraphicsLineItem, which is dropped on to a scene. This parent item can rotated and resized. Once the parent line item is drooped on to the scene, the Arrow line item should appear perpendicular to the parent line item and it should be positioned in such a way that, the arrow item should intercept the parent item. The arrow item should not have the same P1 as the normal vector, it should be positioned as if it is intercepting the parent line.
    I managed to do that by giving the arrow item coordinates
    QPointF translate_point = QPointF(-5.0, arrow_line_start_point.y());

    I called translate on arrow line item with above coordinates.

    I believe this will translate the Arrow's P1 from (0,0) to the specified
    postion. Note : I haven't used any setPos() at all.

    Now when I rotate the parent line item the Arrow item position keeps changing but I want it to remain static at all the time. The arrow should also rotate along with the parent line item. How do I achieve this?



  • I am sorry, but I do not have time to think about your problem in depth. My gut feeling is that the "translate" is hurting you, as it moves the axis around which your item must rotate.

    Try to modify the boundingRect of your arrow item in such a way that the item's position is equal to the rotation axis, i.e. on the intersection point with your parent line (the way I understand your problem).


Log in to reply
 

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