2

I'm building an application using matplotlib version 1.2.1, Qt4.7, python 2.7.

I want to modify the matplotlib toolbar to add another checkable button which sets a mode for selecting data points in response to a pick event. It almost all works, except that the pick event is not sent when the zoom or pan navigation buttons are selected.

What I am looking for is a way to programmatically turn off pan and zoom modes. I thought I would be able to do this by setting the checked state of the toolbar pan and zoom buttons to False. This appears to work (for example if the zoom button is checked, setting to false makes it appear to be unchecked). However it does not change the mode of the canvas - the cursor is still the zoom cursor, and the pick event doesn't fire.

The following code demonstrates this:

import sys, os, math
from PyQt4.QtCore import *
from PyQt4.QtGui import *

import matplotlib
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg
from matplotlib.figure import Figure

class NavigationToolbar( NavigationToolbar2QTAgg ):

    picked=pyqtSignal(int,name='picked')

    def __init__(self, canvas, parent ):
        NavigationToolbar2QTAgg.__init__(self,canvas,parent)
        self.clearButtons=[]
        # Search through existing buttons
        # next use for placement of custom button
        next=None
        for c in self.findChildren(QToolButton):
            if next is None:
                next=c
            # Don't want to see subplots and customize
            if str(c.text()) in ('Subplots','Customize'):
                c.defaultAction().setVisible(False)
                continue
            # Need to keep track of pan and zoom buttons
            # Also grab toggled event to clear checked status of picker button
            if str(c.text()) in ('Pan','Zoom'):
                c.toggled.connect(self.clearPicker)
                self.clearButtons.append(c)
                next=None

        # create custom button
        pm=QPixmap(32,32)
        pm.fill(QApplication.palette().color(QPalette.Normal,QPalette.Button))
        painter=QPainter(pm)
        painter.fillRect(6,6,20,20,Qt.red)
        painter.fillRect(15,3,3,26,Qt.blue)
        painter.fillRect(3,15,26,3,Qt.blue)
        painter.end()
        icon=QIcon(pm)
        picker=QAction("Pick",self)
        picker.setIcon(icon)
        picker.setCheckable(True)
        picker.setToolTip("Pick data point")
        self.picker = picker
        button=QToolButton(self)
        button.setDefaultAction(self.picker)

        # Add it to the toolbar, and connect up event
        self.insertWidget(next.defaultAction(),button)
        picker.toggled.connect(self.pickerToggled)

        # Grab the picked event from the canvas
        canvas.mpl_connect('pick_event',self.canvasPicked)

    def clearPicker( self, checked ):
        if checked:
            self.picker.setChecked(False)

    def pickerToggled( self, checked ):
        if checked:
            for c in self.clearButtons:
                c.defaultAction().setChecked(False)
            self.set_message('Reject/use observation')

    def canvasPicked( self, event ):
        if self.picker.isChecked():
            self.picked.emit(event.ind)

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        self.x=[i*0.1 for i in range(30)]
        self.y=[math.sin(x) for x in self.x]
        self.picked=[]
        self.setWindowTitle('Custom toolbar')

        self.frame = QWidget()

        self.fig = Figure((4.0, 4.0))
        self.canvas = FigureCanvasQTAgg(self.fig)
        self.canvas.setParent(self.frame)

        self.axes = self.fig.add_subplot(111)

        # Create the navigation toolbar, tied to the canvas
        # and link the clicked event
        self.toolbar = NavigationToolbar(self.canvas, self.frame)
        self.toolbar.picked.connect(self.addPoint)

        vbox = QVBoxLayout()
        vbox.addWidget(self.canvas)
        vbox.addWidget(self.toolbar)
        self.frame.setLayout(vbox)
        self.setCentralWidget(self.frame)
        self.draw()

    def draw(self):
        while self.axes.lines:
            self.axes.lines[0].remove()
        self.axes.plot(self.x,self.y,'b+',picker=5)
        xp=[self.x[i] for i in self.picked] 
        yp=[self.y[i] for i in self.picked] 
        self.axes.plot(xp,yp,'rs')
        self.canvas.draw()

    def addPoint(self,index):
        if index in self.picked:
            self.picked.remove(index)
        else:
            self.picked.append(index)
        self.draw()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    form = MainWindow()
    form.show()
    app.exec_()

1 Answer 1

6

If you read the source code of NavigationToolbar2QT, you can find that:

  1. _active is the current state of pan and zoom.
  2. call pan(), zoom() method will toggle the state.

So, here is the code that disable pan & zoom:

def pickerToggled( self, checked ):
    if checked:            
        if self._active == "PAN":
            self.pan()
        elif self._active == "ZOOM":
            self.zoom()
        self.set_message('Reject/use observation')
Sign up to request clarification or add additional context in comments.

1 Comment

Brilliant ... thanks. I did just find this before I saw your comment :-| Also found out why my approach wasn't working in Qt docs for QAction, which is that these are firing of triggered event, and that is not emitted in response to set checked. I could probably have used "if button.isChecked(): button.defaultAction().trigger(). But I like yours better.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.