Faster graph zoom by using a snapshot-preview (#1728)

* Faster graph zoom by using a snapshot-preview

* Added "Fast Zooming..." menu option

---------

Co-authored-by: matlabbe <matlabbe@gmail.com>
This commit is contained in:
Pierre ELIS
2026-07-05 22:06:08 +02:00
committed by GitHub
parent d069becc72
commit e321999b15
2 changed files with 165 additions and 20 deletions

View File

@@ -33,6 +33,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <QGraphicsView>
#include <QtCore/QMap>
#include <QtCore/QSettings>
#include <QTimer>
#include <QGraphicsPixmapItem>
#include <rtabmap/core/Link.h>
#include <rtabmap/core/GPS.h>
#include <opencv2/opencv.hpp>
@@ -130,6 +132,7 @@ public:
bool isOrientationENU() const;
ViewPlane getViewPlane() const;
bool isEnsureFrameVisible() const;
int getFastZoomMinNodes() const;
// setters
void setWorkingDirectory(const QString & path);
@@ -170,6 +173,7 @@ public:
void setOrientationENU(bool enabled);
void setViewPlane(ViewPlane plane);
void setEnsureFrameVisible(bool visible);
void setFastZoomMinNodes(int value);
Q_SIGNALS:
void configChanged();
@@ -184,11 +188,14 @@ protected:
virtual void wheelEvent ( QWheelEvent * event );
virtual void mouseMoveEvent(QMouseEvent * event);
virtual void mousePressEvent(QMouseEvent * event);
virtual void mouseReleaseEvent(QMouseEvent * event);
virtual void contextMenuEvent(QContextMenuEvent * event);
private:
void setupGraphicsScene();
void startZoomOverlay();
void updateZoomOverlay();
void stopZoomOverlay();
private:
QString _workingDirectory;
QColor _nodeColor;
@@ -248,6 +255,11 @@ private:
ViewPlane _viewPlane;
bool _ensureFrameVisible;
QPoint _previousMousePos;
QPoint _initialMousePos;
QTimer _zoomDebounceTimer;
QGraphicsPixmapItem * _zoomOverlayItem;
bool _zoomOverlayActive;
int _fastZoomMinNodes;
};
} /* namespace rtabmap */

View File

