QGraphicsView repeatedly updated when inserting thousands of QGraphicsItems
-
Hi!
I’m writing an application based on the chip example to show (1) a background map and (2) a series of points over this map.
Both the image and the points are QGraphicItems. I use a QGraphicsScene and a QGraphicsView, in the same way the chip example in qt does.
The problem is that the number of points that I have to deal with may be very (very) big, even millions. My data (points, and later, when the problem I’ve got now is solved, also edges connecting the points) have been previously processed to make their handling easier. For instance, I have created a spatial index for the points, so it’s very fast to check what is the subset of points that fall inside some area, as for instance, a scene’s viewport.
Using the spatial index reduces a lot the number of points to deal with, specially if zooming out is limited, so only fragments of the whole image are visible.
Then, the way I update the scene (whenever a panning or zooming event occurs) is the following:
I remove everything in the scene (but the background image).
I retrieve the viewport.
Then I ask the spatial index for the set of points falling inside the viewport.
I insert (one by one) the points in the scene.This procedure could be improved, and I intend to do it, for instance checking what “old” points in the scene do not need to be deleted and reinserted because the change in the viewport didn’t left such points outside of such viewport. However, this is not the problem I’m experiencing, so I prefer to leave this improvement aside until I solve the current issue that’s jeopardizing the performance of my application.
Once I have defined what I’m doing (and what I’m not) I’ll go to the problem:
Inserting, let’s say 150.000 points in the scene produces, I guess, 150.000 updates of the view. That is, I think that adding sequentially a number of X of points that are included in the viewport makes the scene to send X changed() signals to whoever takes care of these signals, so my method paint is called all the time, ruining performance. I have checked this printing some messages each time I enter / leave the method implementing the procedure above. If there are no points in the viewport, the method is called once; if, on the contrary, there are points inside the viewport, the method is called all the time, and the CPU usage goes to the sky.
I have tried changing the way the view is updated, using the method named setViewportUpdateMode, setting its parameter to QGraphicsView::NoViewportUpdate before updating the points and then to QGraphicsView::SmartViewportUpdate once the procedure of inserting the points is over, but the situation has not changed. Therefore, I removed these calls from the view's paint method.
So I guess that my problem is that my view is being updated each time I remove or add a QGraphicsItem to my QgraphicsView. Do you agree?
I have included below the parts of my code that I think are relevant for the problem. If any of you can lend a hand, I’d be really grateful!
THANKS!
Bleriot.
The modified Paint() method in my QGraphicsView. Note that not the whole image is shown, but only the visible area (I use a tiled approach, this is done by method show_visible_image_window() called by the Paint() method.
void GraphicsView:: paintEvent (QPaintEvent* event ) { { // Update the background image only when such image is available. if (got_image_) show_visible_image_window(); // // Update the set of visible points only when a point dataset has // been loaded. // if (spatial_idx_ != nullptr) show_visible_items(); // Do whatever our parent class would do in this very same method. QGraphicsView::paintEvent (event); // Print how many times we've been called total_updates++; qDebug() << "This is update number " << total_updates; } }
The show_visible_items method I briefly described above.
void GraphicsView:: show_visible_items (void) { { double phigh[2]; double plow[2]; QGraphicsItem* point_item; QColor red_color = QColor(Qt::red); qDebug() << "Just entering show_visible_items"; // There's nothing we can do if we didn't got our spatial indexer... if (spatial_idx_ == nullptr) return; // Remove the items currently shown on the screen (but the image tiles!) QList<QGraphicsItem*> all = scene()->items(); for (int i = 0; i < all.size(); i++) { if (all.at(i)->type() != QGraphicsPixmapItem::Type) { QGraphicsItem *gi = all[i]; delete gi; } } // Retrieve the coordinates of the visible rectangle. QRectF viewport = mapToScene(QGraphicsView::viewport()->geometry()).boundingRect(); // // Change the format of the viewport limits so it is understood by the // spatial indexer. // plow[0] = viewport.left(); plow[1] = viewport.top(); phigh[0] = viewport.right(); phigh[1] = viewport.bottom(); // Get the items within this area! spatial_idx_visitor_.the_points_.clear(); Region r = Region(plow, phigh, 2); spatial_idx_->containsWhatQuery(r, spatial_idx_visitor_); // Insert the items in the scene. for (unsigned int i = 0; i < spatial_idx_visitor_.the_points_.size(); i++) { VEGrapher_point vepoint; vepoint = spatial_idx_visitor_.the_points_[i]; point_item = new Chip(red_color, 10); point_item->setPos(vepoint.col + 0.5, vepoint.row + 0.5); //point_item->set_id(vepoint.id); //point_item->set_color(red_color); scene()->addItem(point_item); } // Remove the items in our spatial index visitor to save some memory. spatial_idx_visitor_.the_points_.clear(); qDebug() << "Leaving show_visible_items"; } }
And below, you'll find both the .h and .cpp files for the "Chip" QGraphicsItems. The name is still the same used by QT's chip example (it should be renamed to "Point" or something like this).
The .hpp
#ifndef CHIP_H #define CHIP_H #include <QtWidgets> #include <QColor> #include <QGraphicsItem> #include "GraphicItemsUserDefinedTypes.hpp" class Chip : public QGraphicsItem { public: enum {Type = GraphicFixedSizePointType}; public: Chip (const QColor& color, int z_value); QRectF boundingRect (void) const override; QPainterPath shape (void) const override; int type (void) const override; void paint ( QPainter* painter, const QStyleOptionGraphicsItem* item, QWidget* widget) override; protected: void mousePressEvent (QGraphicsSceneMouseEvent* event) override; void mouseMoveEvent (QGraphicsSceneMouseEvent* event) override; void mouseReleaseEvent (QGraphicsSceneMouseEvent* event) override; private: QColor color; QVector<QPointF> stuff; }; #endif // CHIP_H
And the .cpp
#include "chip.h" Chip:: Chip (const QColor &color, int z_value) { { this->color = color; this->setZValue(z_value); setFlags(ItemIsSelectable); setAcceptHoverEvents(true); } } QRectF Chip:: boundingRect (void) const { { return QRectF(0, 0, 2, 2); } } QPainterPath Chip:: shape (void) const { { QPainterPath path; path.addEllipse(0, 0, 2, 2); return path; } } void Chip:: paint ( QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { { Q_UNUSED(widget) // The fill color depends on the state of the element (selected, hovered, none). QColor theColor = (option->state & QStyle::State_Selected) ? color.darker(150) : color; if (option->state & QStyle::State_MouseOver) theColor = theColor.lighter(125); painter->setPen(theColor); painter->setBrush(theColor); painter->drawEllipse(0, 0, 2, 2); return; } } void Chip:: mousePressEvent (QGraphicsSceneMouseEvent *event) { { QGraphicsItem::mousePressEvent(event); update(); } } void Chip:: mouseMoveEvent (QGraphicsSceneMouseEvent *event) { { if (event->modifiers() & Qt::ShiftModifier) { stuff << event->pos(); update(); return; } QGraphicsItem::mouseMoveEvent(event); } } void Chip:: mouseReleaseEvent (QGraphicsSceneMouseEvent *event) { { QGraphicsItem::mouseReleaseEvent(event); update(); } } int Chip:: type (void) const { { return Type; } }
-
Hi,
You should likely think video game like. Filter out what is not relevant for the scene. It's usually useless to show millions of points. You can't really extract information from that mass of data.
-
SGaist,
Unfortunately showing all this data is a must, since this piece of software is an analysis tool that an expert will use to decide how to proceed later on. Points (and later, connections between points) must be fully shown.
Isn't there a way to PAUSE the update process while inserting the points and then RESTART it again once the insertion is over?
Thanks!
-
@bleriot13 How is the expert going to see anything on the screen if you show millions points with connections? A typical display can show 2-8 millions pixels and if you add connections between those points you will end up with a mess where even an expert can't see anything.
-
@jsulm ,
The filtering that SGaist suggested is somehow implemented by the spatial index I talked about in my first post.
Whenever the a pan or zoom event takes place, my software retrieves the viewport currently shown and asks the index for the list of points included there. So, in fact, I'm never showing millions of points but some thousands. Connections will be shown ONLY for the point that is clicked, so maybe 15-30 lines will be drawn additionally each time. Clicking on a different point will remove the connections of the old point and draw those of the new one.
However, even painting a few thousands of points is a nightmare because of the changed() signal sent by the scene. Each time a point is inserted, changed() is emitted, so an update takes place, which means that thousands of updates are executed instead of a single one.
However, I think that I have solved (?) the problem connecting / disconnecting the scene's changed() signal at the beginning and end of my GraphicsView's show_visible_items() method. The connection uses a slot that simply returns, so no extra update takes place each time an additional point is inserted. When all points have been inserted, that is, at the end of the method, I undo the connection so everything goes back to normal.
This seems to work pretty well. Now the number of updates has been drastically reduced. See the code below:
void GraphicsView:: show_visible_items (void) { { double phigh[2]; double plow[2]; QGraphicsItem* point_item; QColor red_color = QColor(Qt::red); connect (scene(), SIGNAL(changed(const QList<QRectF>)), this , SLOT(ignore_changes(const QList<QRectF>))); //qDebug() << "Just entering show_visible_items"; // There's nothing we can do if we didn't got our spatial indexer... if (spatial_idx_ == nullptr) return; // Remove the items currently shown on the screen (but the image tiles!) QList<QGraphicsItem*> all = scene()->items(); for (int i = 0; i < all.size(); i++) { if (all.at(i)->type() != QGraphicsPixmapItem::Type) { QGraphicsItem *gi = all[i]; delete gi; } } // Retrieve the coordinates of the visible rectangle. QRectF viewport = mapToScene(QGraphicsView::viewport()->geometry()).boundingRect(); // // Change the format of the viewport limits so it is understood by the // spatial indexer. // plow[0] = viewport.left(); plow[1] = viewport.top(); phigh[0] = viewport.right(); phigh[1] = viewport.bottom(); // Get the items within this area! spatial_idx_visitor_.the_points_.clear(); Region r = Region(plow, phigh, 2); spatial_idx_->containsWhatQuery(r, spatial_idx_visitor_); // Insert the items in the scene. for (unsigned int i = 0; i < spatial_idx_visitor_.the_points_.size(); i++) { VEGrapher_point vepoint; vepoint = spatial_idx_visitor_.the_points_[i]; point_item = new Chip(red_color, 10); point_item->setPos(vepoint.col + 0.5, vepoint.row + 0.5); //point_item->set_id(vepoint.id); //point_item->set_color(red_color); scene()->addItem(point_item); } // Remove the items in our spatial index visitor to save some memory. //if ( spatial_idx_visitor_.the_points_.size() > 0) qDebug() << spatial_idx_visitor_.the_points_.size(); spatial_idx_visitor_.the_points_.clear(); disconnect (scene(), SIGNAL(changed(const QList<QRectF>))); //qDebug() << "Leaving show_visible_items"; } }
The slot used to intercept the changed() signal is a very simple (noop) one:
void GraphicsView:: ignore_changes (const QList<QRectF>) { { return; } }
Remember that show_visible_items() was called by the PaintEvent() method in the GraphicsView (the code may be found earlier in this post).
So this seems to solve the problem! If anyone else may suggest a better solution I'd be happy to implement it.
Regards,
Bleriot.
-
Does each point have to be an individual QGraphicsItem? Each item has an overhead. If you could group points together, that might be more efficient.
Also, instead of deleting and creating items, hide and re-use them.
-
thanks for your answer!!!!
Yes, points must be QGraphicsItems since I need to be able to detect mouse events affecting them.
I prefer not to hide / show items instead of creating / destroying these because inserting MILLIONS of points will, most probably, crash my application because of lack of memory.
I have to say that I'm pretty satisfied with the solution I came up with. Shortcircuiting the changed() signal temporarily works like a charm. I think however, that this is not, maybe, the most elegant situation...
Thanks again for your interest!
-
@wujie
If your situation is " the same as yours [OP's]" then you are showing too many points, as several responders said.If you want to "add a batch of many items" you could temporarily detach the
changed()
slot as the OP wrote, or callblockSignals()
.Having said that I'm not sure what the OP's actual issue was. While it is true that
changed()
would be called many times it is not true that each of these causes any visible update to the view. The view is only visually updated the next time the event loop is entered, at which time it should see so many updates that it does not do each one at a time but rather just decides to redraw the view a single time.