Search code examples
pythonmatplotlibqwidget

Python Updating Variables Based on Current Widget Selected


There are a few examples out there of how to do this, but after trying all of them I don't understand how to correctly implement it. I have a program that a button creates a widget with contents inside and assigns it to a grid layout, and it also creates a figure on a canvas. Clicking on the button again creates another widget filled with the same contents and another figure and assigns it to the layout.

One of the contents is a spin box which controls the rotation of the figure. I want each spin box to be able to control the figure that was created with it individually. Here is where I am stuck.

How do I have a general button that can create several widgets, but then on every value change of each spin box, be able to tell which widget it came from so it will rotate the correct figure? I want the widget id or name or however I can access it. Here is what I have so far - Thanks in advance!:

from PyQt4.QtGui import *
from PyQt4.QtCore import *
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas, NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import numpy as np 
import math
from scipy.optimize import fsolve
from mplwindow5 import Ui_mplMainWindow
from cU_widget import cU_Widget

class Viewer(QMainWindow, Ui_mplMainWindow):  
    def __init__(self, parent = None):
        super(Viewer, self).__init__(parent) 
        self.setupUi(self)      
        self.count = 0          
        self.cU_widget = []
        self.cU_rotate = []
        self.lbl_cU_rotate = []
        self.lbl_cU = []

        self.btn_cU.clicked.connect(self.add_cU) 

    def add_cU(self):
        self.cU_widget.append(int(self.count))

        self.cU_widget[self.count] = QWidget(self.scrollAreaWidgetContents)
        self.cU_widget[self.count].setMinimumSize(QSize(101, 81))
        self.cU_widget[self.count].setMaximumSize(QSize(101, 81))
        self.cU_widget[self.count].setObjectName("cU_widget"  + str(self.count+1))

        self.lbl_cU.append(int(self.count))

        self.lbl_cU[self.count] = QLabel("cU " + str(self.count+1), self.cU_widget[self.count])
        self.lbl_cU[self.count].setGeometry(QRect(0, 0, 101, 27))
        self.lbl_cU[self.count].setObjectName("lbl_cU_" + str(self.count+1))

        self.lbl_cU_rotate.append(int(self.count))

        self.lbl_cU_rotate[self.count] = QLabel("R", self.cU_widget[self.count])
        self.lbl_cU_rotate[self.count].setGeometry(QRect(6, 50, 20, 20))
        self.lbl_cU_rotate[self.count].setObjectName("lbl_cU_rotate"  + str(self.count+1))

        self.cU_rotate.append(int(self.count))

        self.cU_rotate[self.count] = QDoubleSpinBox(self.cU_widget[self.count])
        self.cU_rotate[self.count].setGeometry(QRect(20, 40, 71, 27))
        self.cU_rotate[self.count].setObjectName("cU_rotate"  + str(self.count+1))

        self.cU_rotate[self.count].valueChanged.connect(self.cU) # ??? What to use here

        self.gridLayout.addWidget(self.cU_widget[self.count], self.count, 0)

        self.cU()

    def cU(self):

        self.cU_rotate[self.count] = self.cU_rotate[self.count].value()  # ?? What to use here

        rotate = 1          
        tt = np.arange(0,1, 0.001)  

        lco_x0 = 0
        lco_x1 = 4

        lco_y0 = 1
        lco_y1 = 3

        cU_L_x0 = (lco_x0 * math.cos(math.radians(self.cU_rotate[self.count] + rotate))) - (lco_y0 * math.sin(math.radians(self.cU_rotate[self.count] + rotate)))
        cU_L_x1 = (lco_x1 * math.cos(math.radians(self.cU_rotate[self.count] + rotate))) - (lco_y1 * math.sin(math.radians(self.cU_rotate[self.count] + rotate)))
        #...   
        cU_L_y0 = (lco_x0 * math.sin(math.radians(self.cU_rotate[self.count] + rotate))) + (lco_y0 * math.cos(math.radians(self.cU_rotate[self.count] + rotate)))
        cU_L_y1 = (lco_x1 * math.sin(math.radians(self.cU_rotate[self.count] + rotate))) + (lco_y1 * math.cos(math.radians(self.cU_rotate[self.count] + rotate)))
        #...    
        cU_L_ax = ( 1  * cU_L_x0) 
        cU_L_bx = ((-6  * cU_L_x0) +(30 * cU_L_x1))
        # ...    
        cU_L_ay = ( 1  * cU_L_y0)
        cU_L_by = ((-6  * cU_L_y0) +(30 * cU_L_y1))
        #...    
        cU_L_xtt = (cU_L_ax * tt**2) + (cU_L_bx * tt) + 1
        cU_L_ytt = (cU_L_ay * tt**2) + (cU_L_by * tt) + 1

        self.mplContainer.canvas.ax.plot(cU_L_xtt, cU_L_ytt, 'r')

        self.mplContainer.canvas.ax.set_ylim([-5, 5])
        self.mplContainer.canvas.ax.set_xlim([0, 10])
        self.mplContainer.canvas.ax.set_aspect(1)

        self.mplContainer.canvas.draw()

        self.count += 1

