GrapicsLayoutWidget and PlotItem plot - how to change color of plotted data when clicked (what is the signal?)?

2,091 views
Skip to first unread message

Dan

unread,
Nov 23, 2014, 6:29:00 PM11/23/14
to pyqt...@googlegroups.com
I'm having trouble figuring out how to reference the data points of a plot that is plotted within a GraphicsLayoutWidget when those data points are clicked. 

The example usage case is: 

User imports multiple "sweeps" of data and plots them on a single plot. User then wants to select, by clicking, one of those sweeps. 

What I'm trying to achieve is changing the color of the "sweep" that has been clicked on. 

However, I'm having a hard time understanding how to get that signal.

The code associated with what I have (not full application, but relevant parts I think):

self.plotWidget = pg.GraphicsLayoutWidget(Form)


def plot_data(self, plt):
        checked = self.find_checked()
        for key in checked.keys():
            if key in self.vr_headers:
                for sweep in checked[key]:
                    plt.plot(self.df['voltage recording']['Time'][sweep], self.df['voltage recording'][key][sweep],
                             pen=pg.mkPen('b'))
            elif key in self.ls_headers:
                for sweep in checked[key]:
                    plt.plot(self.df['linescan'][key + ' Time'][sweep], self.df['linescan'][key][sweep],
                             pen=pg.mkPen('b'))

def add_new_plot(self):
        name = "Plot" + str(self.counter)
        plt = self.plotWidget.addPlot(row=self.counter, col=0, name=name, title=name)
        self.plot_data(plt)
        plt.setXLink("Plot1")
        self.plotNum_dropdown.addItem(name)
        self.clearPlot_dropdown.addItem(name)
        self.markersPlot_dropdown.addItem(name)
        self.plot_index_list.append(self.counter)
        self.counter += 1

Luke Campagnola

unread,
Nov 24, 2014, 7:23:45 AM11/24/14
to pyqt...@googlegroups.com
On Sun, Nov 23, 2014 at 6:29 PM, Dan <daniel....@gmail.com> wrote:
I'm having trouble figuring out how to reference the data points of a plot that is plotted within a GraphicsLayoutWidget when those data points are clicked. 

The example usage case is: 

User imports multiple "sweeps" of data and plots them on a single plot. User then wants to select, by clicking, one of those sweeps. 

What I'm trying to achieve is changing the color of the "sweep" that has been clicked on. 

Simple example:

    curve = plt.plot(data)
    curve.sigClicked.connect(self.plotClicked)
 
Then the callback looks like:

    def plotClicked(self, curve):
        curve.setPen(...)

Dan

unread,
Nov 24, 2014, 9:18:29 AM11/24/14
to pyqt...@googlegroups.com
Thanks for the response. Still struggling a bit though. 

I understand the response you gave when there is a static plot (e.g. plot1, plot2, etc. - or curve1, curve2, whatever). 

But in my case that plot items (PlotDataItems, correct?) in a PlotItem are generated dynamically based on what the user selects to plot. Just dropping those first two lines of code you provided into my plot_data function doesn't produce any effect. 

My understanding of namespaces is not great, so I don't see how the rest of the application will be able to access curve.sigClicked.connect(self.plotClicked) since that is in the plot_data function namespace. But I don't see where else that could go either. 

Sorry if these questions seem a bit naive, I haven't tried to do something this complicated before and I'm definitely running into the limits of my knowledge at the moment. 

Luke Campagnola

unread,
Nov 24, 2014, 9:33:47 AM11/24/14
to pyqt...@googlegroups.com
Have you seen examples/MouseSelection.py ?
Maybe start there and let us know if that doesn't
work for you.

--
You received this message because you are subscribed to the Google Groups "pyqtgraph" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pyqtgraph+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/pyqtgraph/3043f39d-398e-4722-8e50-0bb4cf346fdd%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Dan

unread,
Nov 24, 2014, 5:07:54 PM11/24/14
to pyqt...@googlegroups.com
Looking through MouseSelection.py I more or less understand how it is working, but I'm still not sure how to adapt that to my case. 

Modifying the plot_data function to the following doesn't produce the intended result:


    def plot_data(self, plt):
        checked = self.find_checked()

        for key in checked.keys():
            if key in self.vr_headers:
                for sweep in checked[key]:
                    curve = plt.plot(self.df['voltage recording']['Time']['Sweep0001'],
                                     self.df['voltage recording'][key][sweep],
                                     pen=pg.mkPen('b'))
                    curve.sigClicked.connect(self.plotClicked)

            elif key in self.ls_headers:
                for sweep in checked[key]:
                    curve = plt.plot(self.df['linescan'][key + ' Time']['Sweep0001'],
                             self.df['linescan'][key][sweep],
                             pen=pg.mkPen('b'))

                    curve.sigClicked.connect(self.plotClicked)

