'Click scatter plot to get the cordinate's information

I'm trying to make a GUI that displays simple scatter plot making use of PyQt5 like below. I want implement a QLabel object at the bottom of GUI that displays the coordinate's information when I clicked certain data point on the plot. Is there anyone who has a good solution?

import sys

import pandas as pd
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QVBoxLayout, QWidget, QLabel
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as canvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure


class MplCanvas(canvas):
    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        super().__init__(fig)


class TableModel(QtCore.QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self._data = data

    def data(self, index, role=Qt.DisplayRole):
        if index.isValid():
            if role == Qt.DisplayRole or role == Qt.EditRole:
                value = self._data.iloc[index.row(), index.column()]
                return str(value)

    def setData(self, index, value, role):
        if role == Qt.EditRole:
            self._data.iloc[index.row(), index.column()] = float(value)
            return True
        return False

    def headerData(self, col, orientation, role):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return self._data.columns[col]

    def flags(self, index):
        return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable

    def rowCount(self, index):
        return self._data.shape[0]

    def columnCount(self, index):
        return self._data.shape[1]

    def headerData(self, section, orientation, role):
        if role == Qt.DisplayRole:
            if orientation == Qt.Horizontal:
                return str(self._data.columns[section])

            if orientation == Qt.Vertical:
                return str(self._data.index[section])


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        layout = QVBoxLayout()

        self.table = QtWidgets.QTableView()

        self.data = pd.DataFrame({'X': [10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
                            'Y': [20, 21, 19, 18, 50, 43, 12, 77, 34, 56],
                            'Z': [65, 34, 34, 90, 45, 23, 34, 54, 23, 12],
                            'A': [33, 56, 34, 12, 76, 45, 22, 87, 45, 20],
                            'B': ['aa', 'aa', 'bb','bb', 'bb', 'bb', 'cc', 'cc', 'cc', 'cc']})

        # Pandas data model setting
        self.model = TableModel(self.data)
        self.table.setModel(self.model)

        # define scatterplot
        sc = MplCanvas(self, width=5, height=4, dpi=100)
        self.plot = self.data.plot.scatter(x='X', y='Y', c='Z', colormap='jet', ax=sc.axes)
        toolbar = NavigationToolbar(sc, self)

        # Refresh button
        info_display = QLabel("X= , Y= , Z= ")
        

        layout.addWidget(self.table)
        layout.addWidget(toolbar)
        layout.addWidget(sc)
        layout.addWidget(info_display)

        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

    def refresh_btn(self):
        self.plot.clear()
        self.plot.scatter(self.data["X"], self.data["Y"], c=self.data["Z"], cmap="jet", s=20, alpha=0.9)
        self.plot.figure.canvas.draw()
        self.plot.figure.canvas.flush_events()


app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

enter image description here



Solution 1:[1]

This needs to be handled by the matplotlib picker events. This allows you to detect selected data points on the plot, returned as an array of indexes in the original data.

First enable the picker on the plot when creating it.

        # define scatterplot
        sc = MplCanvas(self, width=5, height=4, dpi=100)
        self.plot = self.data.plot.scatter(x='X', y='Y', c='Z', colormap='jet', ax=sc.axes, picker=True, pickradius=5)
        toolbar = NavigationToolbar(sc, self)

Then hook up the picker event to a custom method (here self.on_pick).

        # Connect pick event
        self.plot.figure.canvas.mpl_connect('pick_event', self.on_pick)

Finally, you need to define the handler method.

    def on_pick(self, event):
        indexes = event.ind  # Indexes of the data point (array).
        i = indexes[0]  # Only one selection for demo.
        print("Data point:", self.data["X"][i], self.data["Y"][i], self.data["Z"][i])
        # Highlight the row, through the model.
        self.table.selectRow(i)

The event object has an attribute .ind which contains an array of indexes of the clicked points (above we just select the first one). We print out the data, and then use the same index to select the current row in the table.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 mfitzp