1

I am developing a desktop application which, due to complexity I had to break up in different parts. This code simply prints out wether you double clicked or single clicked on the (matplotlib) figure object. A simplified version is as below:

from PyQt5.QtWidgets import QMainWindow, QApplication
import sys
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure


class Window(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle('Matplotlib event handling')
        self.setGeometry(400, 400, 900, 500)
        canvas = Canvas(self, width=8, height=4)
        canvas.move(0, 0)

class Canvas(FigureCanvas):
    def __init__(self, parent=None, width=5, height=5, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)

        FigureCanvas.__init__(self, fig)
        self.setParent(parent)

        fig.canvas.mpl_connect('button_press_event', self.onclick)
        self.plot()

    def plot(self):
        x = [i for i in range(100)]
        y = [i**2 for i in range(100)]

        ax = self.figure.add_subplot(111)
        ax.plot(x,y, color='red')

    def onclick(self, event):
        if event.dblclick:
            print("You doubled clicked...")
        else:
            print('You single clicked...')


app = QApplication(sys.argv)
window = Window()
window.show()
app.exec()

The program works fine, but if I call the onclick function from another script, following error arises:

TypeError: onclick() missing 1 required positional argument: 'event'

I understand it has something to do with the scope of the function, but can anybody explain to me what am I doing wrong??

Both the scripts are as follows:

# script1.py

from PyQt5.QtWidgets import QMainWindow, QApplication, QPushButton
import sys
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from script2 import *

class Window(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle('Matplotlib event handling')
        self.setGeometry(400, 400, 900, 500)
        canvas = Canvas(self, width=8, height=4)
        canvas.move(0, 0)


class Canvas(FigureCanvas):
    def __init__(self, parent=None, width=5, height=5, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)

        FigureCanvas.__init__(self, fig)
        self.setParent(parent)

        fig.canvas.mpl_connect('button_press_event', onclick(self))
        self.plot()

    def plot(self):
        x = [i for i in range(100)]
        y = [i ** 2 for i in range(100)]

        ax = self.figure.add_subplot(111)
        ax.plot(x, y, color='red')


app = QApplication(sys.argv)
window = Window()
window.show()
app.exec()

And...

# script2.py


def onclick(self, event):
    if event.dblclick:
        print("You doubled clicked...")
    else:
        print('You single clicked...')

  • okay you are approaching this to complicated -- just simplify it change this to: `fig.canvas.mpl_connect(self.mplClicked)` then add that as a slot function `@pyqtSlot(object) def mplClicked(self, Event):` then within that function make a call to your new function `onclick(self, Event)` – Dennis Jensen Apr 06 '20 at 20:56

1 Answers1

4

onclick is now its own function separate from the class, so remove the self parameter.

def onclick(event):
   ...

And the signal in Canvas becomes

fig.canvas.mpl_connect('button_press_event', onclick)

If you want to pass the instance it makes more sense to keep onclick inside the Canvas class. Regardless, you can connect the signal in the same manner like this:

fig.canvas.mpl_connect('button_press_event', lambda event: onclick(self, event))

And define onclick:

def onclick(instance, event):
    print(instance)
    if event.dblclick:
        print("You doubled clicked...")
    else:
        print('You single clicked...')
alec
  • 5,799
  • 1
  • 7
  • 20
  • That will do, but in my actual program, I need to pass the instance of that class in which the figure is so I may manipulate some variables in that class. With the solution you proposed, how may I acheive that? – Asad Ullah Butt Apr 06 '20 at 20:40
  • 1
    @AsadUllahButt, you can get the `Canvas` class with `event.canvas` on your `onclick(event)` function – DarK_FirefoX Apr 06 '20 at 20:49
  • Hmm his error is he is missing a parameter and your solution is to remove a parameter ?? – Dennis Jensen Apr 06 '20 at 20:50
  • 1
    @DennisJensen, the OP question was "How to call onclick event from another script in matplotlib?", not how to pass another parameter to the function?. ;) – DarK_FirefoX Apr 06 '20 at 20:54
  • 1
    @DennisJensen Yeah, in the question his `onclick` function didn't make use of the `self` parameter, so it's logical to remove it. – alec Apr 06 '20 at 20:57
  • Well I disagree the issue is not as you claim but as I have outlined in the comment I made to the actual issue -- the issue is the complexity causing things not to be handled correctly and your approach does not actually solve the real issue -- or wait maybe it does since you edited it by adding even more complexity – Dennis Jensen Apr 06 '20 at 20:58
  • @DarK_FirefoX , You're a life saver man! Thanks alot! But I'm still confused about the usage of `event` . The same function `onclick(self, event)` does not work when it is called non-anonymously. But it does when it is called anonomously. How?? Where is this `event` varible coming from??? – Asad Ullah Butt Apr 06 '20 at 21:02
  • @AsadUllahButt, the `event` is comming from the connection made by `mpl_connect` which connects a signal to a slot. You can read this [answer](https://stackoverflow.com/a/42553806/5206417) for more information – DarK_FirefoX Apr 06 '20 at 22:01