I can create a list of the curve objects, and then loop over that when I call the add_new_plot function, but again this doesn't produce any effect. I.E.:

    def plot_data(self, plt):
        checked = self.find_checked()

        for key in checked.keys():
            if key in self.vr_headers:
                for sweep in checked[key]:
                    curve = plt.plot(self.df['voltage recording']['Time']['Sweep0001'],
                                     self.df['voltage recording'][key][sweep],
                                     pen=pg.mkPen('b'))
                    
                    self.curves.append(curve)

            elif key in self.ls_headers:
                for sweep in checked[key]:
                    curve = plt.plot(self.df['linescan'][key + ' Time']['Sweep0001'],
                             self.df['linescan'][key][sweep],
                             pen=pg.mkPen('b'))

                    self.curves.append(curve)

    def add_new_plot(self):
        name = "Plot" + str(self.counter)
        plt = self.plotWidget.addPlot(row=self.counter, col=0, name=name, title=name)
        self.plot_data(plt)
        for curve in self.curves:
            curve.sigClicked.connect(self.plotClicked)
        plt.setXLink("Plot1")
        self.plotNum_dropdown.addItem(name)
        self.clearPlot_dropdown.addItem(name)
        self.markersPlot_dropdown.addItem(name)
        self.plot_index_list.append(self.counter)
        self.counter += 1

It's unclear to me where curve.sigClicked.connect(self.plotClicked) needs to be called. 

Thanks again for the help

----------------------------------------------------------------------------------------------------------------------------------------
The full class definition, in case that might help: 

