Nominate our 2022 Qt Champions!

Algorithm animation(QPainter)

  • I'm trying to make an animation of an algorithm, so far I could only add some rectangles with the scene->addRect(20+(i*30),0-random_numbers_[i],28,random_numbers_[i],Pen,darkMagentaBrush);
    I'm a little bit confused, how I can change the color of the rectangle, for example the fifth one and the sixth, to show that this two will swap. Is it better to use the QPainter??

  • Moderators

    It would be nice if you provided more info or ocde, but I'll just assume scene in your code is QGraphicsScene (am I right?).
    If so, then the addRect()method returns a pointer to QGraphicsRectItem representing your rectangle.
    You should store these pointers in some array, like QVector<QGraphicsRectItem*>.
    Then you can get to any of the items and use its setBrush method to set any brush you want on it. The QBrush you set can be a solid color, gradient , texture or a pattern, whatever you need.

  • @Chris-Kawa, Thank you. BTW can I draw a number inside of a rectangle? I tried to use QPainter but no luck

  • Moderators

    You can create a QGraphicsSimpleTextItem and put it inside of you rectangle.
    Beware though that you're creating rectangles with an anchor outside of them so the text item won't align properly.

    You could use something like this instead:

    for(int i = 0; i < whatever; ++i)
       QGraphicsRectItem* rectangle = scene->addRect(0, 0, someWidth, someHeight);
       QGraphicsSimpleTextItem* text = new QGraphicsSimpleTextItem("hello", rectangle);
       rectangle->moveBy(someOffsetX, someOffsetY);

  • @Chris-Kawa And the last question. How I can do smth like this, for example, I have a red rectangle and after 5 sec it becomes blue during run time?

  • Moderators

    I have a red rectangle and after 5 sec it becomes blue during run time?

    One way would be with a timer:

    QGraphicsRectItem* rectangle = ... //get it from somewhere
    QTimer::singleShot(5000, [=]{ rectangle->setBrush(Qt::blue); });

  • @Chris-Kawa said:

    QTimer::singleShot(5000, [=]{ rectangle->setBrush(Qt::blue); });

    MOTHER OF GOD! Thank you so much now I can continue with the app :-)

  • @Chris-Kawa Ok, got a little bit stuck again how I can update the scene view. For example I found that one rectangle is bigger than the other one how I can animate the swap. I have a list of pointers to the rectangles, after I swapped the pointers in the list, how I can repaint the GraphicsView with the new pointers?

  • Moderators

    one rectangle is bigger than the other one how I can animate the swap

    Sorry, I can't make sense of what you're asking. What swap? What does a rectangle size have to do with it?

    after I swapped the pointers in the list, how I can repaint the GraphicsView with the new pointers?

    Again this makes no sense. Moving a pointer in a list has nothing to do with graphics view. Do you mean you want to move the rectangle in the scene or what? A pointer to a graphics item has nothing to do with its position on the scene.

    What are you trying to do exactly? I mean conceptually.

  • @Chris-Kawa I thought I could repaint the new List which contains the pointers. I want to swap two rectangles. The first one is = 3(height) the second one is = 2; I want to swap them, so that on the first position would be rectangle number two with the height of 2, and on the second position would rectangle number one with height = 3. But I want it to swap with some pause, so I could see it, smth like a timer.

  • Moderators

    You don't need to repaint anything manually. You just change the position of the item in the scene and it will refresh the view automatically. To animate the move you can use a QGraphicsItemAnimation.

    Here's a simple example:

    void moveItemTo(QGraphicsItem* item, const QPointF& endPoint)
        QTimeLine* t= new QTimeLine(2000);
        QObject::connect(t, &QTimeLine::finished, t, &QTimeLine::deleteLater);
        QGraphicsItemAnimation* a = new QGraphicsItemAnimation(t);
        a->setPosAt(0.0, item->pos());
        a->setPosAt(1.0, endPoint);

  • @Chris-Kawa I'll give it a try. Thank you a lot. And the last question, for sure, How I can emit a SingleShot every second in a loop, because
    for(int i=0;i<random_numbers_.size()-1;i++){
    for(int j=0;j<random_numbers_.size()-1;j++){
    QTimer::singleShot(1000, [=]{ rectan[i]->setBrush(Qt::green); });
    During the executing, after a second it makes every rectangle till the 9-th green, it loops and SingleShots every second in a loop. But how I can do this with a pause, liek: The first one is green, after a second the second becomes green, so I could see it?

  • Moderators

    for(int i = 0; i < random_numbers_.size(); ++i)
        QTimer::singleShot(1000 + i * 1000, [=]{ rectan[i]->setBrush(Qt::green); });

  • @Chris-Kawa I thought about that but is there a way that SingleShot won't be chained by time, because In the future I will need to set the second one green and the first one red, So it'l look like that the rectangle is crawling till the end

  • Moderators

    Ok, I think you're going deeper and deeper into the woods but it's a totally bad direction.
    So if I'm getting this right you are making some algorithm that does stuff (sorting?) to some items represented by rectangles and you want to visualize the steps. Is that right?
    In that case setting up hardcoded timers and single shots is going against the wind because you're effectively tying to predict the whole thing and set timers for it. That's just gonna give you a headache and you won't know what you did the next day.

    Step back and look at the problem. You've got some algorithm that operates on an array. Conceptually:


    Now you want to visualize the steps, conceptually:


    The problem is that the visualization should take time, so this approach is not gonna work. What you need to do is make it event driven, i.e. conceptually:

       stepEnded -> visualize
       visualizationEnded ->  done ? exit : doAnotherStep

    For this you can use the signal/slot mechanism of Qt. Make a class that does the algorithm and has a step method. At the end of the method emit a signal. Make another class for visualization and give it a visualize method. At the end of it emit a signal. Then just connect the two.

    Rough concept:

    class Algo : public QObject
        Algo(Vector<stuff>* data) : dataPtr(data) {}
        bool done() {  return /* check if more steps needed */ }
        void doStep() { /* do stuff to dataPtr */; emit stepFinished(someStuffToSwap, someOtherStuffToSwap);  }
       void stepFinished(stuff* item1, stuff* item2);
        Vector<stuff>* dataPtr;
    class Vis : public QObject
        Vis(Vector<stuff>* data) { /*create graphics items and whatnot */   }
        void visualizeStep(stuff* item1, stuff* item2) { /* do the animations here and emit finished() when they're done*/ }
       void finished();
    //and then you can use it like this:
    Vector<stuff> data = ...
    Algo algo(&data);
    Vis vis(&data);
    connect(algo, &Algo::stepFinished, vis, &Vis::visualizeStep);
    connect(vis, &Vis::finished(), [&]{ if(!algo.done()) algo.doStep(); });
    algo.doStep(); /start the first step

Log in to reply