0

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_())
Michael
  • 53
  • 2
  • 6
  • What exactly is not working? Since you never actually call `Kinematic` what would you expect? Also, it seems that `self.pos` is not actually defined anywhere, so I suppose `update_plot` isn't working correctly? – ImportanceOfBeingErnest Nov 08 '16 at 11:35
  • Do not mix `pyplot` with your own embedding, that can lead to subtle global state issues. You should use signal/slots to communicate between your classes. – tacaswell Nov 08 '16 at 14:15
  • @ImportanceOfBeingErnest: Ah sorry, I forgot one line (Line 67). When I try to run It now, I get the error "x_zero = self.pos[:,0] TypeError: 'function' object is not subscriptable". – Michael Nov 08 '16 at 15:47
  • @tacaswell: Ok and what is the best way to make a animated 3D-plot in a separate window? By animated I mean: one gives some values in a SpinBox (or what ever) in the main window which are then processed in the main Programm to calculate some numbers. And when I want to see this graphicaly, I check the Check-Box and see the plot... – Michael Nov 08 '16 at 15:51
  • @Michael : you probably forgot the brackets `Kinematic()`. However, this would not be very sensible to do anyways (you run in circles, Newsphere calls Plot, Plot calls Newshpere). It seems to me that whatever this `Kinematic()` returns is the parameter of the plot?! So it would be wise to provide it already at initialization, maybe as an argument to the `__init__(self, v)` and then `self.pos = v` ? – ImportanceOfBeingErnest Nov 08 '16 at 16:06

1 Answers1

0

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.

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • Thanks for this example! I added some changes in my code from your example and it already works better. But there is just one (important!) thing: in your example there is the function update_factor, which updates the plot when the spin.value is changed. There is also some calculation in this function... What I want is, that by changing of the spin.value the function kinematic is executed again (with the new spin.value) and the returned value from Kinematic is used as new plot-information (in this case a new self.pos). – Michael Nov 09 '16 at 16:18
  • I just want to make all the calculation in the main class (Newspeher) and use the subclass (Plot) just for plotting. Thanks again! – Michael Nov 09 '16 at 16:19
  • Of course you can send whatever value of `kinematic()` to your plot by connecting the `spin.valueChanged` signal to `kinematic` and update `Plot.pos` from within the `kinematic` function. Do you need help with that or was it just a comment? – ImportanceOfBeingErnest Nov 09 '16 at 16:52
  • Ok, I updated my previous code (see the question) with a working minimal example. I still don't know how to update the Plot with new data from the SpinBox... – Michael Nov 10 '16 at 09:21
  • Your example isn't working for me, but what you need to do is adding `if self.Plot != None: self.Plot.pos = pos` to the `kinematic()` function. – ImportanceOfBeingErnest Nov 10 '16 at 09:58