class DataViewer(QtWidgets.QWidget):

    def __init__(self):
        QtWidgets.QWidget.__init__(self)
        self.setupUi(self)
        self.df = None
        self.vr_headers = []
        self.ls_headers = []
        self.sweeps = []
        self.plot_index_list = []
        self.counter = 1
        self.curves = []

    def setupUi(self, Form):
        Form.resize(1742, 988)
        Form.setContextMenuPolicy(QtCore.Qt.PreventContextMenu)
        Form.setWindowTitle("Data Viewer")
        pg.setConfigOption('background', 'w')
        pg.setConfigOption('foreground', 'k')

        self.windowLayout = QtWidgets.QHBoxLayout(Form)
        self.windowLayout.setObjectName("windowLayout")

        self.leftColumn = QtWidgets.QVBoxLayout()
        self.leftColumn.setObjectName("leftColumnLayout")

        self.treeWidget = QtWidgets.QTreeWidget(Form)
        self.treeWidget.setObjectName("treeWidget")
        self.treeWidget.headerItem().setText(0, "Data")

        self.loadDataFolder_btn = QtWidgets.QPushButton("Load Data Folder", Form)
        self.loadDataFolder_btn.setObjectName("loadDataFolder_btn")
        self.loadDataFolder_btn.clicked.connect(self.load_data_folder)
        self.loadDataFolder_btn.clicked.connect(self.update_treeWidget)

        self.clearChecked_btn = QtWidgets.QPushButton("Clear Check Boxes", Form)
        self.clearChecked_btn.setObjectName("clearChecked_btn")
        self.clearChecked_btn.clicked.connect(self.clear_checked)

        self.createPlot_btn = QtWidgets.QPushButton("New Plot", Form)
        self.createPlot_btn.setObjectName("createPlot_btn")
        self.createPlot_btn.clicked.connect(self.add_new_plot)

        self.addToPlot_btn = QtWidgets.QPushButton("Add plot to:", Form)
        self.addToPlot_btn.setObjectName("addToPlot_btn_btn")
        self.addToPlot_btn.clicked.connect(self.add_to_plot)

        self.plotNum_dropdown = QtWidgets.QComboBox(Form)
        self.plotNum_dropdown.setObjectName("plotNum_dropdown")

        self.clearPlot_dropdown = QtWidgets.QComboBox(Form)
        self.clearPlot_dropdown.setObjectName("clearPlot_dropdown")
        self.clearPlot_dropdown.addItem("All")

        self.markersPlot_dropdown = QtWidgets.QComboBox(Form)
        self.markersPlot_dropdown.setObjectName("markersPlot_dropdown")
        self.markersPlot_dropdown.addItem("All")

        self.addPlotLayout = QtWidgets.QHBoxLayout()
        self.addPlotLayout.setObjectName("addPlotLayout")
        self.addPlotLayout.addWidget(self.addToPlot_btn)
        self.addPlotLayout.addWidget(self.plotNum_dropdown)

        self.clearPlot_btn = QtWidgets.QPushButton("Clear:", Form)
        self.clearPlot_btn.setObjectName("clearPlot_btn")
        self.clearPlot_btn.clicked.connect(self.clear_plot)

        self.clearPlotLayout = QtWidgets.QHBoxLayout()
        self.clearPlotLayout.setObjectName("clearPlotLayout")
        self.clearPlotLayout.addWidget(self.clearPlot_btn)
        self.clearPlotLayout.addWidget(self.clearPlot_dropdown)

        self.addMarker_btn = QtWidgets.QPushButton("Add Marker", Form)
        self.addMarker_btn.setObjectName("addMarker_btn")
        self.addMarker_btn.clicked.connect(self.add_marker)

        self.tabWidget = QtWidgets.QTabWidget(Form)
        tab_widget_sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
        self.tabWidget.setSizePolicy(tab_widget_sizePolicy)
        self.tabWidget.setMinimumSize(QtCore.QSize(200, 100))
        self.tabWidget.setObjectName("tabWidget")

        self.Import_and_Plot_tab = QtWidgets.QWidget()
        self.Import_and_Plot_tab.setObjectName("Import_and_Plot_tab")

        self.import_plot_layout = QtWidgets.QVBoxLayout(self.Import_and_Plot_tab)
        self.import_plot_layout.setObjectName("import_plot_layout")

        self.import_plot_layout.addWidget(self.loadDataFolder_btn)
        self.import_plot_layout.addWidget(self.clearChecked_btn)
        self.import_plot_layout.addWidget(self.createPlot_btn)
        self.import_plot_layout.addLayout(self.addPlotLayout)
        self.import_plot_layout.addLayout(self.clearPlotLayout)

        self.Markers_tab = QtWidgets.QWidget()
        self.Markers_tab.setObjectName("Markers_tab")

        self.markers_tab_layout = QtWidgets.QVBoxLayout(self.Markers_tab)
        self.markers_tab_layout.setObjectName("markers_tab_layout")

        self.markers_tab_layout.addWidget(self.addMarker_btn)
        self.markers_tab_layout.addWidget(self.markersPlot_dropdown)

        self.tabWidget.addTab(self.Import_and_Plot_tab, "Import && Plot")
        self.tabWidget.addTab(self.Markers_tab, "Markers")

        self.leftColumn.addWidget(self.treeWidget)
        self.leftColumn.addWidget(self.tabWidget)

        self.line = QtWidgets.QFrame(Form)
        self.line.setFrameShape(QtWidgets.QFrame.VLine)
        self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
        self.line.setObjectName("line")

        self.plotWidget = pg.GraphicsLayoutWidget(Form)
        self.plotWidget.setObjectName("plotWidget")

        self.windowLayout.addLayout(self.leftColumn)
        self.windowLayout.addWidget(self.line)
        self.windowLayout.addWidget(self.plotWidget, QtCore.Qt.AlignCenter)

    def update_treeWidget(self):
        self.treeWidget.clear()
        all_headers = self.vr_headers + self.ls_headers
        for header in all_headers:
            parent_item = QtWidgets.QTreeWidgetItem(self.treeWidget)
            parent_item.setFlags(QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsTristate)
            parent_item.setText(0, header)

            for sweep in self.sweeps:
                child_item = QtWidgets.QTreeWidgetItem(parent_item)
                child_item.setCheckState(0, QtCore.Qt.Unchecked)
                child_item.setText(0, sweep)

    def create_ratio_tab(self):
        self.Ratio_tab = QtWidgets.QWidget()
        self.Ratio_tab.setObjectName("Ratio_tab")

        self.tabWidget.addTab(self.Ratio_tab, "Ratio")

    def load_data_folder(self):
        folder = QtWidgets.QFileDialog.getExistingDirectory(self)
        self.df = pvi.import_folder(folder)
        if self.df['voltage recording'] is not None:
            self.vr_headers = self.df['voltage recording'].columns[1:].tolist()
        if self.df['linescan'] is not None:
            self.ls_headers = self.df['linescan'].columns[1::2].tolist()
            self.create_ratio_tab()
        self.sweeps = self.df['voltage recording'].index.levels[0]

    def find_checked(self):
        checked = dict()
        root = self.treeWidget.invisibleRootItem()
        signal_count = root.childCount()

        for i in range(signal_count):
            signal = root.child(i)

            if signal.checkState(0) == QtCore.Qt.Unchecked:
                continue
            else:
                checked_sweeps = list()
                num_children = signal.childCount()

                for n in range(num_children):
                    child = signal.child(n)

                    if child.checkState(0) == QtCore.Qt.Checked:
                        checked_sweeps.append(child.text(0))

            checked[signal.text(0)] = checked_sweeps

        return checked

    def clear_checked(self):
        root = self.treeWidget.invisibleRootItem()
        signal_count = root.childCount()

        for i in range(signal_count):
            signal = root.child(i)
            if signal.checkState(0) == QtCore.Qt.Unchecked:
                continue
            else:
                signal.setCheckState(0, QtCore.Qt.Unchecked)
                num_children = signal.childCount()
                for n in range(num_children):
                    child = signal.child(n)
                    if child.checkState(0) == QtCore.Qt.Checked:
                        continue
                    else:
                        child.setCheckState(0, QtCore.Qt.Unchecked)

    def plot_data(self, plt):
        checked = self.find_checked()

        for key in checked.keys():
            if key in self.vr_headers:
                for sweep in checked[key]:
                    curve = plt.plot(self.df['voltage recording']['Time']['Sweep0001'],
                                     self.df['voltage recording'][key][sweep],
                                     pen=pg.mkPen('b'))

                    self.curves.append(curve)

            elif key in self.ls_headers:
                for sweep in checked[key]:
                    curve = plt.plot(self.df['linescan'][key + ' Time']['Sweep0001'],
                             self.df['linescan'][key][sweep],
                             pen=pg.mkPen('b'))

                    self.curves.append(curve)

    def add_new_plot(self):
        name = "Plot" + str(self.counter)
        plt = self.plotWidget.addPlot(row=self.counter, col=0, name=name, title=name)
        self.plot_data(plt)
        for curve in self.curves:
            curve.sigClicked.connect(self.plotClicked)
        plt.setXLink("Plot1")
        self.plotNum_dropdown.addItem(name)
        self.clearPlot_dropdown.addItem(name)
        self.markersPlot_dropdown.addItem(name)
        self.plot_index_list.append(self.counter)
        self.counter += 1

    def add_to_plot(self):
        index = self.plotNum_dropdown.currentIndex()
        plt = self.plotWidget.getItem(self.plot_index_list[index], 0)
        self.plot_data(plt)

    def clear_plot(self):
        index = self.clearPlot_dropdown.currentIndex()
        if index == 0:
            self.plotWidget.clear()
            self.plotNum_dropdown.clear()
            self.clearPlot_dropdown.clear()
            self.clearPlot_dropdown.addItem("All")
            self.plot_index_list = []
            self.counter = 1
        elif index > 0:
            print(self.plot_index_list)
            plt = self.plotWidget.getItem(self.plot_index_list[index-1], 0)
            print(type(plt))
            self.plotWidget.removeItem(plt)
            del self.plot_index_list[index-1]
            self.clearPlot_dropdown.removeItem(index)
            self.plotNum_dropdown.removeItem(index-1)
            print(self.plot_index_list)

    def add_marker(self):
        index = self.markersPlot_dropdown.currentIndex()
        if index == 0:
            for plot_index in self.plot_index_list:
                plt = self.plotWidget.getItem(plot_index, 0)
                x_val = (plt.viewRange()[0][0]+plt.viewRange()[0][1])/2
                plt.addLine(x=x_val, movable=True)
        elif index > 0:
            plt = self.plotWidget.getItem(self.plot_index_list[index-1], 0)
            x_val = (plt.viewRange()[0][0]+plt.viewRange()[0][1])/2
            plt.addLine(x=x_val, movable=True)

    def plotClicked(self, curve):
        curve.setPen('r')

Dan

unread,
Dec 1, 2014, 9:36:35 AM12/1/14
to pyqt...@googlegroups.com
So I figured a bit of a workaround for this, as I never got it quite working. But my alternative solutions is actually preferred, so that's fine. 

I am left with a new question though. I'm trying to allow the user to select one of the plots (i.e. PlotItem) by double clicking on it. Neither PlotItem nor ViewBox emit anything when clicked on. I can use mousePressEvent to get when the mouse is clicked, but I don't know how to tell what was clicked on (I can get the overall widget, but not the PlotItem within the widget). 

Any suggestions on achieving this? What I'm basically trying to do is after double clicking on a plot (e.g. Plot1) I'm changing the viewbox border (to indicate that that plot is currently selected) and setting within the overall application that that plot is currently selected (for manipulating with other parts of the application). I know how to achieve everything after I know what PlotItem has been selected, but I'm stuck on knowing which PlotItem was double-clicked on. 

Thanks

Dan

unread,
Dec 1, 2014, 1:00:06 PM12/1/14
to pyqt...@googlegroups.com
Was provided a solution to this second issue over on Stackoverflow. 

Reply all
Reply to author
Forward
0 new messages