Search code examples
pythonanimationmatplotlibpyqt4qt-designer

Making a plot in a second window using data from main window


I'm trying to make a program in which I have a main window and a second window. The second window should be opened by checking a Check-Box in the main window and closed by unchecking it.

The following minimal example works already fine (thanks to ImportanceOfBeingErnest !), but I want to spin the arrow (the one, which is already bent when you run the example) by changing the SpinBox in the main window.

Solution: See 5th comment in the first answer!

      import sys
      from PyQt4 import QtGui, QtCore

      from matplotlib import pyplot as plt
      from mpl_toolkits.mplot3d import Axes3D
      from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
      from matplotlib import animation

      import numpy as np


      class Newsphere(QtGui.QMainWindow):

          def __init__(self):
              super(Newsphere, self).__init__()
              self.mainbox = QtGui.QWidget()
              self.mainbox.setLayout(QtGui.QHBoxLayout())
              self.setCentralWidget(self.mainbox)
              self.spin = QtGui.QSpinBox()
              self.spin.setValue(20)
              self.spin.setMaximum(100)
              self.spin.setMinimum(-100)
              self.checkPlot = QtGui.QCheckBox("Check")
              self.mainbox.layout().addWidget(self.spin)
              self.mainbox.layout().addWidget(self.checkPlot)

              self.Plot = None
              self.checkPlot.clicked.connect(self.showPlot)

         def showPlot(self):
             if self.Plot == None:
                 self.Plot = Plot(self.kinematic())
                 self.Plot.show()
                 # register signal for closure
                 self.Plot.signalClose.connect(self.uncheck)
                 # register signal for spin value changed
                 self.spin.valueChanged.connect(self.kinematic)
             else:
                 self.Plot.close()
                 self.Plot = None

        def kinematic(self):

            x = self.spin.value() / 100

            v = np.matrix([[1.,x,0.],[0.,1.,0.],[0.,0.,1.]])
            zero = np.matrix([[0.,0.,0.],[0.,0.,0.],[0.,0.,0.]])

            pos = np.hstack([v, zero])

            return pos

        def uncheck(self):
            self.checkPlot.setChecked(False)
            self.Plot = None

    class Plot(QtGui.QWidget):

        signalClose = QtCore.pyqtSignal()

        def __init__(self, pos=None):
            super(Plot, self).__init__()
            self.setLayout(QtGui.QHBoxLayout())

            self.fig = plt.figure()
            self.ax = self.fig.add_subplot(111,projection = '3d')
            self.fig.tight_layout()
            self.ax.view_init(40, 225)

            ''' dashed coordinate system '''
            self.ax.plot([0,1], [0,0], [0,0], label='$X_0$', linestyle="dashed", color="red")
            self.ax.plot([0,0], [0,-10], [0,0], label='$Y_0$', linestyle="dashed", color="green")
            self.ax.plot([0,0], [0,0], [0,1], label='$Z_0$', linestyle="dashed", color="blue")

            self.ax.set_xlim3d(-3,3)
            self.ax.set_ylim3d(-3,3)
            self.ax.set_zlim3d(-3,3)

            self.canvas = FigureCanvas(self.fig)
            self.layout().addWidget(self.canvas)

            self.pos = pos

            self.setup_plot()

            self.ani = animation.FuncAnimation(self.fig, self.update_plot, init_func=self.setup_plot, blit=True)

        def setup_plot(self):

            self.ax.legend(loc='best')

            self.position = self.ax.quiver(0, 0, 0, 0, 0, 0, pivot="tail", color="black")

            return self.position,

        def update_plot(self, i):

            x_zero = self.pos[:,3]
            y_zero = self.pos[:,4]
            z_zero = self.pos[:,5]

            v_x = self.pos[0,0:3]
            v_y = self.pos[1,0:3]
            v_z = self.pos[2,0:3]

            self.position = self.ax.quiver(-x_zero, -y_zero, z_zero, -v_x[0,:], v_y[0,:], v_z[0,:], pivot="tail", color="black")
            self.canvas.draw()

            return self.position,

        # We need to make sure the animation stops, when the window is closed
        def closeEvent(self, event):
            self.signalClose.emit()
            self.close()
            super(Plot, self).closeEvent(event)

        def close(self):
            self.ani.event_source.stop()
            super(Plot, self).close()


    if __name__ == '__main__':

        app = QtGui.QApplication(sys.argv)
        main = Newsphere()
        main.show()
        sys.exit(app.exec_())