app = QApplication(sys.argv)
viewer = Viewer()
viewer.show()
sys.exit(app.exec_())

here is mplwindow5:

from PyQt4 import QtCore, QtGui

class Ui_mplMainWindow(object):
    def setupUi(self, mplMainWindow):
        mplMainWindow.setObjectName("mplMainWindow")
        mplMainWindow.resize(1171, 826)
        self.centralwidget = QtGui.QWidget(mplMainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.mplContainer = MplWidget(self.centralwidget)
        self.mplContainer.setGeometry(QtCore.QRect(259, 20, 861, 741))
        self.mplContainer.setObjectName("mplContainer")
        self.inputContainer = QtGui.QWidget(self.centralwidget)
        self.inputContainer.setGeometry(QtCore.QRect(10, 20, 251, 741))
        self.inputContainer.setObjectName("inputContainer")

        self.scrollArea = QtGui.QScrollArea(self.inputContainer)
        self.scrollArea.setGeometry(QtCore.QRect(0, 160, 241, 581))
        self.scrollArea.setFrameShape(QtGui.QFrame.WinPanel)
        self.scrollArea.setLineWidth(1)
        self.scrollArea.setMidLineWidth(10)
        self.scrollArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setObjectName("scrollArea")
        self.scrollAreaWidgetContents = QtGui.QWidget()
        self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 226, 577))
        self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")

        self.scrollLayout = QtGui.QVBoxLayout(self.scrollAreaWidgetContents)

        self.gridLayout = QtGui.QGridLayout()
        self.gridLayout.setObjectName("formLayout")
        self.gridLayout.setColumnStretch(0, 0)
        self.gridLayout.setColumnStretch(2, 4)

        self.scrollLayout.addLayout(self.gridLayout)
        self.scrollLayout.addStretch()

        self.scrollArea.setWidget(self.scrollAreaWidgetContents)
        mplMainWindow.setCentralWidget(self.centralwidget)

        self.btn_cU = QtGui.QPushButton("cU", self.inputContainer)
        self.btn_cU.setGeometry(QtCore.QRect(0, 0, 31, 27))
        self.btn_cU.setObjectName("btn_cU")  

        QtCore.QMetaObject.connectSlotsByName(mplMainWindow)



from mplwidget import MplWidget

mplwidget:

from PyQt4.QtGui import *
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas, NavigationToolbar2QT as NavigationToolbar 
from matplotlib.figure import Figure 

class MplCanvas(FigureCanvas):    
    def __init__(self):    
        self.fig = Figure()
        self.ax = self.fig.add_subplot(111)     
        FigureCanvas.__init__(self, self.fig)   
        FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Preferred)
        FigureCanvas.updateGeometry(self)

class MplWidget(QWidget):
    def __init__(self, parent = None):  
        QWidget.__init__(self, parent)   
        self.main_widget = QWidget(self)
        self.canvas = MplCanvas()
        self.ntb = NavigationToolbar(self.canvas, self.main_widget)

        self.vbl = QGridLayout()
        self.vbl.addWidget(self.canvas)
        self.vbl.addWidget(self.ntb)

        self.setLayout(self.vbl)

