Обновление графика полярной диаграммы в GUI интерфейсе при изменении данных в таблице
В программе написанной на PyQt5 интерфейсом (создавал в Qt Designer), хотел бы использовать полярную диаграмму, с обновлением графика, если вносятся изменения в ячейки таблицы.
Сама таблица с одним столбцом и 12-ю ячейками, созданная с помощью QTableView, QAbstractTableModel, с использованием QSpinDelegat, для ввода только числовых данных.
Долго искал примеры в направлении matplotlib.animation. В основном эти примеры представлены вне GUI интерфейса. В общих чертах есть понимание, как это работает. Но не понятно как связать matplotlib.animation с FigureCanvas и обновлять функцию. Или ловить сигнал от изменяемых ячеек таблицы QTableView, как это сделать?
Вопрос, как связать эти два события: ввод таблицу значений <-> обновление диаграммы? Буду признателен за любую подсказку, пример или в каком направлении искать ответ!
v1_gui.py
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_CalculatorForm(object):
def setupUi(self, CalculatorForm):
CalculatorForm.setObjectName("CalculatorForm")
CalculatorForm.setWindowModality(QtCore.Qt.NonModal)
CalculatorForm.resize(615, 460)
CalculatorForm.setMinimumSize(QtCore.QSize(420, 460))
palette = QtGui.QPalette()
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(231, 231, 231))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(231, 231, 231))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush)
brush = QtGui.QBrush(QtGui.QColor(231, 231, 231))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(231, 231, 231))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush)
CalculatorForm.setPalette(palette)
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(10)
CalculatorForm.setFont(font)
CalculatorForm.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
CalculatorForm.setFocusPolicy(QtCore.Qt.NoFocus)
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap("../../ОБР_ТРЕУГ/повтор/Новая папка/1.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
CalculatorForm.setWindowIcon(icon)
CalculatorForm.setToolTipDuration(-3)
CalculatorForm.setAutoFillBackground(True)
CalculatorForm.setInputMethodHints(QtCore.Qt.ImhNone)
CalculatorForm.setIconSize(QtCore.QSize(40, 24))
self.centralwidget = QtWidgets.QWidget(CalculatorForm)
self.centralwidget.setObjectName("centralwidget")
self.layoutWidget = QtWidgets.QWidget(self.centralwidget)
self.layoutWidget.setGeometry(QtCore.QRect(0, 0, 2, 2))
self.layoutWidget.setObjectName("layoutWidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.layoutWidget)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.layoutWidget1 = QtWidgets.QWidget(self.centralwidget)
self.layoutWidget1.setGeometry(QtCore.QRect(0, 0, 2, 2))
self.layoutWidget1.setObjectName("layoutWidget1")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.layoutWidget1)
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.tableView = QtWidgets.QTableView(self.centralwidget)
self.tableView.setGeometry(QtCore.QRect(60, 40, 121, 331))
self.tableView.setEditTriggers(QtWidgets.QAbstractItemView.AnyKeyPressed|QtWidgets.QAbstractItemView.CurrentChanged|QtWidgets.QAbstractItemView.DoubleClicked|QtWidgets.QAbstractItemView.EditKeyPressed)
self.tableView.setAlternatingRowColors(True)
self.tableView.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
self.tableView.setObjectName("tableView")
self.MplWidget = MplWidget(self.centralwidget)
self.MplWidget.setGeometry(QtCore.QRect(210, 20, 351, 341))
self.MplWidget.setObjectName("MplWidget")
CalculatorForm.setCentralWidget(self.centralwidget)
self.retranslateUi(CalculatorForm)
QtCore.QMetaObject.connectSlotsByName(CalculatorForm)
def retranslateUi(self, CalculatorForm):
_translate = QtCore.QCoreApplication.translate
CalculatorForm.setWindowTitle(_translate("CalculatorForm", "Метод треугольника-обратный расчет"))
from mplwidget import MplWidget
mplwidget.py
from PyQt5.QtWidgets import QWidget, QVBoxLayout
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.figure import Figure
class MplWidget(QWidget):
def __init__(self, parent = None):
QWidget.__init__(self, parent)
self.canvas = FigureCanvas(Figure())
vertical_layout = QVBoxLayout()
vertical_layout.addWidget(self.canvas)
self.canvas.axes = self.canvas.figure.add_subplot(111, polar = True)
self.setLayout(vertical_layout)
self.canvas.axes.set_theta_offset(3.14/2.0)
self.canvas.axes.set_theta_direction(-1)
main_dd.py
import sys
from PyQt5 import QtWidgets
from v1_gui import Ui_CalculatorForm
from math import cos, sin, sqrt, radians, degrees, acos, pi
from matplotlib.backends.backend_qt5agg import (NavigationToolbar2QT as NavigationToolbar)
from PyQt5.QtWidgets import (QApplication, QWidget, QTableView, QPushButton,
QStyledItemDelegate,QSpinBox, QMenu)
from PyQt5.QtGui import QFont,QColor
from PyQt5.QtCore import (Qt, QAbstractTableModel, QModelIndex, QAbstractItemModel)
import numpy as np
class NpModel(QAbstractTableModel):
def __init__(self, data = np.array([[]]), parent=None):
super().__init__()
self.npdata = data
def rowCount(self,index=QModelIndex()):
return len(self.npdata)
def columnCount(self,index=QModelIndex()):
return len(self.npdata[0])
def data(self,index,role):
if not index.isValid(): return None
row = index.row()
col = index.column()
val = self.npdata[row,col]
if role == Qt.DisplayRole or role == Qt.EditRole:
return str(round(val,3))
elif role == Qt.BackgroundRole:
if col == 0: return QColor(0,0,0,8)
def flags(self,index):
if not index.isValid(): return Qt.ItemIsEnabled
if index.column() == 0:
return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable
return Qt.ItemIsEnabled | Qt.ItemIsEditable
def setData(self,index,value,role):
if not index.isValid():
return False
if role == Qt.EditRole:
try : f = float(value.replace(',','.'))
except: return False
self.npdata[index.row(),index.column()] = f
self.dataChanged.emit(index,index)
return True
return False
def headerData(self,section,orientation,role):
if role != Qt.DisplayRole: return None
if orientation == Qt.Horizontal:
return 'TF' + str(section + 1)
else: return str(section + 1)
def load(self,arr):
self.beginResetModel()
self.npdata = arr
self.endResetModel()
class SpinDelegate(QStyledItemDelegate):
def createEditor(self, parent, options, index):
editor = QSpinBox(parent)
editor.setFrame(False)
editor.setRange(0,360)
editor.setSingleStep(1)
return editor
def setEditorData(self, editor, index):
value = index.model().data(index, Qt.EditRole)
editor.setValue(int(value))
def updateEditorGeometry(self, editor, options, index):
editor.setGeometry(options.rect)
def setModelData(self, editor, model, index):
value = str(editor.value())
model.setData(index, value, Qt.EditRole)
class MyWin(QtWidgets.QMainWindow):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
self.ui = Ui_CalculatorForm()
self.ui.setupUi(self)
self.addToolBar(NavigationToolbar(self.ui.MplWidget.canvas, self))
self.ui.tableView.horizontalHeader().setSectionResizeMode(1)
self.ui.tableView.setSelectionMode(1)
r = np.arange(0,12,dtype=int).reshape(12,1)
self.table = self.ui.tableView
self.model = NpModel(r)
self.table.setModel(self.model)
self.table.setItemDelegateForColumn(0,SpinDelegate())
def f(r): return r
a=list(map(f,r))
print(a)
self.ui.MplWidget.canvas.axes.plot(a, c="red", marker='o')
self.ui.MplWidget.canvas.axes.set_rlim(0.0, 12)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
myapp = MyWin()
myapp.show()
sys.exit(app.exec())
Ответы (1 шт):
я отметил строки, в которые внес изменения
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import (QApplication, QWidget, QTableView, QPushButton,
QStyledItemDelegate,QSpinBox, QMenu, QVBoxLayout)
from PyQt5.QtGui import QFont,QColor
from PyQt5.QtCore import (Qt, QAbstractTableModel, QModelIndex, QAbstractItemModel)
import numpy as np
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar # +
from matplotlib.figure import Figure
class Ui_CalculatorForm(object):
def setupUi(self, CalculatorForm):
CalculatorForm.setObjectName("CalculatorForm")
CalculatorForm.setWindowModality(QtCore.Qt.NonModal)
CalculatorForm.resize(615, 460)
CalculatorForm.setMinimumSize(QtCore.QSize(420, 460))
palette = QtGui.QPalette()
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(231, 231, 231))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(231, 231, 231))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush)
brush = QtGui.QBrush(QtGui.QColor(231, 231, 231))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(231, 231, 231))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush)
CalculatorForm.setPalette(palette)
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(10)
CalculatorForm.setFont(font)
CalculatorForm.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
CalculatorForm.setFocusPolicy(QtCore.Qt.NoFocus)
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap("../../ОБР_ТРЕУГ/повтор/Новая папка/1.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
CalculatorForm.setWindowIcon(icon)
CalculatorForm.setToolTipDuration(-3)
CalculatorForm.setAutoFillBackground(True)
CalculatorForm.setInputMethodHints(QtCore.Qt.ImhNone)
CalculatorForm.setIconSize(QtCore.QSize(40, 24))
self.centralwidget = QtWidgets.QWidget(CalculatorForm)
self.centralwidget.setObjectName("centralwidget")
self.layoutWidget = QtWidgets.QWidget(self.centralwidget)
self.layoutWidget.setGeometry(QtCore.QRect(0, 0, 2, 2))
self.layoutWidget.setObjectName("layoutWidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.layoutWidget)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.layoutWidget1 = QtWidgets.QWidget(self.centralwidget)
self.layoutWidget1.setGeometry(QtCore.QRect(0, 0, 2, 2))
self.layoutWidget1.setObjectName("layoutWidget1")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.layoutWidget1)
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.tableView = QtWidgets.QTableView(self.centralwidget)
self.tableView.setGeometry(QtCore.QRect(60, 40, 121, 331))
self.tableView.setEditTriggers(QtWidgets.QAbstractItemView.AnyKeyPressed|QtWidgets.QAbstractItemView.CurrentChanged|QtWidgets.QAbstractItemView.DoubleClicked|QtWidgets.QAbstractItemView.EditKeyPressed)
self.tableView.setAlternatingRowColors(True)
self.tableView.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
self.tableView.setObjectName("tableView")
self.MplWidget = MplWidget(self.centralwidget)
self.MplWidget.setGeometry(QtCore.QRect(210, 20, 351, 341))
self.MplWidget.setObjectName("MplWidget")
CalculatorForm.setCentralWidget(self.centralwidget)
self.retranslateUi(CalculatorForm)
QtCore.QMetaObject.connectSlotsByName(CalculatorForm)
def retranslateUi(self, CalculatorForm):
_translate = QtCore.QCoreApplication.translate
CalculatorForm.setWindowTitle(_translate("CalculatorForm", "Метод треугольника-обратный расчет"))
class MplWidget(QWidget):
def __init__(self, parent = None):
QWidget.__init__(self, parent)
self.canvas = FigureCanvas(Figure())
vertical_layout = QVBoxLayout()
vertical_layout.addWidget(self.canvas)
self.canvas.axes = self.canvas.figure.add_subplot(111, polar = True)
self.setLayout(vertical_layout)
self.canvas.axes.set_theta_offset(3.14/2.0)
self.canvas.axes.set_theta_direction(-1)
class NpModel(QAbstractTableModel):
def __init__(self, data = np.array([[]]), parent=None):
super().__init__()
self.npdata = data
def rowCount(self,index=QModelIndex()):
return len(self.npdata)
def columnCount(self,index=QModelIndex()):
return len(self.npdata[0])
def data(self,index,role):
if not index.isValid(): return None
row = index.row()
col = index.column()
val = self.npdata[row, col]
if role == Qt.DisplayRole or role == Qt.EditRole:
return str(round(val,3))
elif role == Qt.BackgroundRole:
if col == 0: return QColor(0,0,0,8)
def flags(self,index):
if not index.isValid(): return Qt.ItemIsEnabled
if index.column() == 0:
return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable
return Qt.ItemIsEnabled | Qt.ItemIsEditable
def setData(self,index,value,role):
if not index.isValid():
return False
if role == Qt.EditRole:
try : f = float(value.replace(',','.'))
except: return False
self.npdata[index.row(), index.column()] = f
self.dataChanged.emit(index,index)
return True
return False
def headerData(self,section,orientation,role):
if role != Qt.DisplayRole: return None
if orientation == Qt.Horizontal:
return 'TF' + str(section + 1)
else: return str(section + 1)
def load(self,arr):
self.beginResetModel()
self.npdata = arr
self.endResetModel()
class SpinDelegate(QStyledItemDelegate):
signal_delegate = QtCore.pyqtSignal(int, int) # +++ !!!
def createEditor(self, parent, options, index):
editor = QSpinBox(parent)
editor.valueChanged.connect(lambda val, row=index.row(): self.update_editor(val, row)) # +++ # +++
editor.setFrame(False)
editor.setRange(0, 360)
editor.setSingleStep(1)
return editor
def setEditorData(self, editor, index):
value = index.model().data(index, Qt.EditRole)
editor.setValue(int(value))
def updateEditorGeometry(self, editor, options, index):
editor.setGeometry(options.rect)
def setModelData(self, editor, model, index):
value = str(editor.value())
model.setData(index, value, Qt.EditRole)
def update_editor(self, val, row): # +++
self.signal_delegate.emit(val, row) # +++
class MyWin(QtWidgets.QMainWindow, Ui_CalculatorForm): # + Ui_CalculatorForm
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
# self.ui = Ui_CalculatorForm() # ui.
self.setupUi(self) # ui.
# self.spinBox = QSpinBox()
self.addToolBar(NavigationToolbar(self.MplWidget.canvas, self)) # ui.
self.tableView.horizontalHeader().setSectionResizeMode(1) # ui.
self.tableView.setSelectionMode(1) # ui.
#+ r = np.arange(0,12,dtype=int).reshape(12,1)
self.r = np.arange(0, 12, dtype=int).reshape(12, 1) # + self.r
self.table = self.tableView # ui.
self.model = NpModel(self.r)
self.table.setModel(self.model)
delegate = SpinDelegate(self) # +
delegate.signal_delegate.connect(self.update_canvas) # +++ !!!
# self.table.setItemDelegateForColumn(0, SpinDelegate())
self.table.setItemDelegateForColumn(0, delegate) # +++
def f(r):
return r
a = list(map(f, self.r))
print(a)
self.MplWidget.canvas.axes.plot(a, c="red", marker='o')
self.MplWidget.canvas.axes.set_rlim(0.0, 12)
def update_canvas(self, val, row): # +++
#print(f'update_canvas ===+++ {val}, {row}')
def f(r):
return r
self.r[row] = val # <--- !!!
a = list(map(f, self.r))
self.MplWidget.canvas.axes.clear()
self.MplWidget.canvas.axes.set_theta_offset(3.14/2.0)
self.MplWidget.canvas.axes.set_theta_direction(-1)
self.MplWidget.canvas.axes.plot(a, c="red", marker='o')
self.MplWidget.canvas.axes.set_rlim(0.0, 12)
self.MplWidget.canvas.draw() # !!!
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
myapp = MyWin()
myapp.show()
sys.exit(app.exec())
