4

I have Qt application where I draw graphs using pyqtgraph lib and use dock containers also from pyqtgraph. So I create several dock containers and put one graph in each. Then, in my code I need to identify the graph with which user is working to update some other staff in the application (e.g. to show some properties for "active" graph in some common for all widget).

My idea is to receive mouse click signal and identify the graph where it was clicked to know the last graph user interacted. But pyqtgraph PlotWidget, PlotItem or ViewBox classes don't provide such a signals and I don't know if there is a way to implement it myself. Also, I didn't find a way to identify which dock container is active. I only see sigMouseReleased for the PlotWidget but even this doesn't work for me (see code below)

Here is my minimum code:

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import pyqtgraph as pg
from pyqtgraph.dockarea import *

# I use Qt Designer, below I just cut generated code to minimum
class Ui_StartForm(object):
    def setupUi(self, StartForm):
        StartForm.setObjectName("StartForm")
        StartForm.resize(1507, 968)
        self.GraphLayout = QtWidgets.QGridLayout(StartForm)

# my application
class AppWindow(QtWidgets.QWidget, Ui_StartForm):
    def __init__(self):
        super(AppWindow, self).__init__()
        self.setupUi(self)

        self.dock_area_main = DockArea()
        self.GraphLayout.addWidget(self.dock_area_main)

        self.Dock1 = Dock("Dock 1", size=(1, 1))
        self.dock_area_main.addDock(self.Dock1, 'left')

        self.Dock2 = Dock("Dock 2", size=(1, 1))
        self.dock_area_main.addDock(self.Dock2, 'right')

        self.GraphViewList = []

        self.pl1 = pg.PlotWidget()
        self.pl2 = pg.PlotWidget()

        self.Dock1.addWidget(self.pl1)
        self.Dock2.addWidget(self.pl2)

        self.pl1.sigMouseReleased.connect(self.mouse_release) # try to get some mouse event

    def mouse_release(self):
        print('click')   # never execute


app = QtWidgets.QApplication(sys.argv)
w = AppWindow()
w.show()
sys.exit(app.exec_())

My question is how can I implement signal mouse clicked for pyqtgraph PlotItem or ViewBox to identify which graph was last interacted by user. Same time, it shouldn't influence functions of pyqtgraph plots (it should catch all mouse events normally)

If there is better strategy to do so - please suggest

Ostap
  • 119
  • 2
  • 9

3 Answers3

8

PyQtGraph does implement a sigMouseClicked signal in the GraphicsScene class, but somehow this is undocumented. The GraphicsScene page only explains about why it implements a parallel mouse event system, but if you look at the source you see that it also emits some handy signals.

Since these are undocumented you should use them at your own risk! Perhaps they will change in the future, although I think that's unlikely. Or maybe you can open an issue and ask for them to be officially supported.

The signal has the originating mouse event as a parameter. There is no reference to the plot that was clicked, but if you can resolve this by overriding the pg.PlotWidget and connecting to a slot of that derived class. Like so...

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import pyqtgraph as pg
from pyqtgraph.dockarea import *

# I use Qt Designer, below I just cut generated code to minimum
class Ui_StartForm(object):
    def setupUi(self, StartForm):
        StartForm.setObjectName("StartForm")
        StartForm.resize(1507, 968)
        self.GraphLayout = QtWidgets.QGridLayout(StartForm)


class MyPlotWidget(pg.PlotWidget):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        # self.scene() is a pyqtgraph.GraphicsScene.GraphicsScene.GraphicsScene
        self.scene().sigMouseClicked.connect(self.mouse_clicked)    


    def mouse_clicked(self, mouseClickEvent):
        # mouseClickEvent is a pyqtgraph.GraphicsScene.mouseEvents.MouseClickEvent
        print('clicked plot 0x{:x}, event: {}'.format(id(self), mouseClickEvent))



# my application
class AppWindow(QtWidgets.QWidget, Ui_StartForm):
    def __init__(self):
        super(AppWindow, self).__init__()
        self.setupUi(self)

        self.dock_area_main = DockArea()
        self.GraphLayout.addWidget(self.dock_area_main)

        # Best to use lower case for variables and upper case for types, so I
        # renamed self.Dock1 to self.dock1.        

        self.dock1 = Dock("Dock 1", size=(1, 1)) 
        self.dock_area_main.addDock(self.dock1, 'left')

        self.dock2 = Dock("Dock 2", size=(1, 1))
        self.dock_area_main.addDock(self.dock2, 'right')

        self.GraphViewList = []

        self.pl1 = MyPlotWidget()
        self.pl2 = MyPlotWidget()

        self.dock1.addWidget(self.pl1)
        self.dock2.addWidget(self.pl2)



app = QtWidgets.QApplication(sys.argv)
w = AppWindow()
w.show()
sys.exit(app.exec_())
titusjan
  • 5,376
  • 2
  • 24
  • 43
  • 1
    Hi titusjan, thanks a lot for your answer, your example works quite good. Just have one more question. My original code can identify mouse click on the plotted curve, so that I can show somewhere properties for this curve and edit settings. The code draw the curve on the plot: curve = pl.plot(y, pen='b') and then connect to mouse click: curve.sigClicked.connect(self.sig_update_conf). After I modified my code with solution you provide, above function doesn't work any more. As I understand, I intercept mouse click and it doesn't go to plot object. Is there any way to have both events? – Ostap Nov 01 '19 at 10:41
  • 2
    You have to make your curve clickable. So `curve.curve.setClickable(True)`. – titusjan Nov 01 '19 at 13:59
0

Probably it's to late to answer this question but I encoutered the same issue only recently. I found helpful to create a class that inherits pg.PlotWidget. Here is my example code:

from PyQt5 import QtWidgets
import pyqtgraph as pg
import sys

import numpy as np
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class MyPlotWidget(pg.PlotWidget):

    sigMouseClicked = pyqtSignal(object) # add our custom signal

    def __init__(self, *args, **kwargs):
        super(MyPlotWidget, self).__init__(*args, **kwargs)

    def mousePressEvent(self, ev):
        super().mousePressEvent(ev)
        self.sigMouseClicked.emit(ev)

class Plot2D(QtWidgets.QWidget):
    def __init__(self, *args, **kwargs):
        super(Plot2D, self).__init__(*args, **kwargs)
        self.initUI()

    def initUI(self):
        self.plt = MyPlotWidget()

        lay = QVBoxLayout()
        lay.addWidget(self.plt)
        self.setLayout(lay)

        self.data_line =  self.plt.plot([x*x for x in range(-10,11)])

        self.plt.sigMouseClicked.connect(self.plot_clicked)

    def plot_clicked(self):
        print("clicked!")


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    main = Plot2D()
    main.show()
    sys.exit(app.exec_())
0

When calling p = PlotWidget.plot() the returned object is of type PlotDataItem which has a signal for clicks, note that clickable must be set to True. It's possible to connect this signal to a custom method that compares the parent PlotItem to the PlotItem of each PlotWidget. Unfortunatly, I couldn't find a way to get the PlotWidget from the the PlotDataItem

p = plot_widget.plot(x, y, clickable=True)
p.sigClicked.connect(plot_clicked)

def plot_clicked(*args):
    plot_item_parent = args[0].parentItem().parentItem().parentItem()
    if plot_item_parent == plot_widget.getPlotItem():
        return True

In this way you have access in the callback to all objects you mentioned (PlotWidget, PlotItem, ViewBox) as well as the PlotItemData

SiP
  • 1,080
  • 3
  • 8