Solution

  • Here is an working example of what I think you are trying to achieve.
    The Main Window has a spin box and a check box. Once the checkbox is clicked, a new window with a plot will show up and an animation will start. The current value and some array will be given to the plot window. If you change the spin box value while the animation is running, it will be updated. When the plot window is closed or when the checkbox is unchecked, the animation will stop (and be deleted).

    import sys
    from PyQt4 import QtGui, QtCore
    
    from matplotlib import pyplot as plt
    from mpl_toolkits.mplot3d import Axes3D
    from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib import animation
    
    import numpy as np
    
    
    class Newsphere(QtGui.QMainWindow):
    
        def __init__(self):
            super(Newsphere, self).__init__()
            self.mainbox = QtGui.QWidget()
            self.mainbox.setLayout(QtGui.QHBoxLayout())
            self.setCentralWidget(self.mainbox)
            self.spin = QtGui.QSpinBox()
            self.spin.setValue(5)
            self.spin.setMaximum(10)
            self.spin.setMinimum(1)
            self.checkPlot = QtGui.QCheckBox("Check")
            self.mainbox.layout().addWidget(self.spin)
            self.mainbox.layout().addWidget(self.checkPlot)
    
            self.Plot = None
            self.checkPlot.clicked.connect(self.showPlot)
    
        def showPlot(self):
            if self.Plot == None:
                self.Plot = Plot(self.kinematic(), self.spin.value())
                self.Plot.show()
                # register signal for closure
                self.Plot.signalClose.connect(self.uncheck)
                # register signal for spin value changed
                self.spin.valueChanged.connect(self.Plot.update_factor)
            else:
                self.Plot.close()
                self.Plot = None
    
        def kinematic(self):
            v = np.array([[1.,2.,3.],[2.,1.,3.],[3.,2.,1.]])
            return v
    
        def uncheck(self):
            self.checkPlot.setChecked(False)
            self.Plot = None
    
    class Plot(QtGui.QWidget):
    
        signalClose = QtCore.pyqtSignal()
    
        def __init__(self, v=None, factor=1):
            super(Plot, self).__init__()
            self.setLayout(QtGui.QHBoxLayout())
    
            self.fig = plt.figure()
            self.ax = self.fig.add_subplot(111,projection = '3d')
            self.ax.set_aspect('equal')
            self.fig.tight_layout()
            self.ax.view_init(40, 225)
    
            self.ax.set_xlim3d(0,3)
            self.ax.set_ylim3d(0,3)
            self.ax.set_zlim3d(0,4)
    
            self.canvas = FigureCanvas(self.fig)
            self.layout().addWidget(self.canvas)
    
            self.pos = v
    
            self.setup_plot()
            self.update_factor(factor)
    
            self.ani = animation.FuncAnimation(self.fig, self.update_plot, blit=False)
    
        def setup_plot(self):
            xpos, ypos = np.meshgrid(np.arange(self.pos.shape[0]),np.arange(self.pos.shape[1]) )
            self.xpos = xpos.flatten('F')
            self.ypos = ypos.flatten('F')
            self.zpos = np.zeros_like(self.xpos)
            self.bar = None
    
        def update_factor(self, factor):
            self.factor = factor
            self.dx = np.ones_like(self.xpos)*np.min(np.abs(self.factor/10.), 0.1)
            self.dy = self.dx.copy()
    
        def update_plot(self, i):
    
            if self.bar != None:
                self.bar.remove()
                del self.bar
            pos = self.pos+np.sin(i/8.)
            dz = pos.flatten()
    
            self.bar = self.ax.bar3d(self.xpos, self.ypos, self.zpos, self.dx, self.dy, dz, 
                            color=(1.-self.factor/10.,0,self.factor/10.), zsort='average', linewidth=0)
    
            self.canvas.draw()
    
        # We need to make sure the animation stops, when the window is closed
        def closeEvent(self, event):
            self.signalClose.emit()
            self.close()
            super(Plot, self).closeEvent(event)
    
        def close(self):
            self.ani.event_source.stop()
            super(Plot, self).close()
    
    
    if __name__ == '__main__':
    
        app = QtGui.QApplication(sys.argv)    
        main = Newsphere()
        main.show()
        sys.exit(app.exec_())
    

    Since I wasn't sure about what you want to animate, I changed the plot to a barplot, but you can change it back to whatever you need. Hope that helps.