Solution

  • As it's not necessary to make extensive mathematical manipulations to solve this issue, I ignored that part. So let's assume that we want to change the slope of several lines using SpinBoxes.

    An option would be to make the lines be part of a class that controls the SpinBoxes as well as the matplotlib lines. I called it LineWidget in the code below. When the button is pressed a new LineWidget instance is created and added to a scroll area, where you can manipulate the parameter. Once the parameter changes the line is updated.

    Here is a full example where I also simplified the rest of the code.

    import sys
    from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
    from matplotlib.figure import Figure
    import numpy as np 
    from PyQt4 import QtGui , QtCore
    
    class Viewer(QtGui.QMainWindow):  
        def __init__(self, parent = None):
            super(Viewer, self).__init__(parent) 
            self.setupUI() 
            self.ax = self.fig.add_subplot(111)
            self.count = 0          
            self.container = []
            self.button.clicked.connect(self.addLine) 
    
        def setupUI(self):
            self.centralwidget = QtGui.QWidget(self)
            self.setCentralWidget(self.centralwidget)
            self.centralwidget.setLayout(QtGui.QHBoxLayout())
    
            self.leftWidget = QtGui.QWidget(self)
            self.leftWidget.setMinimumWidth(200)
            self.leftWidget.setLayout(QtGui.QVBoxLayout())
    
            self.mplWidget = QtGui.QWidget(self)
            self.mplWidget.setLayout(QtGui.QVBoxLayout())
    
            self.fig = Figure()
            self.canvas = FigureCanvas(self.fig)
            self.ntb = NavigationToolbar(self.canvas, self.mplWidget)
            self.mplWidget.layout().addWidget(self.canvas)
            self.mplWidget.layout().addWidget(self.ntb)
    
            self.button = QtGui.QPushButton("Push")
    
            self.scrollWidget = QtGui.QWidget()
            self.scrollLayout = QtGui.QVBoxLayout()
            self.scrollWidget.setLayout(self.scrollLayout)
            self.scrollLayout.addStretch()
            self.scrollArea = QtGui.QScrollArea()
            self.scrollArea.setWidgetResizable(True)
            self.scrollArea.setWidget(self.scrollWidget)
    
            self.leftWidget.layout().addWidget(self.button)
            self.leftWidget.layout().addWidget(self.scrollArea)
    
            self.centralwidget.layout().addWidget(self.leftWidget)
            self.centralwidget.layout().addWidget(self.mplWidget)
    
        def addLine(self):
            b = LineWidget(self.count, self.ax)
            self.container.append(b)
            self.scrollLayout.insertWidget(self.scrollLayout.count() - 1, b)
            self.count += 1
    
    
    class LineWidget(QtGui.QWidget):
        def __init__( self, number, ax, R=0, parent=None, **kwargs):
            super(LineWidget, self).__init__(parent)
            self.number = number
            label = QtGui.QLabel("cU " + str(self.number))
            self.spin = QtGui.QDoubleSpinBox()
            self.spin.setSingleStep(0.2)
            self.spin.setRange(-100,100)
            self.setLayout(QtGui.QHBoxLayout())
            self.layout().addWidget(label)
            self.layout().addWidget(self.spin)
    
            self.R = R
            self.t = np.linspace(0,1)
            self.f = lambda t, R: R*t
            self.ax = ax
            self.line, = self.ax.plot([],[], **kwargs)
    
            self.update()
            self.spin.valueChanged.connect(self.changed)
    
        def changed(self):
            self.R = self.spin.value()
            self.update()
    
        def update(self):
            self.line.set_data(self.t, self.f(self.t, self.R))
            self.ax.relim()
            self.ax.autoscale_view()
            self.ax.figure.canvas.draw_idle()
    
    
    app = QtGui.QApplication(sys.argv)
    viewer = Viewer()
    viewer.show()
    sys.exit(app.exec_())
    

    enter image description here