PDA

View Full Version : Qt GraphicsScene performance compared to kivy



qtnewbie6019
10th April 2015, 00:02
Hi, here is a general problem I am struggling with

I am trying to create a graphicssceene that basically renders thousands of points in a zoomable, pannable graph,(think star field where each point represents a star/planet)
There are about 5000 points when fully zoomed out. User is allowed to interact with the items on the scene by zooming, and dragging or panning around. However it is quite choppy and slow as the number of points increase to several thousand.
I would like to make my application to be more responsive especially to user interaction(zoom,dragging and panning)

Currently it seems that the Qgraphicsscene/Qgraphicsview is not very performant when there are thousands of points that allow for user interaction.
I am comparing this with kivy (http://kivy.org/#home) and wondering if the Qgraphicsscene/Qgraphicsview is the correct technology/toolkit for doing what I want.
Is Qgraphicsscene/Qgraphicsview as powerful/performant as what is touted by kivy?

NOTE:
I'm experimenting with pyqt and python

wysota
10th April 2015, 06:10
Graphics View can easily handle thousands of objects. If you get poor performance then there is a reason to that.

Comparing kivy to Graphics View is probably like comparing apples to oranges. I suspect Qt Quick is more comparable to kivy. Nevertheless Graphics View will easily handle your current requirements if used properly.

anda_skoa
10th April 2015, 07:06
GraphicsView should be able to handle that.
Have a look at the "40000 Chips" example: http://doc.qt.io/qt-5/qtwidgets-graphicsview-chip-example.html

Cheers,
_

wysota
10th April 2015, 08:13
Here is an example with 100k points. Ctrl+mouse wheel to zoom.


#include <QtWidgets>

class View : public QGraphicsView {
public:
View(QWidget *parent = 0) : QGraphicsView(parent) {}
protected:
void wheelEvent(QWheelEvent *e) {
if(e->modifiers() & Qt::ControlModifier) {
if(e->delta() > 0) {
scale(1.2, 1.2);
} else if(e->delta() < 0) {
scale(0.8, 0.8);
}
} else QGraphicsView::wheelEvent(e);
}
};

int main(int argc, char **argv) {
QApplication app(argc, argv);
QGraphicsScene scene;
View view;
for(int i=0;i<100000;++i) {
QGraphicsEllipseItem *item = scene.addEllipse(QRectF(-1, -1, 2, 2));
item->setBrush(QColor(QColor::colorNames().at((qrand() % 20))));
item->setPos(qrand() % 4000, qrand() % 4000);
item->setFlag(QGraphicsItem::ItemIgnoresTransformations) ;
}
view.setScene(&scene);
view.show();
return app.exec();
}

d_stranz
10th April 2015, 16:42
a graphicssceene that basically renders thousands of points in a zoomable, pannable graph

I routinely create interactive scientific data scatterplots with thousands of individual QGraphicsItem instances. Zooming, panning, and other operations are nearly instantaneous.

I suspect that what you might be doing is destroying and recreating your scene each time it is zoomed or panned. This is wrong. You should create your scene once (or when there is a massive change in the underlying data) and use transformations on the view to change the region of the scene that is displayed at any one time. You do not have to do anything to the scene; simply set the sceneRect used by the view and the view will automatically update itself to show that part of the scene.

If you aren't changing the scene when zooming or panning, are you moving the scene items when panning or zooming? This is also wrong. Each move of a scene item is going to result in a repaint of the views where the item is displayed. If you are doing this for thousands of items, then you are repainting the view thousands of time for every zoom or pan operations. Again, if the scene items are fixed in world space, then all you have to do is change the region of that space shown in the view, by modifying the view's sceneRect.

qtnewbie6019
11th April 2015, 00:52
Hi, thanks for all the responses. and even the sample code. Really appreciate it,
I have had a brief look at "40000 Chips" example: http://doc.qt.io/qt-5/qtwidgets-grap...p-example.html
I think I forgot to mention one thing in my original question.
I am trying to create a graphicssceene that basically renders thousands of points in a zoomable, pannable graph,(think star field where each point represents a star/planet),User is allowed to interact with the items on the scene by zooming, and dragging or panning around

The part I forgot to highlight is that the user should be able to drag graphicsitems(stars/planets) around the scene, even select a number of graphicsitems(stars/planets) and be able to drag them.

This kind of interaction would mean that objects in the scene are not going to be static, the 40000 Chips example has a large number of objets but none of them move about much so its all static, so zooming and panning work great

What I want to do means the items on the scene are fully movable(dragable), and later if I choose to be really ambitious moving a selection of graphicsitems(stars/planets) around the scene may have a "springy" effect on graphicsitems(stars/planets) that are near the objects that have been dragged this means that moving a few objects may affect the position of a large number of surrounding/near-by objects.

So far in my experiment its fine if the total number of graphicsitems(stars/planets) is small, however when I have lots of them it seems that the performance drops. I have tried things like setting the ItemIndexMethod to BspTreeIndex, or NoIndex. I cannot say that I have seen the kind of dynamic speed updates that are shown on some of the kivy examples

So Is Qgraphicsscene/Qgraphicsview as powerful/performant enough to do this kind of dynamic panning,zooming and dragging?

Thanks

wysota
11th April 2015, 07:34
I am trying to create a graphicssceene that basically renders thousands of points in a zoomable, pannable graph,(think star field where each point represents a star/planet),User is allowed to interact with the items on the scene by zooming, and dragging or panning around
So how is that different from the code example I posted? You didn't bother to compile and run it, did you?

anda_skoa
11th April 2015, 07:51
The part I forgot to highlight is that the user should be able to drag graphicsitems(stars/planets) around the scene, even select a number of graphicsitems(stars/planets) and be able to drag them.

Which is also true for the 40000 chips demo.
You can drag any chip or selection of chips in any of the views. You can even do that if you rotate/zoom a view differently than the others, etc.
All views will show the updated scene.

Cheers,
_

qtnewbie6019
13th April 2015, 21:22
Hi thanks, I must apologies I haven't looked closely at the 40000 chips demo, I did play with it a while back and was impressed, I'm trying to get the pyqt version so I can have more of a play, I didn't realise that you could drag the chips around. I did see the zooming
However, I have found an example that's much closer to what I'm doing http://doc.qt.io/qt-5/qtwidgets-graphicsview-elasticnodes-example.html

This is the elastic nodes. I have got the pyqt version but in essence it works great, very fluid and dynamic, however I believe it will slow right down when there are 2000+ nodes and edges.

I suspect that


// Sum up all forces pushing this item away
qreal xvel = 0;
qreal yvel = 0;
foreach (QGraphicsItem *item, scene()->items()) {
Node *node = qgraphicsitem_cast<Node *>(item);
if (!node)
continue;

QPointF vec = mapToItem(node, 0, 0);
qreal dx = vec.x();
qreal dy = vec.y();
double l = 2.0 * (dx * dx + dy * dy);
if (l > 0) {
xvel += (dx * 150.0) / l;
yvel += (dy * 150.0) / l;
}
}

...


// Now subtract all forces pulling items together
double weight = (edgeList.size() + 1) * 10;
foreach (Edge *edge, edgeList) {
QPointF vec;
if (edge->sourceNode() == this)
vec = mapToItem(edge->destNode(), 0, 0);
else
vec = mapToItem(edge->sourceNode(), 0, 0);
xvel -= vec.x() / weight;
yvel -= vec.y() / weight;
}

...


QRectF sceneRect = scene()->sceneRect();
newPos = pos() + QPointF(xvel, yvel);
newPos.setX(qMin(qMax(newPos.x(), sceneRect.left() + 10), sceneRect.right() - 10));
newPos.setY(qMin(qMax(newPos.y(), sceneRect.top() + 10), sceneRect.bottom() - 10));
}

