pyqtgraph mouse interaction: using mouse right or middle button to zoom to the selected box

Viewed 605

in pyqtgraph examples -> View Box Freatures, we could use mouse interaction "left drag zoom to box" by setting v2.setMouseMode(v2.RectMode)

enter image description here

But this interaction is only available in one-button mouse mode. How could I modify the interaction, so that I can in standard three-button mouse mode use right mouse button or middle mouse button to zoom to the selected box?

Thank you all!

1 Answers

The ViewBox class of pyqtgraph has only two "mouse modes":

  • PanMode: Left and Middle mouse work for drag. Right mouse work for zoom in/out by drag.
  • RectMode: Left and Middle mouse work to zoom the box. Right mouse work for zoom in/out by drag.

The reason why Left and Middle mouse works the same lays in the source code of the ViewBox class. You want the Left mouse to drag the picture, the Middle mouse work to zoom the box and the Middle mouse work to zoom the box for zoom in/out by drag. My solution may be radical, but it works. I modified the class, using the source code as a model (source : Pyqtgraph's ViewBox class):

import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np

class ViewBox(pg.ViewBox):
    MyMode = 2
    def __init__(self):
        pg.ViewBox.__init__(self)
        
    def setMouseMode(self, mode):
        ## First we change this method to accept our new configuration
        if mode not in [ViewBox.PanMode, ViewBox.RectMode, ViewBox.MyMode]:
            raise Exception("Mode must be ViewBox.PanMode, ViewBox.RectMode or ViewBox.MyMode")
        self.state['mouseMode'] = mode
        self.sigStateChanged.emit(self)

    def mouseDragEvent(self, ev, axis=None):
        ev.accept()
        pos = ev.pos()
        lastPos = ev.lastPos()
        dif = pos - lastPos
        dif = dif * -1
        mouseEnabled = np.array(self.state['mouseEnabled'], dtype=np.float)
        mask = mouseEnabled.copy()
        if axis is not None:
            mask[1-axis] = 0.0

        ## Here is the main change for it to work    
        if ev.button() & QtCore.Qt.LeftButton:
            if self.state['mouseMode'] == ViewBox.RectMode and axis is None:
                if ev.isFinish():  ## This is the final move in the drag; change the view scale now
                    #print "finish"
                    self.rbScaleBox.hide()
                    ax = QtCore.QRectF(QtCore.QPointF(ev.buttonDownPos(ev.button())), QtCore.QPointF(pos))
                    ax = self.childGroup.mapRectFromParent(ax)
                    self.showAxRect(ax)
                    self.axHistoryPointer += 1
                    self.axHistory = self.axHistory[:self.axHistoryPointer] + [ax]
                else:
                    ## update shape of scale box
                    self.updateScaleBox(ev.buttonDownPos(), ev.pos())
            else:
                tr = self.childGroup.transform()
                tr = pg.functions.invertQTransform(tr)
                tr = tr.map(dif*mask) - tr.map(QtCore.QPointF(0,0))
                x = tr.x() if mask[0] == 1 else None
                y = tr.y() if mask[1] == 1 else None
                self._resetTarget()
                if x is not None or y is not None:
                    self.translateBy(x=x, y=y)
                self.sigRangeChangedManually.emit(self.state['mouseEnabled'])
        elif ev.button() & QtCore.Qt.MidButton:
            if self.state['mouseMode'] in {ViewBox.RectMode,ViewBox.MyMode} and axis is None:
                if ev.isFinish():  ## This is the final move in the drag; change the view scale now
                    #print "finish"
                    self.rbScaleBox.hide()
                    ax = QtCore.QRectF(QtCore.QPointF(ev.buttonDownPos(ev.button())), QtCore.QPointF(pos))
                    ax = self.childGroup.mapRectFromParent(ax)
                    self.showAxRect(ax)
                    self.axHistoryPointer += 1
                    self.axHistory = self.axHistory[:self.axHistoryPointer] + [ax]
                else:
                    ## update shape of scale box
                    self.updateScaleBox(ev.buttonDownPos(), ev.pos())
            else:
                tr = self.childGroup.transform()
                tr = pg.functions.invertQTransform(tr)
                tr = tr.map(dif*mask) - tr.map(QtCore.QPointF(0,0))
                x = tr.x() if mask[0] == 1 else None
                y = tr.y() if mask[1] == 1 else None
                self._resetTarget()
                if x is not None or y is not None:
                    self.translateBy(x=x, y=y)
                self.sigRangeChangedManually.emit(self.state['mouseEnabled'])
        elif ev.button() & QtCore.Qt.RightButton:
            if self.state['aspectLocked'] is not False:
                mask[0] = 0
            dif = ev.screenPos() - ev.lastScreenPos()
            dif = np.array([dif.x(), dif.y()])
            dif[0] *= -1
            s = ((mask * 0.02) + 1) ** dif
            tr = self.childGroup.transform()
            tr = pg.functions.invertQTransform(tr)
            x = s[0] if mouseEnabled[0] == 1 else None
            y = s[1] if mouseEnabled[1] == 1 else None
            center = QtCore.QPointF(tr.map(ev.buttonDownPos(QtCore.Qt.RightButton)))
            self._resetTarget()
            self.scaleBy(x=x, y=y, center=center)
            self.sigRangeChangedManually.emit(self.state['mouseEnabled'])

We inherit Pyqtgraph's ViewBox class, and then change two methods:

  • setMouseMode() for compatibility
  • mouseDragEvent for the different function of the 3 clicks

Whit the class above you maintain the other two mouse modes as they are, but adding a new mode: MyMode.

Then instead of adding the ViewBox like this :

sub2 = win.addLayout()
sub2.addLabel("<b>One-button mouse interaction:</b><br>left-drag zoom to box, wheel to zoom out.")
sub2.nextRow()
v2 = sub2.addViewBox()
v2.setMouseMode(v2.RectMode)
l2 = pg.PlotDataItem(y)
v2.addItem(l2)

You should do it like this:

sub2 = win.addLayout()
sub2.addLabel("<b>One-button mouse interaction:</b><br>left-drag zoom to box, wheel to zoom out.")
sub2.nextRow()
v2 = ViewBox() ## This is our new class
sub2.addItem(v2)
v2.setMouseMode(v2.MyMode) ## Seting the new Mouse Mode
l2 = pg.PlotDataItem(y)
v2.addItem(l2)

I tested the code and did not found any issues, but I have to admit that the solution is a little bit exaggerated. I hope there is a simpler method.

Edit: The example is taken from Pyqtgraph's examples (ViewBox Features) (Here)

Related