I have a GUI, which consist of a Qtableview and dataframe scatter plot widget. Plot draws the X, Y value from the table and has colormap with Z value. Here's the point. When I modify Y value in Qtableview, then press the refresh button on the bottom of GUI, the altered dataframe value can be seen on console. Of course, however, graphs are not updated in response to changes in data. How can I display the altered value on the graph when I pushed the refresh button?
import sys
import pandas as pd
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton
import matplotlib
import pandas as pd
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()] = 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(
[[1, 9, 2], [2, 0, -1], [3, 5, 2], [4, 3, 2], [5, 8, 9],],
columns=["X", "Y", "Z"])
# 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.data.plot.scatter(x='X', y='Y', c = 'Z', colormap = 'jet', ax=sc.axes)
toolbar = NavigationToolbar(sc, self)
# Refresh button
button_refresh = QPushButton("Refresh")
button_refresh.clicked.connect(self.refresh_btn)
layout.addWidget(self.table)
layout.addWidget(toolbar)
layout.addWidget(sc)
layout.addWidget(button_refresh)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
def refresh_btn(self):
print("data: \n", self.data)
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
First, You have to modify Your setData
method to convert edited value into float. That way only numbers are stored in new data column (str is default). Then You have to update matplotlib plot again manually. You've got reference to the plot in self.data.plot
object.
Here is modified code, which is updating plot after Refresh
button is clicked:
import sys
import pandas as pd
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QVBoxLayout, QWidget, QPushButton
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(
[[1, 9, 2], [2, 0, -1], [3, 5, 2], [4, 3, 2], [5, 8, 9], ],
columns=["X", "Y", "Z"])
# 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
button_refresh = QPushButton("Refresh")
button_refresh.clicked.connect(self.refresh_btn)
layout.addWidget(self.table)
layout.addWidget(toolbar)
layout.addWidget(sc)
layout.addWidget(button_refresh)
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_()