@@ -32,6 +32,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <QGraphicsScene>
#include <QGraphicsEllipseItem>
#include <QGraphicsRectItem>
#include <QGraphicsPixmapItem>
#include <QtGui/QWheelEvent>
#include <QGraphicsSceneHoverEvent>
#include <QMenu>
@@ -341,10 +342,18 @@ GraphViewer::GraphViewer(QWidget * parent) :
_orientationENU(false),
_mouseTracking(false),
_viewPlane(XY),
_ensureFrameVisible(true)
_ensureFrameVisible(true),
_previousMousePos(-1,-1),
_initialMousePos(-1,-1),
_zoomDebounceTimer(this),
_zoomOverlayItem(0),
_zoomOverlayActive(false),
_fastZoomMinNodes(0)
{
this->setDragMode(QGraphicsView::ScrollHandDrag);
_workingDirectory = QDir::homePath();
_zoomDebounceTimer.setSingleShot(true);
connect(&_zoomDebounceTimer, &QTimer::timeout, this, &GraphViewer::stopZoomOverlay);
setupGraphicsScene();
@@ -355,6 +364,9 @@ GraphViewer::GraphViewer(QWidget * parent) :
this->restoreDefaults();
this->fitInView(this->sceneRect(), Qt::KeepAspectRatio);
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
setResizeAnchor(QGraphicsView::AnchorUnderMouse);
}
GraphViewer::~GraphViewer()
@@ -363,11 +375,20 @@ GraphViewer::~GraphViewer()
void GraphViewer::setupGraphicsScene()
{
_zoomDebounceTimer.stop();
_zoomOverlayActive = false;
_zoomOverlayItem = 0;
if(this->scene())
{
delete this->scene();
}
this->setScene(new QGraphicsScene(this));
_zoomOverlayItem = this->scene()->addPixmap(QPixmap());
_zoomOverlayItem->setZValue(1000000);
_zoomOverlayItem->setVisible(false);
_zoomOverlayItem->setAcceptedMouseButtons(Qt::NoButton);
_zoomOverlayItem->setAcceptHoverEvents(false);
_world = (QGraphicsItem *)this->scene()->addEllipse(QRectF(-0.0001,-0.0001,0.0001,0.0001));
_root = (QGraphicsItem *)this->scene()->addEllipse(QRectF(-0.0001,-0.0001,0.0001,0.0001));
_root->setParentItem(_world);
@@ -507,6 +528,62 @@ void GraphViewer::setupGraphicsScene()
}
void GraphViewer::startZoomOverlay()
{
if(_fastZoomMinNodes==0 || _nodeItems.size() < _fastZoomMinNodes)
{
return;
}
if(_zoomOverlayActive || !_zoomOverlayItem)
{
return;
}
const QRect viewportRect = this->viewport()->rect();
if(viewportRect.isEmpty())
{
return;
}
const QPixmap snapshot = this->viewport()->grab();
if(snapshot.isNull())
{
return;
}
const QRectF sourceRect = this->mapToScene(viewportRect).boundingRect();
const qreal sx = sourceRect.width() / qreal(snapshot.width());
const qreal sy = sourceRect.height() / qreal(snapshot.height());
_zoomOverlayItem->setPixmap(snapshot);
_zoomOverlayItem->setTransform(QTransform::fromScale(sx, sy));
_zoomOverlayItem->setPos(sourceRect.topLeft());
_zoomOverlayItem->show();
_world->setVisible(false);
_zoomOverlayActive = true;
}
void GraphViewer::stopZoomOverlay()
{
_zoomDebounceTimer.stop();
if(!_zoomOverlayActive)
{
return;
}
_zoomOverlayActive = false;
if(_zoomOverlayItem)
{
_zoomOverlayItem->hide();
}
if(_world)
{
_world->setVisible(true);
}
this->viewport()->update();
}
void GraphViewer::setWorldMapRotation(const float & theta)
{
_worldMapRotation = theta;
@@ -1432,6 +1509,7 @@ void GraphViewer::clearNodeColorByValue()
void GraphViewer::clearAll()
{
stopZoomOverlay();
clearMap();
clearGraph();
@@ -1489,6 +1567,7 @@ void GraphViewer::saveSettings(QSettings & settings, const QString & group) cons
settings.setValue("orientation_ENU", this->isOrientationENU());
settings.setValue("view_plane", (int)this->getViewPlane());
settings.setValue("ensure_frame_visible", (int)this->isEnsureFrameVisible());
settings.setValue("fast_zoom_min_nodes", this->getFastZoomMinNodes());
if(!group.isEmpty())
{
settings.endGroup();
@@ -1540,6 +1619,7 @@ void GraphViewer::loadSettings(QSettings & settings, const QString & group)
this->setOrientationENU(settings.value("orientation_ENU", this->isOrientationENU()).toBool());
this->setViewPlane((ViewPlane)settings.value("view_plane", (int)this->getViewPlane()).toInt());
this->setEnsureFrameVisible(settings.value("ensure_frame_visible", this->isEnsureFrameVisible()).toBool());
this->setFastZoomMinNodes(settings.value("fast_zoom_min_nodes", this->getFastZoomMinNodes()).toInt());
if(!group.isEmpty())
{
settings.endGroup();
@@ -1602,6 +1682,10 @@ bool GraphViewer::isEnsureFrameVisible() const
{
return _ensureFrameVisible;
}
int GraphViewer::getFastZoomMinNodes() const
{
return _fastZoomMinNodes;
}
void GraphViewer::setWorkingDirectory(const QString & path)
{
@@ -1967,6 +2051,11 @@ void GraphViewer::setEnsureFrameVisible(bool visible)
_ensureFrameVisible = visible;
}
void GraphViewer::setFastZoomMinNodes(int value)
{
_fastZoomMinNodes = value;
}
void GraphViewer::restoreDefaults()
{
@@ -1997,18 +2086,26 @@ void GraphViewer::restoreDefaults()
setGlobalPathVisible(true);
setLocalPathVisible(true);
setGtGraphVisible(true);
setFastZoomMinNodes(0);
}
void GraphViewer::wheelEvent ( QWheelEvent * event )
void GraphViewer::wheelEvent(QWheelEvent * event)
{
if(event->angleDelta().y() < 0)
if(event->angleDelta().y() == 0)
{
this->scale(0.95, 0.95);
}
else
{
this->scale(1.05, 1.05);
event->ignore();
return;
}
// Start buffering zoom interaction with a raster snapshot.
startZoomOverlay();
const qreal factor = event->angleDelta().y() < 0 ? 0.95 : 1.05;
this->scale(factor, factor);
// When wheel stops for 400 ms, restore the real scene and let it redraw once.
_zoomDebounceTimer.start(400);
event->accept();
}
void GraphViewer::mouseMoveEvent(QMouseEvent * event)
@@ -2029,14 +2126,16 @@ void GraphViewer::mouseMoveEvent(QMouseEvent * event)
if (event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier) && event->buttons() & Qt::LeftButton) {
// same modifiers than 3D view, change zoom
if(_previousMousePos.y()!=0) {
if(event->pos().y() - _previousMousePos.y() > 0)
{
this->scale(0.98, 0.98);
}
else
{
this->scale(1.02, 1.02);
}
qreal factor = (event->pos().y() - _previousMousePos.y() > 0) ? 0.98 : 1.02;
QGraphicsView::ViewportAnchor oldAnchor = this->transformationAnchor();
this->setTransformationAnchor(QGraphicsView::NoAnchor);
QPointF scenePointBefore = this->mapToScene(_initialMousePos);
this->scale(factor, factor);
QPointF scenePointAfter = this->mapToScene(_initialMousePos);
QPointF delta = scenePointAfter - scenePointBefore;
this->translate(delta.x(), delta.y());
this->setTransformationAnchor(oldAnchor);
}
_previousMousePos = event->pos();
return;
@@ -2047,11 +2146,21 @@ void GraphViewer::mouseMoveEvent(QMouseEvent * event)
void GraphViewer::mousePressEvent(QMouseEvent * event)
{
QGraphicsItem *item = this->scene()->itemAt(mapToScene(event->pos()), QTransform());
if((event->button() == Qt::LeftButton) &&
(event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier)))
{
startZoomOverlay();
_previousMousePos = event->pos();
_initialMousePos = event->pos();
event->accept();
return;
}
QGraphicsItem * item = this->scene()->itemAt(mapToScene(event->pos()), QTransform());
if(item)
{
NodeItem *nodeItem = qgraphicsitem_cast<NodeItem*>(item);
LinkItem *linkItem = qgraphicsitem_cast<LinkItem*>(item);
NodeItem * nodeItem = qgraphicsitem_cast<NodeItem*>(item);
LinkItem * linkItem = qgraphicsitem_cast<LinkItem*>(item);
if(nodeItem && nodeItem->parentItem() == _graphRoot && nodeItem->id() != 0)
{
Q_EMIT nodeSelected(nodeItem->id());
@@ -2071,6 +2180,19 @@ void GraphViewer::mousePressEvent(QMouseEvent * event)
}
}
void GraphViewer::mouseReleaseEvent(QMouseEvent * event)
{
_previousMousePos = QPoint(-1, -1);
_initialMousePos = QPoint(-1, -1);
if(_zoomOverlayActive)
{
stopZoomOverlay();
}
QGraphicsView::mouseReleaseEvent(event);
}
QIcon createIcon(const QColor & color)
{
QPixmap pixmap(50, 50);
@@ -2167,6 +2289,7 @@ void GraphViewer::contextMenuEvent(QContextMenuEvent * event)
QAction * aShowHideOdomCacheOverlay;
QAction * aOrientationENU;
QAction * aMouseTracking;
QAction * aFastZooming;
QAction * aViewPlaneXY;
QAction * aViewPlaneXZ;
QAction * aViewPlaneYZ;
@@ -2269,6 +2392,7 @@ void GraphViewer::contextMenuEvent(QContextMenuEvent * event)
aMouseTracking->setCheckable(true);
aMouseTracking->setChecked(_mouseTracking);
aMouseTracking->setEnabled(_viewPlane == XY);
aFastZooming = menu.addAction(tr("Fast Zooming..."));
aShowHideGraph->setEnabled(_viewPlane == XY);
aShowHideGlobalPath->setEnabled(_globalPathLinkItems.size());
aShowHideLocalPath->setEnabled(_localPathLinkItems.size());
@@ -2714,6 +2838,15 @@ void GraphViewer::contextMenuEvent(QContextMenuEvent * event)
{
_mouseTracking = aMouseTracking->isChecked();
}
else if(r == aFastZooming)
{
bool ok;
double value = QInputDialog::getInt(this, tr("Fast Zooming"), tr("Minimum number of nodes to raster the graph\nwhile zooming (0=disabled):"), _fastZoomMinNodes, 0, 2147483647, 100, &ok);
if(ok)
{
setFastZoomMinNodes(value);
}
}
else if(r == aViewPlaneXY)
{
this->setViewPlane(XY);