PDA

View Full Version : Google Maps mousewheel zoom (How To)



alketi
20th May 2013, 19:48
In case this is of some use to others who desire a similar feature, here's some code to zoom in Qwt like Google Maps.

Behavior
The mousewheel in Google Maps, Photoshop, Bing Maps, Mapquest, and others, zooms toward the cursor location. This allows the user to hover over a point/area of interest and keep that point under the cursor when zooming in/out. This is highly desirable in a plotter as well, since it generally removes the need to subsequently pan the display.

Wheel direction
The wheel direction matches the zoom direction in Google Maps, Photoshop, Bing Maps, and Mapquest. Most everyone is familiar with one or more of these. If you prefer the current Qwt mousewheel direction, this can be easily reversed in the code.

Rolling mousewheel AWAY from you zooms IN
Rolling mousewheel TOWARD you zooms OUT

Implementation
I handled the mouse events entirely in a subclass of QwtPlot(), which fit my needs, but I'm sure there are many other different ways to do it. If there's clearly a better way, please let me know. :)

Aside from the event handling, the key part is the math algorithm to aim the zoom toward the mouse cursor. For this, I found various code snippets online and adjusted them for Qwt.

Issues
Occasionally when zooming in the underlying waveform will move slightly away from the mouse pointer. Judging by the behavior, I would guess that this is a result of the axis scale being adjusted to provide round numbers, and thus not delivering the exact zoom rectangle that was requested.

Cheers.

QwtPlot subclass

QwtPlotX::QwtPlotX( QWidget *widget )
: QwtPlot(widget), m_ctrlKeyDown(false)
{
setFocusPolicy(Qt::StrongFocus); // Needed to catch keyReleaseEvent()

// Set plot title
setCanvasBackground( Qt::white );
setAxisScale( QwtPlot::yLeft, 0.0, 10.0 );
insertLegend( new QwtLegend() );
setMinimumHeight(150);

// Grid
QPen pen;
pen.setColor(QColor(128, 128, 128, 128));//Qt::gray);
pen.setWidth(0);
pen.setStyle(Qt::DotLine);
QwtPlotGrid *trendGrid = new QwtPlotGrid();
trendGrid->setPen(pen);
trendGrid->attach(this);

// Zoom control
m_zoomer = new QwtPlotZoomerX( QwtPlot::xBottom, QwtPlot::yLeft, this->canvas() );

// Plot zooming with mouse wheel
this->canvas()->setMouseTracking(true);

// Install event filter to catch mouse presses
this->canvas()->installEventFilter(this);
}



bool QwtPlotX::eventFilter( QObject *object, QEvent *event )
{
if ( object == this->canvas() )
{
if (event->type() == QEvent::MouseButtonPress)
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if (mouseEvent->button() == Qt::LeftButton)
{
// qDebug() << "Left button";
}
if (mouseEvent->button() == Qt::MiddleButton)
{
// qDebug() << "Middle button";
}
if (mouseEvent->button() == Qt::RightButton)
{
// qDebug() << "Right button";
}
}
// Google Maps zoom
if (event->type() == QEvent::Wheel)
{
QMouseEvent *mEvent = (QMouseEvent *)event;
QWheelEvent *wEvent = (QWheelEvent *)event;
QPointF mousePos;
QPointF plotPos;
QRectF zoomRect;
float xw, yw, xhw, yhw, xz, yz;
float slideFactor;
float scaleFactor;

// Zoom IN
if (wEvent->delta() > 0)
{
scaleFactor = 1.2;
}
else
{
scaleFactor = 0.8;
}

// Mouse position
mousePos.setX(mEvent->x());
mousePos.setY(mEvent->y());

// How much to slide the zoom toward the mouse
slideFactor = 1.0 - (1.0 / scaleFactor);

// Get current zoom rectangle
zoomRect = m_zoomer->zoomRect();

// Calculate current widths
xw = zoomRect.width();
yw = zoomRect.height();

// Calculate mid-points (half-widths)
xhw = xw/2.0 + zoomRect.left();
yhw = yw/2.0 + zoomRect.top(); // Note: QRect top is actually the bottom of the plot.

// Calculate widths after zoom
xz = xw / scaleFactor;
yz = yw / scaleFactor;

// To zoom purely to the center
zoomRect.setLeft (zoomRect.left() +(xw - xz)/2.0);
zoomRect.setBottom(zoomRect.bottom()-(yw - yz)/2.0);
zoomRect.setRight (zoomRect.right() -(xw - xz)/2.0);
zoomRect.setTop (zoomRect.top() +(yw - yz)/2.0);

// Aim the zoom toward the mouse location
// Get distance of mouse from center
float mx = (this->invTransform(QwtPlot::xBottom, mousePos.x()) - xhw) * slideFactor;
float my = (this->invTransform(QwtPlot::yLeft, mousePos.y()) - yhw) * slideFactor;

// Correct zoom for mouse position
zoomRect.setLeft (zoomRect.left() + mx);
zoomRect.setRight (zoomRect.right() + mx);
zoomRect.setBottom(zoomRect.bottom()+ my);
zoomRect.setTop (zoomRect.top() + my);

m_zoomer->zoom(zoomRect);
}
}

return QwtPlot::eventFilter( object, event );
}

Seamus
20th May 2013, 22:30
Rolling mousewheel AWAY from you zooms IN
Rolling mousewheel TOWARD you zooms OUT
I also prefer this this mode of zooming. Its supported in Qwt already, just set a negative wheel factor with QwtMagnifier::setMouseFactor().

alketi
20th May 2013, 23:25
I also prefer this this mode of zooming. Its supported in Qwt already, just set a negative wheel factor with QwtMagnifier::setMouseFactor().
Thank you! I wasn't aware of that.