These for_each loops may be fine when the number of nodes isn't many but as the number of nodes grows that loop may become a performance issue. As the positions of each node as well as each edge needs recalculating. I hope this demonstrates better what I'm doing and where I am struggling. maybe it works better in C but in python/pyqt I bet its noticeably slower when the scene is busy. I have to confess that I haven't tried the elastic nodes with 100000 nodes though, can anyone say that its still fast and smooth?

wysota
13th April 2015, 21:43
Elastic nodes is in essence an n-body force simulation. It has no chance of being quick with its current implementation. The goal of the example was to demonstrate the use of itemChange(). Without knowing what you are doing it is impossible to say how close it really is to what you are doing. Are you doing an n-body force simulation?

d_stranz
14th April 2015, 02:31
Are you doing an n-body force simulation?

Yes, he is (or plans to):


What I want to do means the items on the scene are fully movable(dragable), and later if I choose to be really ambitious moving a selection of graphicsitems(stars/planets) around the scene may have a "springy" effect on graphicsitems(stars/planets) that are near the objects that have been dragged this means that moving a few objects may affect the position of a large number of surrounding/near-by objects.

This is an N^2 problem (I think) if you allow every object to "see" every other object. In practice, any force has to decrease with increasing distance between objects. For any two objects, you can calculate the distance at which this force becomes "negligible" (however you define that) and ignore any object if it is farther away from your target object than that distance. Note that you can precompute this distance for all pairs of objects - it has nothing to do with where the objects happen to be, it simply says that if they are closer than X, then you can't ignore the interaction.

You can also think about data structures that let you quickly determine the objects in the vicinity of the target object. This will help you avoid iterating over all pairs of objects in nested loops.

I suspect that the kivy example you are comparing Qt to uses some of these optimizations, which is why the performance seems so much better.

qtnewbie6019
14th April 2015, 07:47
Yes I guess what I am doing is an n-body force simulation. I didn't know there was a name for it. Thanks. Any articles/samples I should look at that may help me improve perfomance? looking at d_stranz comment I am drawn to some kind of dictionary data structure to store/look up positions. Does this sound like a good starting point?

wysota
14th April 2015, 09:44
Yes I guess what I am doing is an n-body force simulation. I didn't know there was a name for it. Thanks. Any articles/samples I should look at that may help me improve perfomance?

There are lots of articles and algorithms on the subject including doing the calculations on the GPU. This is totally unrelated to whether you are using Qt or not. I have an implementation that does what you want (more or less, it is for calculating graph node distribution) in Qt with quite decent performance using a number of algorithms (both 2D using Graphics View and 3D using OpenGL).