示例#1
0
 def clear_plotted_items(self, graph: pg.PlotWidget):
     dataItems = graph.listDataItems()
     for i in dataItems:
         # console(i.name(), " ", y_name)
         if i is not None:
             graph.removeItem(i)
     graph.plotItem.legend.clear()
class PyQtGraphDataPlot(QWidget):
    _colors = [
        Qt.red, Qt.blue, Qt.magenta, Qt.cyan, Qt.green, Qt.darkYellow,
        Qt.black, Qt.darkRed, Qt.gray, Qt.darkCyan
    ]

    def __init__(self, parent=None):
        super(PyQtGraphDataPlot, self).__init__(parent)
        self._plot_widget = PlotWidget()
        self._plot_widget.setBackground((255, 255, 255))
        self._plot_widget.setXRange(0, 10, padding=0)
        self.legend = self._plot_widget.addLegend()
        vbox = QVBoxLayout()
        vbox.addWidget(self._plot_widget)
        self.setLayout(vbox)

        self._color_index = 0
        self._curves = {}

    def add_curve(self, curve_id, curve_name, data_x, data_y):
        color = QColor(self._colors[self._color_index % len(self._colors)])
        self._color_index += 1
        pen = mkPen(color, width=1)
        plot = self._plot_widget.plot(name=curve_name, pen=pen)
        data_x = numpy.array(data_x)
        data_y = numpy.array(data_y)
        self._curves[curve_id] = {'x': data_x, 'y': data_y, 'plot': plot}
        self._update_legend()

    def remove_curve(self, curve_id):
        curve_id = str(curve_id)
        if curve_id in self._curves:
            self._plot_widget.removeItem(self._curves[curve_id]['plot'])
            del self._curves[curve_id]
            self._update_legend()

    def _update_legend(self):
        # TODO Figure this out
        pass

    @Slot(str, list, list)
    def update_values(self, curve_id, x, y):
        curve = self._curves[curve_id]
        curve['x'] = numpy.append(curve['x'], x)
        curve['y'] = numpy.append(curve['y'], y)

    def redraw(self):
        # Set axis bounds
        x_range, _ = self._plot_widget.viewRange()
        x_delta = x_range[1] - x_range[0]
        x_max = 0
        for curve in self._curves.values():
            if len(curve['x']) == 0:
                continue

            x_max = max(x_max, curve['x'][-1])
            curve['plot'].setData(curve['x'], curve['y'])

        self._plot_widget.setXRange(x_max - x_delta, x_max, padding=0)
示例#3
0
class RealtimePlotWidget(QWidget):
    COLORS = [
        Qt.red, Qt.blue, Qt.green, Qt.magenta, Qt.cyan, Qt.darkRed,
        Qt.darkBlue, Qt.darkGreen, Qt.darkYellow, Qt.gray
    ]

    def __init__(self, parent=None):
        super(RealtimePlotWidget, self).__init__(parent)
        self._plot_widget = PlotWidget()
        self._plot_widget.setBackground((0, 0, 0))
        self._plot_widget.addLegend()
        self._plot_widget.showButtons()
        self._plot_widget.enableAutoRange()
        self._plot_widget.showGrid(x=True, y=True, alpha=0.2)
        vbox = QVBoxLayout()
        vbox.addWidget(self._plot_widget)
        self.setLayout(vbox)

        self._color_index = 0
        self._curves = {}

    def add_curve(self, curve_id, curve_name, data_x=[], data_y=[]):
        color = QColor(self.COLORS[self._color_index % len(self.COLORS)])
        self._color_index += 1
        pen = mkPen(color, width=1)
        plot = self._plot_widget.plot(name=curve_name, pen=pen)
        data_x = numpy.array(data_x)
        data_y = numpy.array(data_y)
        self._curves[curve_id] = {'x': data_x, 'y': data_y, 'plot': plot}

    def remove_curve(self, curve_id):
        curve_id = str(curve_id)
        if curve_id in self._curves:
            self._plot_widget.removeItem(self._curves[curve_id]['plot'])
            del self._curves[curve_id]

    def set_x_range(self, left, right):
        self._plot_widget.setRange(xRange=(left, right))

    def update_values(self, curve_id, x, y):
        curve = self._curves[curve_id]
        curve['x'] = numpy.append(curve['x'], x)
        curve['y'] = numpy.append(curve['y'], y)

    def redraw(self):
        for curve in self._curves.values():
            if len(curve['x']):
                curve['plot'].setData(curve['x'], curve['y'])

    def lazy_redraw(self, period):
        timestamp = time.time()
        if not hasattr(self, '_prev_lazy_redraw'):
            self._prev_lazy_redraw = 0.0
        if timestamp - self._prev_lazy_redraw > period:
            self._prev_lazy_redraw = timestamp
            self.redraw()
示例#4
0
class RealtimePlotWidget(QWidget):
    COLORS = [Qt.red, Qt.blue, Qt.green, Qt.magenta, Qt.cyan,
              Qt.darkRed, Qt.darkBlue, Qt.darkGreen, Qt.darkYellow, Qt.gray]

    def __init__(self, parent=None):
        super(RealtimePlotWidget, self).__init__(parent)
        self._plot_widget = PlotWidget()
        self._plot_widget.setBackground((0, 0, 0))
        self._plot_widget.addLegend()
        self._plot_widget.showButtons()
        self._plot_widget.enableAutoRange()
        self._plot_widget.showGrid(x=True, y=True, alpha=0.2)
        vbox = QVBoxLayout()
        vbox.addWidget(self._plot_widget)
        self.setLayout(vbox)

        self._color_index = 0
        self._curves = {}

    def add_curve(self, curve_id, curve_name, data_x=[], data_y=[]):
        color = QColor(self.COLORS[self._color_index % len(self.COLORS)])
        self._color_index += 1
        pen = mkPen(color, width=1)
        plot = self._plot_widget.plot(name=curve_name, pen=pen)
        data_x = numpy.array(data_x)
        data_y = numpy.array(data_y)
        self._curves[curve_id] = {'x': data_x, 'y': data_y, 'plot': plot}

    def remove_curve(self, curve_id):
        curve_id = str(curve_id)
        if curve_id in self._curves:
            self._plot_widget.removeItem(self._curves[curve_id]['plot'])
            del self._curves[curve_id]

    def set_x_range(self, left, right):
        self._plot_widget.setRange(xRange=(left, right))

    def update_values(self, curve_id, x, y):
        curve = self._curves[curve_id]
        curve['x'] = numpy.append(curve['x'], x)
        curve['y'] = numpy.append(curve['y'], y)

    def redraw(self):
        for curve in self._curves.values():
            if len(curve['x']):
                curve['plot'].setData(curve['x'], curve['y'])

    def lazy_redraw(self, period):
        timestamp = time.time()
        if not hasattr(self, '_prev_lazy_redraw'):
            self._prev_lazy_redraw = 0.0
        if timestamp - self._prev_lazy_redraw > period:
            self._prev_lazy_redraw = timestamp
            self.redraw()
示例#5
0
 def update_graph(self,
                  graph: pg.PlotWidget,
                  x,
                  y,
                  y_name,
                  x_Unit,
                  y_Unit='V',
                  color=(255, 255, 102)):
     """
     Updates a graph
     :param graph: plotWidget
     :param x: x dataset
     :param y: y dataset
     :param y_name: name (MUST)
     :param color: default: 255, 255, 102
     :return:
     """
     sizex = len(x)
     sizey = len(y)
     np_x = np.asarray(x)
     np_y = np.asarray(y)
     if sizex == sizey:
         dataItems = graph.listDataItems()
         for i in dataItems:
             # console(i.name(), " ", y_name)
             if i is not None:
                 if i.name() == y_name:
                     graph.removeItem(i)
         cpen = mkPen(color=color, width=3)
         # npx, npy = get_mod_array(np_x, np_y, self.ui.corZeroBox.isChecked(), self.ui.formulaEdit.text())
         graph.plot(np_x, np_y, pen=cpen, name=y_name)
         self.replot_saved_graphs()
         graph.setLabel('bottom', "Time scale", units=str(x_Unit))
         graph.setLabel('left', "CH scale", units=str(y_Unit))
     else:
         console("Inequality", y_name, " ; ", sizex, " ; ", sizey)
         self.append_html_paragraph(
             "Inequality: " + str(y_name) + " ; " + str(sizex) + " ; " +
             str(sizey), -1, True)
示例#6
0
class widget_mfi_lon_plot(QWidget):

    #-----------------------------------------------------------------------
    # DEFINE THE INITIALIZATION FUNCTION.
    #-----------------------------------------------------------------------

    def __init__(self, core):

        # Inherit all attributes of an instance of "QWidget".

        super(widget_mfi_lon_plot, self).__init__()

        # Store the Janus core.

        self.core = core

        # Prepare to respond to signals received from the core.

        self.connect(self.core, SIGNAL('janus_rset'), self.resp_rset)
        self.connect(self.core, SIGNAL('janus_chng_mfi'), self.resp_chng_mfi)

        # Initialize this widget's instance of "PlotWidget", which will
        # contain the plot of MFI magnetic field data.

        # Note.  The "QGridLayout" object given to this widget as its
        #        layout is essentially a dummy.  I could have just had
        #        this class inherit "PlotWidget", but I think that this
        #        gives me a bit more control (and a similar structure
        #        "janus_widget_fc_cup").

        self.setLayout(QGridLayout())

        self.plt = PlotWidget()
        self.layout().addWidget(self.plt)

        self.layout().setContentsMargins(0, 0, 0, 0)

        # Extract the individual elements of the "PlotWidget" object
        # (e.g., it's axes) for more convenient access later.

        self.vbx = self.plt.getViewBox()

        self.axs_x = self.plt.getAxis('bottom')
        self.axs_y = self.plt.getAxis('left')

        self.ptm = self.plt.getPlotItem()

        # Initialize and store the pens and fonts.

        self.pen_vbx = mkPen(color='k')
        self.pen_crv_lon = mkPen(color='#FFD700')

        self.fnt = self.core.app.font()

        # Configure the plot: disable automatic adjustments and
        # adjustments made by the user, change the background and
        # foreground colors, enable grid lines for both axes, label the
        # axes, adjust the tick font size, adjust the "AxisItem" sizes,
        # and add a margin around the entire plot.

        self.plt.disableAutoRange()
        self.plt.setMouseEnabled(False, False)
        self.plt.setMenuEnabled(False)
        self.plt.hideButtons()

        self.plt.setBackground('w')
        setConfigOption('foreground', 'k')

        #####self.plt.showGrid( True, True )

        labelStyle = {'color': 'k'}
        self.axs_x.setLabel('Time [s]', **labelStyle)
        self.axs_y.setLabel('Azim. [deg]', **labelStyle)

        self.axs_x.label.setFont(self.fnt)
        self.axs_y.label.setFont(self.fnt)

        self.axs_x.setTickFont(self.fnt)
        self.axs_y.setTickFont(self.fnt)

        self.axs_x.setHeight(35)
        self.axs_y.setWidth(40)

        self.vbx.border = self.pen_vbx

        self.ptm.setContentsMargins(5, 5, 5, 5)

        # Initialize the curves that will be added to this plot.

        self.crv_lon = None

        # Populate this plot and adjust it's settings.

        self.make_plt()

    #-----------------------------------------------------------------------
    # DEFINE THE FUNCTION FOR POPULATING THE PLOT.
    #-----------------------------------------------------------------------

    def make_plt(self):

        # Reset the plot (i.e., remove all plot elements).

        self.rset_plt()

        # Establish the ranges of its time and magnetic field values.
        # If the core contains no data or only a single datum,
        # improvise (for the purpose of later establishing axis limits).

        if (self.core.n_mfi >= 1):

            # Establish the domain of the plot.

            t_min = min(amin(self.core.mfi_s), 0.)
            t_max = max(amax(self.core.mfi_s), self.core.fc_spec['dur'])

            # Establish the range of the plot.  As part of this,
            # ensure that the range satisfies a minimum size and has
            # sufficient padding.

            ang_max = max(self.core.mfi_b_lon)
            ang_min = min(self.core.mfi_b_lon)

            ang_max = 5. + ang_max
            ang_min = -5. + ang_min

            d_t_0 = t_max - t_min

            d_t = max(1.5 + d_t_0, 3.)

            t_max = t_min + d_t

        else:

            t_min = 0.001
            t_max = 3.500

            ang_min = -360
            ang_max = 360

        # Set the range of the axis of each plot.

        self.plt.setXRange(t_min, t_max, padding=0.0)
        self.plt.setYRange(ang_min, ang_max, padding=0.0)

        # If the core contains no Wind/MFI magnetic field data, return.

        if (self.core.n_mfi <= 0):
            return

        # Generate and display each curve for the plot.

        self.crv_lon = PlotDataItem(self.core.mfi_s,
                                    self.core.mfi_b_lon,
                                    pen=self.pen_crv_lon)

        self.plt.addItem(self.crv_lon)

    #-----------------------------------------------------------------------
    # DEFINE THE FUNCTION FOR RESETTING THIS PLOT (CLEARING ALL ELEMENTS).
    #-----------------------------------------------------------------------

    def rset_plt(self):

        # Hide and remove each of this plot's elements.

        if (self.crv_lon is not None):
            self.plt.removeItem(self.crv_lon)

        # Permanently delete this plot's elements by setting each of the
        # variables that store them to "None".

        self.crv_lon = None

    #-----------------------------------------------------------------------
    # DEFINE THE FUNCTION FOR RESPONDING TO THE "rset" SIGNAL.
    #-----------------------------------------------------------------------

    def resp_rset(self):

        # Reset the plot.

        self.rset_plt()

    #-----------------------------------------------------------------------
    # DEFINE THE FUNCTION FOR RESPONDING TO THE "chng_mfi" SIGNAL.
    #-----------------------------------------------------------------------

    def resp_chng_mfi(self):

        # Regenerate the plot.

        self.make_plt()
示例#7
0
class widget_mfi_lin_plot(QWidget):

    #-----------------------------------------------------------------------
    # DEFINE THE INITIALIZATION FUNCTION.
    #-----------------------------------------------------------------------

    def __init__(self, core):

        # Inherit all attributes of an instance of "QWidget".

        super(widget_mfi_lin_plot, self).__init__()

        # Store the Janus core.

        self.core = core

        # Prepare to respond to signals received from the core.

        self.connect(self.core, SIGNAL('janus_rset'), self.resp_rset)
        self.connect(self.core, SIGNAL('janus_chng_mfi'), self.resp_chng_mfi)

        # Initialize this widget's instance of "PlotWidget", which will
        # contain the plot of MFI magnetic field data.

        # Note.  The "QGridLayout" object given to this widget as its
        #        layout is essentially a dummy.  I could have just had
        #        this class inherit "PlotWidget", but I think that this
        #        gives me a bit more control (and a similar structure
        #        "janus_widget_fc_cup").

        self.setLayout(QGridLayout())

        self.plt = PlotWidget()
        self.layout().addWidget(self.plt)

        self.layout().setContentsMargins(0, 0, 0, 0)

        # Extract the individual elements of the "PlotWidget" object
        # (e.g., it's axes) for more convenient access later.

        self.vbx = self.plt.getViewBox()

        self.axs_x = self.plt.getAxis('bottom')
        self.axs_y = self.plt.getAxis('left')

        self.ptm = self.plt.getPlotItem()

        # Initialize and store the pens and fonts.

        self.pen_vbx = mkPen(color='k')
        self.pen_crv_m = mkPen(color='k')
        self.pen_crv_n = mkPen(color='k')
        self.pen_crv_x = mkPen(color='r')
        self.pen_crv_y = mkPen(color='g')
        self.pen_crv_z = mkPen(color='b')

        self.fnt = self.core.app.font()

        # Configure the plot: disable automatic adjustments and
        # adjustments made by the user, change the background and
        # foreground colors, enable grid lines for both axes, label the
        # axes, adjust the tick font size, adjust the "AxisItem" sizes,
        # and add a margin around the entire plot.

        self.plt.disableAutoRange()
        self.plt.setMouseEnabled(False, False)
        self.plt.setMenuEnabled(False)
        self.plt.hideButtons()

        self.plt.setBackground('w')
        setConfigOption('foreground', 'k')

        #####self.plt.showGrid( True, True )

        labelStyle = {'color': 'k'}
        self.axs_x.setLabel('Time [s]', **labelStyle)
        self.axs_y.setLabel('Magnetic Field [nT]', **labelStyle)

        self.axs_x.label.setFont(self.fnt)
        self.axs_y.label.setFont(self.fnt)

        self.axs_x.setTickFont(self.fnt)
        self.axs_y.setTickFont(self.fnt)

        self.axs_x.setHeight(35)
        self.axs_y.setWidth(40)

        self.vbx.border = self.pen_vbx

        self.ptm.setContentsMargins(5, 5, 5, 5)

        # Initialize the curves that will be added to this plot.

        self.crv_m = None
        self.crv_n = None
        self.crv_x = None
        self.crv_y = None
        self.crv_z = None
        self.pl = []

        # Populate this plot and adjust it's settings.

        self.make_plt()

    #-----------------------------------------------------------------------
    # DEFINE THE FUNCTION FOR POPULATING THE PLOT.
    #-----------------------------------------------------------------------

    def make_plt(self):

        # Reset the plot (i.e., remove all plot elements).

        self.rset_plt()

        # Establish the ranges of its time and magnetic field values.
        # If the core contains no data or only a single datum,
        # improvise (for the purpose of later establishing axis limits).

        if (self.core.n_mfi >= 1):

            # Establish the domain of the plot.

            t_min = min(amin(self.core.mfi_s), 0.)
            t_max = max(amax(self.core.mfi_s), self.core.fc_spec['dur'])

            # Establish the range of the plot.  As part of this,
            # ensure that the range satisfies a minimum size and has
            # sufficient padding.

            b_max = amax(self.core.mfi_b)
            b_min = -b_max

            d_t_0 = t_max - t_min
            d_b_0 = b_max - b_min

            d_t = max(1.5 + d_t_0, 3.)
            d_b = max(1.2 * d_b_0, 5.)

            t_max = t_min + d_t

            b_min = b_min - (d_b - d_b_0) / 2.
            b_max = b_max + (d_b - d_b_0) / 2.

        else:

            t_min = 0.001
            t_max = 3.500

            b_min = -2.5
            b_max = 2.5

        # Set the range of the axis of each plot.

        self.plt.setXRange(t_min, t_max, padding=0.0)
        self.plt.setYRange(b_min, b_max, padding=0.0)

        # Set the PESA-L pen with a width corresponding to one rotation
        # Note: For some reason, the lines are not wide enough unless 5
        #       is added to the scaled width of the rotation time

        rot = 3.05 * self.axs_x.width() / (t_max - t_min) + 5

        self.pen_pl = mkPen(color=(245, 245, 245), width=rot)

        # If the core contains no Wind/MFI magnetic field data, return.

        if (self.core.n_mfi <= 0):
            return

        # Generate and display each curve for the plot.

        self.crv_m = PlotDataItem(self.core.mfi_s,
                                  self.core.mfi_b,
                                  pen=self.pen_crv_m)
        self.crv_n = PlotDataItem(self.core.mfi_s,
                                  [-b for b in self.core.mfi_b],
                                  pen=self.pen_crv_n)
        self.crv_x = PlotDataItem(self.core.mfi_s,
                                  self.core.mfi_b_x,
                                  pen=self.pen_crv_x)
        self.crv_y = PlotDataItem(self.core.mfi_s,
                                  self.core.mfi_b_y,
                                  pen=self.pen_crv_y)
        self.crv_z = PlotDataItem(self.core.mfi_s,
                                  self.core.mfi_b_z,
                                  pen=self.pen_crv_z)

        # If PESA-L spectra were loaded, add the vertical indicators
        # showing their time relative to the start of the FC spectrum

        for n in range(len(self.core.pl_spec_arr)):

            time = self.core.pl_spec_arr[n]['time'][0]

            t_0 = self.core.fc_spec['time']

            delta_t = (time - t_0).total_seconds()

            self.pl += [
                InfiniteLine(delta_t + self.core.fc_spec['rot'] / 2.,
                             pen=self.pen_pl)
            ]

        for i in range(len(self.pl)):

            self.plt.addItem(self.pl[i])

        self.plt.addItem(self.crv_m)
        self.plt.addItem(self.crv_n)
        self.plt.addItem(self.crv_x)
        self.plt.addItem(self.crv_y)
        self.plt.addItem(self.crv_z)

    #-----------------------------------------------------------------------
    # DEFINE THE FUNCTION FOR RESETTING THIS PLOT (CLEARING ALL ELEMENTS).
    #-----------------------------------------------------------------------

    def rset_plt(self):

        # Hide and remove each of this plot's elements.

        if (self.crv_m is not None):
            self.plt.removeItem(self.crv_m)

        if (self.crv_n is not None):
            self.plt.removeItem(self.crv_n)

        if (self.crv_x is not None):
            self.plt.removeItem(self.crv_x)

        if (self.crv_y is not None):
            self.plt.removeItem(self.crv_y)

        if (self.crv_z is not None):
            self.plt.removeItem(self.crv_z)

        if (self.pl != []):

            for i in range(len(self.pl)):

                self.plt.removeItem(self.pl[i])

        # if ( self.crv_colat is not None ) :
        # 	self.plt.removeItem( self.crv_colat )

        # if ( self.crv_lon is not None ) :
        # 	self.plt.removeItem( self.crv_lon )

        # Permanently delete this plot's elements by setting each of the
        # variables that store them to "None".

        self.crv_m = None
        self.crv_n = None
        self.crv_x = None
        self.crv_y = None
        self.crv_z = None
        self.pl = []

    #-----------------------------------------------------------------------
    # DEFINE THE FUNCTION FOR RESPONDING TO THE "rset" SIGNAL.
    #-----------------------------------------------------------------------

    def resp_rset(self):

        # Reset the plot.

        self.rset_plt()

    #-----------------------------------------------------------------------
    # DEFINE THE FUNCTION FOR RESPONDING TO THE "chng_mfi" SIGNAL.
    #-----------------------------------------------------------------------

    def resp_chng_mfi(self):

        # Regenerate the plot.

        self.make_plt()
示例#8
0
class MesCourbes():
    def __init__(self):
        self.plot_widget = PlotWidget()
        self.plot_widget.showGrid(x=True,y=True)
        # self.plot_widget.getPlotItem().addLegend()
        self.plot_widget.setBackground((0, 0, 0))
        #  dictionary with all the curve and their data 
        # curve 0 is dedicated to the live/acquisition plot
        self.curves = {}

    def add_curve(self, curve_id, curve_color, markers_on=False):
        curve_name = curve_id
        pen = pg.mkPen(curve_color, width=3)
        symbol = "o"
        symbolPen = pg.mkPen(0,0,0)
        symbolBrush = curve_color
        symbolSize = 8
        # this adds the item to the plot and legend
        if markers_on:
            plot = self.plot_widget.plot(
                name=curve_name, pen=pen, symbol=symbol, symbolPen=symbolPen, 
                symbolBrush=symbolBrush, symbolSize=symbolSize
            )
        else:
            plot = self.plot_widget.plot(name=curve_name, pen=pen)
        self.curves[curve_id] = {
                    'plot':plot,
                    'data':{'Current':[],'X':[],'Y':[],'R':[],'Phi':[]}
                        }

    def average(self,curve_id,param_tree):
        current_min = param_tree.give('Laser Driver','current min')
        current_max = param_tree.give('Laser Driver','current max')
        nbr_pts = param_tree.give('Laser Driver','nbr pts')
        nbr_seqs = param_tree.give('Laser Driver','nbr seqs')
        current_list = np.linspace(current_min,current_max,nbr_pts)
        self.curves[curve_id]['data']['Current'] = current_list
        for k in ['X','Y','R','Phi']:
            temp = self.curves[curve_id]['data'][k]
            self.curves[curve_id]['data'][k] = toolbox.function.average(temp,nbr_seqs)

    def clear_data(self,curve_id):
        for k in ['Current','X','Y','R','Phi']:
            self.curves[curve_id]['data'][k] = []       

    def create_current_list(self,curve_id,param_tree):
        '''
        Create a array with all the frequency use for the frequency sweep
        It take into account if many sequences have been asked
        '''
        current_min = param_tree.give('Laser Driver','current min')
        current_max = param_tree.give('Laser Driver','current max')
        nbr_pts = param_tree.give('Laser Driver','nbr pts')
        nbr_seqs = param_tree.give('Laser Driver','nbr seqs')
        current_list = np.linspace(current_min,current_max,nbr_pts)
        if nbr_seqs > 1:
            temp = current_list
            i=0
            while i < int(nbr_seqs)-1:
                i+=1
                current_list = np.append(current_list,temp[::(-1)**i])
        self.curves[curve_id]['data']['Current'] = current_list

    def display(self,curve_id,what):
        X = self.curves[curve_id]['data']['Current']
        Y = self.curves[curve_id]['data'][what]
        # during acquisition freq is longer than the others datas
        # so it is useful to reduce it
        if len(X) != len(Y):
            X = self.curves[curve_id]['data']['Current'][0:len(Y)]
        self.set_values(curve_id,X,Y)

    def remove_curve(self, curve_id):
        curve_id = str(curve_id)
        if curve_id in self.curves:
            self.plot_widget.removeItem(self.curves[curve_id]['plot'])
            del self.curves[curve_id]

    def set_values(self, curve_id, data_x, data_y):
        curve = self.curves[curve_id]['plot']
        curve.setData(data_x, data_y)

    def update_X_Y_R_Phi(self,curve_id,A):
        self.curves[curve_id]['data']['X'] = np.append(self.curves[curve_id]['data']['X'],A[0])
        self.curves[curve_id]['data']['Y'] = np.append(self.curves[curve_id]['data']['Y'],A[1])
        self.curves[curve_id]['data']['R'] = np.append(self.curves[curve_id]['data']['R'],A[2])
        self.curves[curve_id]['data']['Phi'] = np.append(self.curves[curve_id]['data']['Phi'],A[3]) 
class clsDataView(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()

        # 类成员变量初始化
        self.colorDex = ['#7CFC00', '#B22222', '#E0FFFF', '#FFFF00', '#66FF00']

        self.lPlottedItems = []         # list of plotItems in the dataplot area
        self.currentPlotWin = ''        # keep current selected plot window for next curve plotting
        self.curLabelofYvalue = None          # the label of Y value in current plot area
        self.lPlotWindows = ['Plot1']            # list of plot window
        self.lViewBoxes = []              # list of View box corresponding to the plotitem
        self.lAxisItems = []           # list of axis item of the layout of plotItem
        self.lPlottedCurves = []            # list of plotCurves of each plotItem
        self.lDataFileName = []          # data file name list
        self.shortfname = ''           # data file name without path
        self.bPlotted = False           # not curve is plotted   - could be replaced by len(lPlotItems) > 1
        self.dataInRange_x = []           # keep the x ['TIME'] of data in range  - first curve plotted
        self.dataInRange_y = []           # keep the y of data in range  - first curve plotted

        self.lTestDATA = []      # the test data to be reviewed, each item is a class of data structure
                                    #  [testData1, testData2 ...]
                                    #  [(filename, column name, dataframe of data)
        self.parColPlotted = []           # parameter column in plotting

        self.minTimestamp = 1514764800.0      # the minimum of 20180101 08:00:00, ie. 1514764800.0 = datetime.datetime.strptime('2018-1-1 8:00:0', '%Y-%m-%d %H:%M:%S').timestamp()
        self.maxTimestamp = 1514800800.0 # datetime.strptime('2018-1-1 18:00:0', '%Y-%m-%d %H:%M:%S').timestamp()
        self.minYvalue = -20000
        self.maxYvalue = 20000

            # r'C:\onedrive\OneDrive - Honeywell\VPD\parameters code.csv'
        self.dataparam = dataParam(self.resource_path('parameters_code.csv'))   # data parameter definition
        #self.dataparam = dateParam()
        paramlist = self.dataparam.getParamName()
        #self.dataparam.getParamInfo('ABCVIINR', 'paramDesc')
        #self.dfData = pd.DataFrame()    # pandas dataframes to be plot

        # pyqtGraph 相关设置,必须要在self.setupUi之前
        setConfigOption('background', 'w')  # before loading widget

        # # set the time axis of X
        ### TODO: need to comment the self.dataplot line in the mainUI.py if it is recreated
        ###        or there is a error the plot widget being with no name of Plot1
        xAxis = self.TimeAxisItem(orientation='bottom')
        self.dataPlot = PlotWidget(self, axisItems={'bottom': xAxis}, name='Plot1')  ### TODO: need to comment the self.dataplot line in the mainUI.py if it is recreated

        self.setupUi(self)
        self.initUI()


        self.show()
        #self.showMaximized()   # max the window


    def initUI(self):

        # 添加打开菜单
        selFileAction = QAction('&Open', self)  # QAction(QIcon('open.png'), '&Open', self)
        selFileAction.setShortcut('Ctrl+O')
        selFileAction.setStatusTip('Open new File')
        selFileAction.triggered.connect(self.openFile)     # open data file
        selFileAction.setIcon(QIcon(self.resource_path('import.png')))

        exitAction = QAction('&Exit', self)    #QtGui.QAction(QIcon('exit.png'), '&Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit the application')
        #exitAction.triggered.connect(QtGui.qApp.quit)
        exitAction.triggered.connect(self.exitAPP)     # exit the application
        exitAction.setIcon(QIcon(self.resource_path('exit.png')))

        clearAction = QAction('Clear', self)   # QtGui.QAction(QIcon('Clear.png'), 'Clear', self)
        clearAction.triggered.connect(self.clearPlotArea)
        clearAction.setIcon(QIcon(self.resource_path('clear.png')))

        addPlotAction = QAction( 'Add a Plot', self)  #QtGui.QAction(QIcon('Addplot.png'), 'Add a Plot', self)
        addPlotAction.triggered.connect(self.addPlotAera)
        addPlotAction.setIcon(QIcon(self.resource_path('addplot.png')))

        removePlotAction = QAction('Remove the Plot', self) # QtGui.QAction(QIcon('Addplot.png'), 'Remove a Plot', self)
        removePlotAction.triggered.connect(self.removeDataPlotWin)
        removePlotAction.setIcon(QIcon(self.resource_path('remvplot.png')))

        viewAllAction = QAction("View All", self)
        viewAllAction.triggered.connect(self.autoRangeAllWins)
        viewAllAction.setIcon(QIcon(self.resource_path('viewall.png')))

        menubar = self.menuBar()
        fileMenu = menubar.addMenu('&File')         # add menu File
        fileMenu.addAction(selFileAction)            # link menu bar to openfile action with a menu item
        fileMenu.addAction(exitAction)               # add menu item exit

        plotMenu = menubar.addMenu("Plot")           # add menu Plot
        plotMenu.addAction(clearAction)               # add menu item of 'Clear' plot
        plotMenu.addAction(addPlotAction)             # add menu item of 'Add a Plot'
        plotMenu.addAction(removePlotAction)          # add menu item of 'Add a Plot'

        helpMenu = menubar.addMenu("Help")  # add menu help
        helpAction = QAction('?', helpMenu)
        helpAction.triggered.connect(self.helpme)
        helpMenu.addAction(helpAction)

        toolBar = self.addToolBar("Open")
        toolBar.addAction(selFileAction)             # link tool bar to openfile action

        toolBar.addAction(clearAction)
        toolBar.addAction(addPlotAction)
        toolBar.addAction(removePlotAction)
        toolBar.addAction(viewAllAction)




        # toolBar = self.addToolBar('Exit')
        # toolBar.addAction(selExitAction)  # link menu bar to openfile action

        # 设置dataPlot  class: PlotWidget
        self.dataPlot.plotItem.showGrid(True, True, 0.5)
        #self.dataPlot.plotItem.addLegend()

        self.dataPlot.setAutoVisible(y=True)


        # 设置treeWidget的相关  class: QTreeWidget
        self.treeWidget.setContextMenuPolicy(Qt.CustomContextMenu)
        self.treeWidget.customContextMenuRequested.connect(self.showContextMenu)
        self.treeWidget.treeContextMenu = QMenu(self)
        self.actionA = self.treeWidget.treeContextMenu.addAction(u'Plot')
        self.actionA.triggered.connect(
            lambda: self.plotData(self.currSelctPlotWgt, self.treeWidget.selectedItems()))
        self.treeWidget.setColumnCount(4)
        self.treeWidget.setHeaderLabels(['#', 'Parameter', 'Parameter Name', 'Unit'])
        self.treeWidget.setColumnWidth(0, 10)
        self.treeWidget.setColumnWidth(1, 50)
        self.treeWidget.setColumnWidth(2, 100)

        ### drag and drop
        self.treeWidget.setDragDropMode(self.treeWidget.DragOnly)


        # set up context menu of list widget
        self.listWidget.setContextMenuPolicy(Qt.CustomContextMenu)
        self.listWidget.customContextMenuRequested.connect(self.showListContextMenu)
        self.listWidget.listContextMenu = QMenu(self)
        self.actionB = self.listWidget.listContextMenu.addAction(u'Remove')
        self.actionB.triggered.connect(
            lambda: self.removeItemInPlot(self.listWidget.selectedItems()))


        #################### get the test data from the import window
        self.winImpData = clsImportData(self.dataparam, self.lTestDATA)     # instance of the ImportData window

        # # x axis for time
        # xAxis = self.TimeAxisItem("bottom")
        xAxis = self.dataPlot.plotItem.axes['bottom']['item']
        # plotitem and viewbox
        ## at least one plotitem is used whioch holds its own viewbox and left axis
        viewBox = self.dataPlot.plotItem.vb  # reference to viewbox of the plotitem
        viewBox.scaleBy(y=None)

        # # link x axis to view box
        xAxis.linkToView(viewBox)

        self.dataPlot.plotItem.scene().sigMouseMoved.connect(self.mouseMove)
        #self.dataPlot.plotItem.scene().sigMouseClicked.connect(self.mouseClick)

        # self.dataPlot.HoverEnterEvent = self.hoverEnterEvent

        ## drag and drop
        # self.dataPlot.dragEnterEvent = self.dragEnterEvent
        # self.dataPlot.plotItem.setAcceptDrops(True)
        # self.dataPlot.plotItem.dropEvent = self.dropEvent


        vLine = InfiniteLine(angle=90, movable=False, name='vline')
        hLine = InfiniteLine(angle=0, movable=False, name='hline')

        self.dataPlot.addItem(vLine, ignoreBounds=True)
        self.dataPlot.addItem(hLine, ignoreBounds=True)

        # set the default plot range
        self.dataPlot.setXRange(self.minTimestamp,self.maxTimestamp,padding=20)
        self.dataPlot.setYRange(-10, 10, padding=20)

        self.dataPlot.plotItem.getViewBox().setLimits()

        self.dataPlot.plotItem.getAxis('left').setWidth(w=30)
        self.dataPlot.plotItem.hideButtons()


        #self.dataPlot.plotItem.scene().sigMouseLeave.connect(self.mouseLeave) # ##TODO: cleaning house job
        self.dataPlot.installEventFilter(self)

        txtY_value = TextItem("", fill=(0, 0, 255, 80), anchor=(0,1),color='w')
        txtY_value.setParentItem(viewBox)

        self.curLabelofYvalue = txtY_value
        # #self.dataPlot.addItem(self.lableY_value)
        # labelY_value.setPos(self.minTimestamp,100.0)


        self.configPlotArea(self.dataPlot)

        # set current selection plot window background
        self.currSelctPlotWgt = self.dataPlot
        self.currSelctPlotWgt.setBackground(0.95)

    def eventFilter(self, source, event):
        #print (event.type())
        if event.type() == QEvent.Enter: #HoverEnter:
            #print("Enter " + source.plotItem.vb.name)
            self.currSelctPlotWgt.setBackground('default')
            self.currSelctPlotWgt = source
            self.currSelctPlotWgt.setBackground(0.95)
            plotAreaName = source.plotItem.vb.name
            #self.lPlottedItems.append({'Plot': plotWgtName, 'Curvename': curve_name, 'Filename': filename})

            labelofYvalueExisting = False
            plotAreaDirty = False
            for i in self.lPlottedItems:
                if i['Plot'] == plotAreaName:  # there is at least a curve in the plot
                    plotAreaDirty = True
                    break

            if plotAreaDirty:

                # get the lable of labelY_value
                for item in source.getViewBox().childItems():
                    if isinstance(item, graphicsItems.TextItem.TextItem):  # the text label is linked to the viewbox, not showing up
                        self.curLabelofYvalue = item
                        source.addItem(self.curLabelofYvalue)                 # add the text label to show it up
                        labelofYvalueExisting = True
                        break
                if not labelofYvalueExisting:
                    for item in source.plotItem.items:                      # the text label is in the plot item list
                        if isinstance(item, graphicsItems.TextItem.TextItem):
                            self.curLabelofYvalue = item
                            break


        if event.type() == QEvent.Leave: # and source is self.dataPlot:
            #print("Leave " + source.plotItem.vb.name)

            for item in source.plotItem.items:
                if isinstance(item, graphicsItems.TextItem.TextItem):
                    source.plotItem.removeItem(item)                    # remove the item
                    item.setParentItem(source.getViewBox())             # keep the link of the text label in the view box
                    break

            # move the hline to 0
            for iLine in source.items():  # loop for the hline
                if hasattr(iLine, 'name'):
                    if iLine.name() == 'hline':
                        iLine.setPos(self.minTimestamp)
                        break

        # print(event.type())
        # if event.type() == QEvent.GraphicsSceneDragEnter:
        #     self.currSelctPlotWgt.setBackground('default')
        #     self.currSelctPlotWgt = source
        #     self.currSelctPlotWgt.setBackground(0.95)

        return super(clsDataView,self).eventFilter(source,event)


    def configPlotArea(self, plotWin):

        vLine = InfiniteLine(angle=90, movable=False, name='vline')
        hLine = InfiniteLine(angle=0, movable=False, name='hline')
        plotWin.addItem(vLine, ignoreBounds=True)
        plotWin.addItem(hLine, ignoreBounds=True)
        #self.dataPlotRange.addItem(self.region, ignoreBounds=True)



    def showContextMenu(self):
        self.treeWidget.treeContextMenu.move(QCursor.pos())
        self.treeWidget.treeContextMenu.show()

    def dragEnterEvent(self, evt):
        evt.accept()

        # for i in range(self.dataPlotLayout.count()):
        #     plotAera = self.dataPlotLayout.itemAt(i).widget()
        #     print(plotAera.underMouse())
        #     if plotAera.underMouse():
        #         self.currSelctPlotWgt = plotAera
        #
        #         break

        # if self.currSelctPlotWgt.underMouse():

        # else:
        #     evt.ignore()

    def hoverEnterEvent(self,evet):
        pass

    def dropEvent(self, evt):
        #self.emit(mouseEnter event)
        #if self.currSelctPlotWgt.underMouse():
        for i in range(self.dataPlotLayout.count()):
            plotAera = self.dataPlotLayout.itemAt(i).widget()
            print(plotAera.plotItem.vb.name)
            print (plotAera.underMouse())
            if plotAera.underMouse():
                self.currSelctPlotWgt = plotAera
                self.plotData(plotAera, self.treeWidget.selectedItems())
                break

        self.plotData(self.currSelctPlotWgt, self.treeWidget.selectedItems())

    def showListContextMenu(self):
        self.listWidget.listContextMenu.move(QCursor.pos())
        self.listWidget.listContextMenu.show()


    def autoRangeAllWins(self):
        for i in range(self.dataPlotLayout.count()):
            plotItem = self.dataPlotLayout.itemAt(i).widget()

            plotItem.getViewBox().autoRange()

    def mouseClick(self, evnt):
        if self.currSelctPlotWgt:
            self.currSelctPlotWgt.setBackground('default')
            if evnt.currentItem is not None:
                try:
                    self.currSelctPlotWgt = evnt.currentItem._viewWidget()    # get the current selected widget
                    self.currSelctPlotWgt.setBackground(0.95)
                except Exception as e:
                    pass
                    #QMessageBox.critical(self, "Error", e.__str__())

    def clearPlotArea(self):
        #self.dataPlot.plotItem.clear()
        choice = QMessageBox.question(self, 'Plot1', "Remove all items in the first plot 1?",
                                            QMessageBox.Yes | QMessageBox.No)
        if choice == QMessageBox.Yes:

            for item in self.dataPlot.items():
                self.dataPlot.removeItem(item)

            lstitems = self.listWidget.findItems('Plot1', Qt.MatchStartsWith)
            if len(lstitems) > 0:
                for iitem in lstitems:
                    self.listWidget.takeItem(self.listWidget.row(iitem))

            for item in self.currSelctPlotWgt.scene().items():
                if isinstance(item, graphicsItems.LegendItem.LegendItem):  #  remove items in the scene including the legend
                    self.currSelctPlotWgt.scene().removeItem(item)

            #self.dataPlotRange.plotItem.clear()
            self.bPlotted = False
            self.configPlotArea(self.dataPlot)


    def addPlotAera(self):
        plotname = 'Plot' + str(len(self.lPlotWindows) + 1)
        axis = self.TimeAxisItem(orientation='bottom')
        vb = ViewBox()
        newdataPlot = PlotWidget(self, viewBox=vb, axisItems={'bottom': axis}, name = plotname)
        self.dataPlotLayout.addWidget(newdataPlot)
        self.configPlotArea(newdataPlot)

        newdataPlot.plotItem.scene().sigMouseClicked.connect(self.mouseClick)
        newdataPlot.plotItem.scene().sigMouseMoved.connect(self.mouseMove)

        ## drag and drop
        # newdataPlot.dragEnterEvent = self.dragEnterEvent
        # newdataPlot.plotItem.setAcceptDrops(True)
        # newdataPlot.plotItem.dropEvent = self.dropEvent

        # set the default plot range
        newdataPlot.setXRange(self.minTimestamp,self.maxTimestamp,padding=20)
        newdataPlot.setYRange(-10, 10, padding=20)

        newdataPlot.plotItem.getAxis('left').setWidth(w=30)
        newdataPlot.plotItem.hideButtons()

        newdataPlot.installEventFilter(self)

        newdataPlot.plotItem.showGrid(True, True, 0.5)

        vb.scaleBy(y=None)

        # make it the current selection plot area
        self.currSelctPlotWgt.setBackground('default')
        self.currSelctPlotWgt = newdataPlot  # set the current selection to plot1
        self.currSelctPlotWgt.setBackground(0.95)

        # link x axis to view box of the first data plot
        viewBox = self.dataPlot.plotItem.vb  # reference to viewbox of the plot 1
        axis.linkToView(viewBox)
        #axis.linkToView(vb)

        # Link plot 1 X axia to the view box
        lastplotItem = self.dataPlotLayout.itemAt(self.dataPlotLayout.count()-2).widget()
        lastplotItem.getViewBox().setXLink(newdataPlot)
        #lastplotItem.getViewBox().autoRange()

        txtY_value = TextItem("", fill=(0, 0, 255, 80), anchor=(0, 1), color='w')
        txtY_value.setParentItem(newdataPlot.plotItem.getViewBox())

        self.autoRangeAllWins()
        self.lPlotWindows.append(plotname)


    def removeDataPlotWin(self):
        curreSelctPlotWgtName = self.currSelctPlotWgt.getViewBox().name
        if curreSelctPlotWgtName != 'Plot1' and curreSelctPlotWgtName in self.lPlotWindows:  # can't delete plot1
            choice = QMessageBox.question(self, curreSelctPlotWgtName, "Remove the selected plot window?",
                                                QMessageBox.Yes | QMessageBox.No)
            if choice == QMessageBox.Yes:

                for item in self.currSelctPlotWgt.items():   # delete the items of the plot
                    self.currSelctPlotWgt.removeItem(item)

                lstitems = self.listWidget.findItems(curreSelctPlotWgtName, Qt.MatchStartsWith)  # delete the list in the list widget
                if len(lstitems) > 0:
                    for iitem in lstitems:
                        self.listWidget.takeItem(self.listWidget.row(iitem))

                for item in self.currSelctPlotWgt.scene().items():  #  remove everything in the scene including the legend
                    self.currSelctPlotWgt.scene().removeItem(item)



                self.dataPlotLayout.removeWidget(self.currSelctPlotWgt)
                self.currSelctPlotWgt.deleteLater()    #setHidden(True)     # hide the selected widget, should be deleted, to be updated with delect command
                self.currSelctPlotWgt = None
                self.lPlotWindows.remove(curreSelctPlotWgtName)    # remove the plot name from list of plot windows

                self.currSelctPlotWgt = self.dataPlot   # set the current selection to plot1
                self.currSelctPlotWgt.setBackground(0.95)

    def plotData(self, plotItem, selectedItems):
        '''selectedItems: items selected in tree view
           dfData: data frame of the selected data
        '''

        #plotItem = self.dataPlot.plotItem

        # viewbox = pg.ViewBox()
        # plotItem.scene().addItem(viewbox)

        #plotItem = self.currSelctPlotWgt
        plotItem.addLegend()

        #plotItem.getAxis('bottom').setPen(pg.mkPen(color='#000000', width=1))
        i = 0
        for iItem in selectedItems:
            if iItem.parent():     # not the root item
                filename = iItem.parent().text(1)    # get the parent item name - filename

                for iData in self.lTestDATA:          # find out the data from the data frame list by the filename
                    if filename == iData.fileName:
                        dfData = iData.data
                        break                       # break out of the loop for data


                data_head = iItem.text(1)           # get the column name of data for plotting
                curve_name = data_head + '>>' + iItem.text(2) + '>>' + iItem.text(3)    # parameter/parameter desc/unit

                # y axis
                data_2_plot = list(dfData[data_head])

                # get the list of time column, for x axis
                sTime = list(dfData['TIME'])

                # convert the time in string to date time object
                iTime = [self.sTimeToDateTime(j) for j in sTime]

                i += 1  # for color index use

                # example
                # pw.plot(x=[x.timestamp() for x in iTime ], y= list(df['BCVIIN']), pen = 'r')
                try:
                    plotcurve = PlotCurveItem(x=[x.timestamp() for x in iTime], y= data_2_plot, name = curve_name, pen=self.colorDex[i%5])
                    plotItem.addItem(plotcurve)
                except Exception as e:
                    QMessageBox.critical(self, "Error", "Error with data to plot.\n" + e.__str__())

                if not self.bPlotted:
                    self.bPlotted = True
                plotWgtName = self.currSelctPlotWgt.getViewBox().name
                if not plotWgtName: print("check the plotwidget definition in the mainUI.py, comment it!!!!")
                self.lPlottedItems.append({'Plot': plotWgtName, 'Curvename': curve_name, 'Filename': filename })
                self.listWidget.addItem(plotWgtName + '||' + curve_name + '||' + filename )

                # labl = QLabel(curve_name)
                # plotItem.addItem(labl)

                for lgditem in plotItem.scene().items():  # remove the legend
                    if isinstance(lgditem, graphicsItems.LegendItem.ItemSample):  #
                        lgditem.hide()   # hide the sample of legend  # plotItem.scene().items()[5].item is the curve itself
                        break

                self.autoRangeAllWins()


    def removeItemInPlot(self, selectedItem):
        try:
            if selectedItem[0]:
                [plotname,itemname,filename] = selectedItem[0].text().split('||')  #selectedItems()[0].text().split('||')

                for i in range(self.dataPlotLayout.count()):     # plot name = plot1 or plot2
                    plotWin = self.dataPlotLayout.itemAt(i).widget()
                    if plotname == plotWin.getViewBox().name:    # get the plot item
                        break

                for j in plotWin.plotItem.curves:    # get the curve item
                    curvename = j.name()
                    if curvename == itemname:
                        curveFound = True
                        break
                if curveFound:
                    plotWin.removeItem(j)               # delete the curve from the plot
                    #plotWin.scene().removeItem(plotWin.plotItem.legend)
                    for item in plotWin.scene().items():    # remove the legend
                        if isinstance(item, graphicsItems.LegendItem.LegendItem):      #isinstance(plotWin.scene().items()[6], pg.graphicsItems.LegendItem.LegendItem)
                            if item.items[0][1].text == curvename:                      # get the legend of the curve
                                plotWin.scene().removeItem(item)
                                break
                    self.listWidget.takeItem( self.listWidget.row(selectedItem[0]))    # remove the item from the list
                    for iPlottedItem in self.lPlottedItems:
                        if iPlottedItem['Filename'] == filename and iPlottedItem['Curvename'] == curvename:
                            self.lPlottedItems.remove(iPlottedItem)
                            break

                self.autoRangeAllWins()

        except Exception as e:
            print(e.__str__())



    def mouseMove(self, evt):
        #evtsender = self.sender()
        try:
            pos = evt  # get the point of mouse
            y_value = {}    # to keep the y values of all curves

        except Exception as e:
            print('exception @ mousemove 1' + e.__str__())

        if self.bPlotted:
            try:
                mousePoint = self.currSelctPlotWgt.plotItem.vb.mapSceneToView(pos)  # map the mouse position to the view position
                # mpOffset = plotWin.plotItem.vb.mapSceneToView(QPointF(0.0, 0.0))   # offset the mouse point
                x = self.minTimestamp
                timeIndex = datetime.fromtimestamp(x).strftime('%H:%M:%S:%f')[:12]
                if mousePoint.x() < self.minTimestamp - 3600 or mousePoint.x() > self.maxTimestamp + 2 * 3600:
                    #self.curLabelofYvalue.setPos(self.minTimestamp, mousePoint.y())
                    self.currSelctPlotWgt.plotItem.removeItem(self.curLabelofYvalue)  # remove the item
                    self.curLabelofYvalue.setParentItem(self.currSelctPlotWgt.getViewBox())
                    self.currSelctPlotWgt.plotItem.vb.autoRange()
                    return
                if mousePoint.y() < self.minYvalue or mousePoint.y() > self.maxYval:
                    #self.curLabelofYvalue.setPos(mousePoint.x(), self.minYvalue)
                    self.currSelctPlotWgt.plotItem.removeItem(self.curLabelofYvalue)  # remove the item
                    self.curLabelofYvalue.setParentItem(self.currSelctPlotWgt.getViewBox())
                    self.currSelctPlotWgt.plotItem.vb.autoRange()
                    #self.currSelctPlotWgt.scale(1,1,[{self.minTimestamp,self.minYvalue}])
                    return
            except Exception as e:
                pass

            try:
                #currentPlotArea = self.currSelctPlotWgt
                # move the vline in all plot area
                for i in range(self.dataPlotLayout.count()):  # loop for each plot area
                    plotWin = self.dataPlotLayout.itemAt(i).widget()
                    if plotWin.plotItem.sceneBoundingRect().contains(pos):  # mouse point in the plot aera
                        #print('Plot name: %s' % plotWin.getViewBox().name)
                        #print('view pos x: %0.1f + y: %0.1f' % (mousePoint.x(), mousePoint.y()))
                        # map the mouse position to the view position
                        mousePoint = plotWin.plotItem.vb.mapSceneToView(pos)
                        x = mousePoint.x()
                        # convert x coord from timestamp to time string
                        timeIndex = datetime.fromtimestamp(x).strftime('%H:%M:%S:%f')[:12]
                        #print('time: %s' % timeIndex)


                        for iLine in plotWin.items():  # loop for the vline
                            if hasattr(iLine, 'name'):
                                if iLine.name() == 'vline':
                                    iLine.setPos(mousePoint.x())
                                    break


                        #if plotWin.underMouse():  # check if the mouse is on the widget, True: current plot the mouse is in
                            #currentPlotArea = plotWin

                # move both hline in current plot area
                for iLine in self.currSelctPlotWgt.items():  # loop for the vline and hline
                    mousePoint = self.currSelctPlotWgt.plotItem.vb.mapSceneToView(pos)
                    if hasattr(iLine, 'name'):
                        if iLine.name() == 'hline':
                            iLine.setPos(mousePoint.y())
                            break

            except Exception as e:
                print('exception @ mousemove 2' + e.__str__())

            # get the y value of all plotted curves
            try:
                if self.lPlottedItems.__len__() > 0:
                    curr_Y = [round(mousePoint.y(),2)]
                    for iCurve in self.lPlottedItems:
                        plotname = iCurve['Plot']
                        filename = iCurve["Filename"]
                        curvename = iCurve["Curvename"].split('>>')[0]

                        for dataset in self.lTestDATA:
                            dfData = dataset.data
                            startTime = datetime.strptime('2018 ' + dfData['TIME'].iloc[0],
                                                          '%Y %H:%M:%S:%f').timestamp()
                            endTime = datetime.strptime('2018 ' + dfData['TIME'].iloc[-1],
                                                        '%Y %H:%M:%S:%f').timestamp()
                            rate = dataset.rate

                            if x > startTime and x < endTime:
                                row = round((x - startTime) * rate)  # the the row number
                                #print('row number: %d' % row)
                                y = dfData[curvename].iloc[row]  # dfData[curvename].iloc()[row]
                                #print('y: %f' % y)
                                y_value[curvename] = y  # keep the curve value in y to the list

                                if self.currSelctPlotWgt.getViewBox().name == plotname:  # the data set of current plot area
                                    curr_Y.append(round(y,2))

            except Exception as e:
                print('exception @ mousemove 3' + e.__str__())

            # display the y value of all curves
            try:
                self.labelTime.setText("<span style='font-size: 11pt'>Time=%s" % (timeIndex))

                if y_value:
                    # show the values of all curves shown in plots
                    self.labelValueY.setText("<span style='font-size: 11pt; color: red'>" + str(
                        ["%s=%0.1f" % (k, v) for k, v in y_value.items()]))
            except Exception as e:
                print('exception @ mousemove 4' + e.__str__())

            # show the label in current plot area

            try:
                if curr_Y.__len__() > 0:
                    # labelY_value = pg.TextItem("v")
                    # currentPlotArea.addItem(labelY_value)
                    # currentPlotArea.setPos(mousePoint.x(), mousePoint.y())
                    self.curLabelofYvalue.setText((''.join(str(e) + '\n' for e in curr_Y))[:-1])   # [:-1] to remove the last '\n'
                    self.curLabelofYvalue.setPos(mousePoint.x(), mousePoint.y())
                    #print(self.curLabelofYvalue.__str__)
                    #self.dataPlot.addItem(labelY_value)
            except Exception as e:
                print('exception @ mousemove 5' + e.__str__())



    def openFile(self):
        self.winImpData.exec_()  # Run the imp data window in modal
        self.treeUpdate()

    def exitAPP(self):
        choice = QMessageBox.question(self, 'Exit', "Close the application?",
                                           QMessageBox.Yes | QMessageBox.No)
        if choice == QMessageBox.Yes:
            sys.exit()
        else:
            pass

    def treeUpdate(self):
        QTreeWidget.clear(self.treeWidget)
        for tdataset in self.lTestDATA:
            fname = tdataset.fileName           #os.path.basename(self.winImpData.sDataFilePath)
            rate = tdataset.rate

            treeRoot = QTreeWidgetItem(self.treeWidget)
            treeRoot.setText(1, fname)
            treeRoot.setText(2, str(rate) + 'Hz')

            self.treeItem = tdataset.header  # list(self.winImpData.dfData)
            self.numTree = tdataset.column     #self.treeItem.__len__()

            for i in range(1, len(self.treeItem)):
                child = QTreeWidgetItem(treeRoot)
                child.setText(0, str(i))
                child.setText(1, self.treeItem[i])
                child.setText(2, self.dataparam.getParamInfo(self.treeItem[i],'paramDesc'))
                child.setText(3, self.dataparam.getParamInfo(self.treeItem[i],'unit'))

    def helpme(self):
        QMessageBox.information(self,'Wheel & Brake Test Data Explorer', 'Technical support:\nHON MS&C Shanghai.')

    ### for PyInataller use to bundle data file into one file
    def resource_path(self, relative_path):
        """ Get absolute path to resource, works for dev and for PyInstaller """
        if hasattr(sys, '_MEIPASS'):
            return path.join(sys._MEIPASS, relative_path)
        return path.join(path.abspath("."), relative_path)
        # base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
        # return os.path.join(base_path, relative_path)


    class TimeAxisItem(AxisItem): #### class TimeAxisItem is used for overloading x axis as time
        def tickStrings(self, values, scale, spacing):
            strns = []
            #rng = max(values) - min(values)    # values are timestamp of date
            #946656000 = datetime.strptime('2000', '%Y').timestamp() ,  handel dates after 2000 only
            # if min(values) < 946656000:  # Windows can't handle dates before 1970,
            #     # 1514764800.0 = datetime.datetime.strptime('2018-1-1 8:00:0', '%Y-%m-%d %H:%M:%S').timestamp()
            #     # 1514766600.0 = datetime.datetime.strptime('2018-1-1 8:30:0', '%Y-%m-%d %H:%M:%S').timestamp()
            #     #defaultValues = range(1514736000.0, 1514768400.0, 720)
            #
            #     return pg.AxisItem.tickStrings(self, values, scale, spacing)

            for x in values:
                try:
                    if x < 946656000: x += 946656000     ## handle time starting from 1/1/2000
                    strns.append(datetime.fromtimestamp(x).strftime('%H:%M:%S'))
                except ValueError:  ## Windows can't handle dates before 1970
                    strns.append('')

            return strns

            # show hour:minute:second on the x axis
            #return [datetime.fromtimestamp(value).strftime('%H:%M:%S') for value in values]
                # 946656000 = datetime.strptime('2000', '%Y').timestamp()


    def sTimeToDateTime(self, inTime):  # convert time from string to datetime object
        # inTime: '13:43:02:578' string type
        # outTime: 2018-01-01 13:43:02.578000  datetime object

        # '2018 ' + startTime, '%Y %H:%M:%S'
        #itime = inTime[:8] + "." + inTime[10:12]   # convert 13:43:02:578 to 13:43:02.578
        # add date (2018-01-01)to the TIME for the sake of format of datetime class. could use real date of the data created
        try:
            outTime = datetime.strptime('2018 ' + inTime, '%Y %H:%M:%S:%f')  # convert the time from string to the datetime format
        except Exception as e:
            QMessageBox.critical(self, "Error", "TIME format error.\n" + e.__str__())
            outTime = datetime.now()
        return outTime
示例#10
0
class Plot(QObject, Object):
    """Generic widget to plot data series. The data series have to be added
    first, then one may add plot items using two them as x and y values. The
    series will be automatically updated when the master attribute fires a
    value_changed signal or based on an internal update timer. This master
    series has to be defined while adding it.
    
    .. todo:: Currently only setting both series of a plot item to the same,
        fixed length works. Find a better handling for this.
    """
    TYPE_STATIC = 0
    TYPE_TIME = 1
    TYPE_SCALAR = 2
    TYPE_SPECTRUM = 3

    ROLE_SLAVE = 0
    ROLE_MASTER = 1

    def __init__(self, parent=None):
        """Construct a new Plot instance.
        
        :param parent: Optional, but needed for painting.
        :type parent: QGraphicsItem
        """
        QObject.__init__(self)
        Object.__init__(self)
        self.parent = parent
        self.lines = {}
        self.items = {}  #plot items
        self.data = {}  #data series to the plot items
        self.master = None  #id of the data series which triggers the update
        self.x_range = -1  #id of the data series which range defines the plot range
        self.mutex = QMutex()
        self.update_timer = QTimer(self)
        self.update_timer_start = None
        self.setup_ui()

    def setup_ui(self):
        """Setup the internal widget object. Called by :func:`__init__()`."""
        self.widget = PlotWidget(self.parent)
        self.widget.setBackgroundBrush(QBrush(Qt.NoBrush))
        count = len(self.janus.widgets["mainwindow"].findChildren(PlotWidget))
        self.widget.setObjectName("plotWidgetPlot{0}".format(count))
        self.widget.getAxis("bottom").setPen(QPen(Qt.black))
        self.widget.getAxis("left").setPen(QPen(Qt.black))

    def update_values(self, attribute=None):
        """Update all data of TYPE_TIME, TYPE_SCALAR, TYPE_SPECTRUM and plot it.

        This method is connected to either the the value_changed signal of the
        device instance that the master attribute belongs to or the internal
        update_timer if the master is of TYPE_TIME. 

        :param attribute: Name of the master attribute
            which triggered the update or None if master is of TYPE_TIME.
        :type attribute: str|None
        """
        self.mutex.lock()
        if not ((attribute is None and \
                self.data[self.master]["data_type"] == Plot.TYPE_TIME) or \
                (attribute == self.data[self.master]["name"] and \
                self.data[self.master]["data_type"] in \
                [Plot.TYPE_SCALAR, Plot.TYPE_SPECTRUM])):
            self.mutex.unlock()
            return
        if self.update_timer_start is not None:
            time_stamp = time.time() - self.update_timer_start
        update = len(self.items) * [False]
        #retrieve new data and cycle the buffer if max_length is reached
        for i in range(len(self.data)):
            if self.data[i]["data_type"] == Plot.TYPE_TIME:
                if self.data[i]["data"].shape[0] > self.data[i]["max_length"]:
                    self.data[i]["data"].resize(self.data[i]["max_length"])
                if self.data[i]["data"].shape[0] == self.data[i]["max_length"]:
                    self.data[i]["data"] = numpy.concatenate( \
                            (self.data[i]["data"][1:], self.data[i]["data"][:1]))
                    self.data[i]["data"][-1] = time_stamp
                else:
                    numpy.concatenate(self.data[i]["data"], time_stamp)
            elif self.data[i]["data_type"] == Plot.TYPE_SCALAR:
                if self.data[i]["data"].shape[0] > self.data[i]["max_length"]:
                    self.data[i]["data"].resize(self.data[i]["max_length"])
                if self.data[i]["data"].shape[0] == self.data[i]["max_length"]:
                    self.data[i]["data"] = numpy.concatenate( \
                            (self.data[i]["data"][1:], self.data[i]["data"][:1]))
                    self.data[i]["data"][-1] = self.data[i]["getter"](
                        refresh=True)
                else:
                    numpy.concatenate(self.data[i]["data"], \
                            self.data[i]["getter"](refresh=True))
            elif self.data[i]["data_type"] == Plot.TYPE_SPECTRUM:
                self.data[i]["data"] = numpy.ndarray(
                    self.data[i]["getter"](refresh=True))
                if self.data[i]["data"].shape[0] >= self.data[i]["max_length"] or \
                        self.data[i]["data"].shape[0] < self.data[i]["min_length"]:
                    self.data[i]["data"].resize(self.data[i]["max_length"])
            else:
                continue
            for item in self.data[i]["items"]:
                update[item] = True
        #set view boundaries if tie_x_range is set to a dataset
        if self.x_range > -1:
            if self.data[self.x_range]["data_type"] == Plot.TYPE_TIME:
                self.widget.setLimits( \
                        xMin=self.data[self.x_range]["data"][1], \
                        xMax=self.data[self.x_range]["data"][-1])
            else:
                self.widget.setLimits( \
                        xMin=numpy.amin(self.data[self.x_range]["data"]), \
                        xMax=numpy.amax(self.data[self.x_range]["data"]))
        #replot items
        for i in range(len(self.items)):
            if update[i]:
                self.items[i]["plot"].setData( \
                        self.data[self.items[i]["x"]]["data"], \
                        self.data[self.items[i]["y"]]["data"])
        self.mutex.unlock()

    def add_plot_data(self, i, data=None, attr=None, data_type=TYPE_STATIC, \
                role=ROLE_SLAVE, interval=1.0, \
                min_length=0, length=-1, max_length=-1):
        """Add a data series to the plot.
        
        :param i: Id of the data series. Should be ascending natural numbers 
            from 0 up.
        :type i: int
        :param data: Initial series of values. May be omited if not of
            TYPE_STATIC.
        :type data: numpy.ndarray|None
        :param attr: Reference to the getter function for an attribute of an
            device object. May be omited if not of TYPE_SCALAR or TYPE_SPECTRUM.
        :type attr: Device.method
        :param data_type: How the data is updated. May be one of the following 
            - TYPE_STATIC no update,
            - TYPE_TIME append time stamp at trigger point,
            - TYPE_SCALAR append attribute value at trigger point,
            - or TYPE_SPECTRUM exchange data series by attribute values at
                trigger point.
        :type data_type: int|TYPE_STATIC|TYPE_TIME|TYPE_SCALAR|TYPE_SPECTRUM
        :param role: Should be ROLE_MASTER for data series which triggers the
            plot update.
        :type role: int|ROLE_SLAVE|ROLE_MASTER
        :param interval: Interval, to which the update_timer is set if of
            TYPE_TIME and ROLE_MASTER. May be omited otherwise.
        :type interval: float
        :param min_length: Data series will be extended to this length. May be
            omited.
        :type min_length: int
        :param length: Sets min_length and max_length to this value. -1 will
            will disable this feature. May be omited.
        :type length: int
        :param max_length: Data series will be shortened to this length. -1 will
            will disable this feature. May be omited.
        :type max_length: int
        :return: False on errors, True otherwise.
        :rtype: bool
        """
        if length > -1:
            min_length = length
            max_length = length
        if data is not None and type(data) != numpy.ndarray:
            data = numpy.array(data)
        if data is not None and data.shape[0] < min_length:
            data.resize(min_length)
        if data is not None and max_length > 0 and data.shape[0] > max_length:
            data.resize(max_length)
        datum = { \
                "data": data, \
                "data_type": data_type, \
                "min_length": min_length, \
                "length": length, \
                "max_length": max_length, \
                "items" : []}
        if data_type in [Plot.TYPE_SCALAR, Plot.TYPE_SPECTRUM]:
            try:
                if callable(attr):
                    device = attr.__self__
                    name = attr.__name__
                else:
                    return False
            except:
                return False
            datum["device"] = device  #device instance
            datum["name"] = name  #attribute name
            datum["getter"] = attr  #attribute getter method
            if data is None and data_type == Plot.TYPE_SCALAR:
                start_value = attr(refresh=True)
                datum["data"] = numpy.full(min_length, start_value)
            elif data is None:
                datum["data"] = numpy.array(attr(refresh=True))
                if datum["data"].shape[0] < min_length:
                    datum["data"].resize(min_length)
                if max_length > 0 and datum["data"].shape[0] > max_length:
                    datum["data"].resize(max_length)
        elif data_type == Plot.TYPE_TIME:
            datum["interval"] = interval
            if data is None:
                datum["data"] = numpy.linspace( \
                        -(min_length-1)*interval, 0, min_length)
        elif data_type != Plot.TYPE_STATIC:
            return False
        self.mutex.lock()
        self.data[i] = datum
        self.mutex.unlock()
        if role == Plot.ROLE_MASTER:
            self.set_master(i)
        return True

    def remove_plot_data(self, i):
        if i == self.master:
            self.unset_master()
        items = self.data[i]["items"]
        for item in items:
            self.remove_plot_item(item)
        del self.data[i]

    def add_plot_item(self, i, x, y, colour=Qt.red):
        """Add a plot item representing two already added data series.
        
        :param i: Id of the plot item. Should be ascending natural numbers 
            from 0 up.
        :type i: int
        :param x: Id of the data series holding the x values.
        :type x: int
        :param y: Id of the data series holding the y values.
        :type y: int
        :param colour: Colour of the plot item. The default is red.
        :type colour: str|QRgb|QColor|Qt.GlobalColor
        """
        self.items[i] = {"x": x, "y": y}
        self.data[x]["items"].append(i)
        self.data[y]["items"].append(i)
        self.items[i]["plot"] = PlotDataItem( \
                self.data[x]["data"], \
                self.data[y]["data"], \
                pen=QColor(colour))
        self.widget.addItem(self.items[i]["plot"])

    def remove_plot_item(self, i):
        self.mutex.lock()
        self.data[self.items[i]["x"]]["items"].remove(i)
        self.data[self.items[i]["y"]]["items"].remove(i)
        self.widget.removeItem(self.items[i]["plot"])
        del self.items[i]
        self.mutex.unlock()

    def add_line_item(self, i, **kwargs):
        self.mutex.lock()
        self.lines[i] = InfiniteLine(**kwargs)
        self.widget.addItem(self.lines[i])
        self.mutex.unlock()

    def remove_line_item(self, i):
        self.mutex.lock()
        self.widget.removeItem(self.lines[i])
        del self.lines[i]
        self.mutex.unlock()

    def set_master(self, i):
        """Set which data series will trigger a plot update.
        
        :param data: Id of the data series which triggers the update.
        :type data: int
        """
        self.mutex.lock()
        if self.master is not None and self.master != i:
            self.unset_master()
            self.data[i]["device"].value_changed.disconnect(self.update_values)
        if self.data[i]["data_type"] in [Plot.TYPE_SCALAR, Plot.TYPE_SPECTRUM]:
            self.data[i]["device"].value_changed.connect(self.update_values)
        elif self.data[i]["data_type"] == Plot.TYPE_TIME:
            self.update_timer.timeout.connect(self.update_values)
            self.update_timer_start = time.time()
            self.update_timer.start(self.data[i]["interval"] * 1000.)
        self.master = i
        self.mutex.unlock()

    def unset_master(self):
        self.mutex.lock()
        if self.data[self.master]["data_type"] in \
                [Plot.TYPE_SCALAR, Plot.TYPE_SPECTRUM]:
            self.data[self.master]["device"].value_changed.disconnect( \
                    self.update_values)
        elif self.data[self.master]["data_type"] == Plot.TYPE_TIME:
            self.update_timer.timeout.disconnect(self.update_values)
            self.update_timer.stop()
        self.mutex.unlock()

    def tie_x_range(self, data=-1):
        """Sets the plot x range to be the same as the given data series.
        
        :param data: Id of the data series holding the x values.
        :type data: int
        """
        self.x_range = data

    def clear(self):
        data = list(self.data.keys())
        for datum in data:
            self.remove_plot_data(datum)
        for line in list(self.lines.keys()):
            self.remove_line_item(line)
示例#11
0
class PyQtGraphDataPlot(QWidget):

    limits_changed = Signal()

    def __init__(self, parent=None):
        super(PyQtGraphDataPlot, self).__init__(parent)
        self._plot_widget = PlotWidget()
        self._plot_widget.getPlotItem().addLegend()
        self._plot_widget.setBackground((255, 255, 255))
        self._plot_widget.setXRange(0, 10, padding=0)
        vbox = QVBoxLayout()
        vbox.addWidget(self._plot_widget)
        self.setLayout(vbox)
        self._plot_widget.getPlotItem().sigRangeChanged.connect(
            self.limits_changed)

        self.bins = 10
        self.window = 100
        self._curves = {}
        self._current_vline = None

    def add_curve(self,
                  curve_id,
                  curve_name,
                  curve_color=QColor(Qt.blue),
                  markers_on=False):
        pen = mkPen(curve_color, width=1)
        # this adds the item to the plot and legend
        plot = self._plot_widget.plot(stepMode=True,
                                      fillLevel=0,
                                      brush=(0, 0, 255, 150))
        self._curves[curve_id] = plot

    def remove_curve(self, curve_id):
        curve_id = str(curve_id)
        if curve_id in self._curves:
            self._plot_widget.removeItem(self._curves[curve_id])
            del self._curves[curve_id]
            self._update_legend()

    def _update_legend(self):
        # clear and rebuild legend (there is no remove item method for the legend...)
        self._plot_widget.clear()
        self._plot_widget.getPlotItem().legend.items = []
        for curve in self._curves.values():
            self._plot_widget.addItem(curve)
        if self._current_vline:
            self._plot_widget.addItem(self._current_vline)

    def redraw(self):
        pass

    def set_values(self, curve_id, data_x, data_y):
        curve = self._curves[curve_id]
        if len(data_y) > 0:
            y, x = numpy.histogram(data_y[-self.window:], self.bins)
            curve.setData(x, y)
        else:
            curve.clear()
        self._plot_widget.autoRange()

    def vline(self, x, color):
        if self._current_vline:
            self._plot_widget.removeItem(self._current_vline)
        self._current_vline = self._plot_widget.addLine(x=x, pen=color)

    def set_xlim(self, limits):
        # TODO: this doesn't seem to handle fast updates well
        self._plot_widget.setXRange(limits[0], limits[1], padding=0)

    def set_ylim(self, limits):
        self._plot_widget.setYRange(limits[0], limits[1], padding=0)

    def get_xlim(self):
        x_range, _ = self._plot_widget.viewRange()
        return x_range

    def get_ylim(self):
        _, y_range = self._plot_widget.viewRange()
        return y_range
示例#12
0
class Plotter(QWidget):
    MAX_DATA_POINTS_PER_CURVE = 200000

    COLORS = [Qt.red, Qt.green, Qt.blue,                        # RGB - http://ux.stackexchange.com/questions/79561
              Qt.yellow, Qt.cyan, Qt.magenta,                   # Close to RGB
              Qt.darkRed, Qt.darkGreen, Qt.darkBlue,            # Darker RGB
              Qt.darkYellow, Qt.darkCyan, Qt.darkMagenta,       # Close to RGB
              Qt.gray, Qt.darkGray]                             # Leftovers

    INITIAL_X_RANGE = 60

    def __init__(self, parent=None):
        # Parent
        super(Plotter, self).__init__(parent)
        self.setWindowTitle('UAVCAN Plotter')
        self.setWindowIcon(APP_ICON)

        # Redraw timer
        self._update_timer = QTimer()
        self._update_timer.timeout.connect(self._update)
        self._update_timer.setSingleShot(False)
        self._update_timer.start(30)

        # PyQtGraph
        self._plot_widget = PlotWidget()
        self._plot_widget.setBackground((0, 0, 0))
        self._legend = self._plot_widget.addLegend()
        self._plot_widget.setRange(xRange=(0, self.INITIAL_X_RANGE), padding=0)
        self._plot_widget.showButtons()
        self._plot_widget.enableAutoRange()
        self._plot_widget.showGrid(x=True, y=True, alpha=0.4)

        # Controls
        # https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
        button_add_matcher = QtGui.QPushButton('New matcher', self)
        button_add_matcher.setIcon(QtGui.QIcon.fromTheme('list-add'))
        button_add_matcher.setToolTip('Add new curve matcher')
        button_add_matcher.clicked.connect(
            lambda: NewCurveMatcherWindow(self, lambda: sorted(self._active_messages), self._add_curve_matcher).show())

        button_clear_plots = QtGui.QPushButton('Clear plots', self)
        button_clear_plots.setIcon(QtGui.QIcon.fromTheme('edit-clear'))
        button_clear_plots.setToolTip('Clear the plotting area')
        button_clear_plots.clicked.connect(lambda: self._remove_all_curves())

        def delete_all_matchers():
            self._curve_matchers = []
            for i in reversed(range(self._curve_matcher_container.count())):
                self._curve_matcher_container.itemAt(i).widget().deleteLater()
            self._remove_all_curves()

        button_delete_all_matchers = QtGui.QPushButton('Delete matchers', self)
        button_delete_all_matchers.setIcon(QtGui.QIcon.fromTheme('edit-delete'))
        button_delete_all_matchers.setToolTip('Delete all matchers')
        button_delete_all_matchers.clicked.connect(delete_all_matchers)

        self._autoscroll = QtGui.QCheckBox('Autoscroll', self)
        self._autoscroll.setChecked(True)
        self._max_x = self.INITIAL_X_RANGE

        # Layout
        control_panel = QHBoxLayout()
        control_panel.addWidget(button_add_matcher)
        control_panel.addWidget(button_clear_plots)
        control_panel.addWidget(self._autoscroll)
        control_panel.addStretch()
        control_panel.addWidget(button_delete_all_matchers)

        self._curve_matcher_container = QVBoxLayout()

        layout = QVBoxLayout()
        layout.addWidget(self._plot_widget, 1)
        layout.addLayout(control_panel)
        layout.addLayout(self._curve_matcher_container)
        self.setLayout(layout)

        # Logic
        self._color_index = 0
        self._curves = {}
        self._message_queue = multiprocessing.Queue()
        self._active_messages = set() # set(data type name)
        self._curve_matchers = []

        # Defaults
        self._add_curve_matcher(CurveMatcher('uavcan.protocol.debug.KeyValue', 'value', [('key', None)]))

    def _add_curve_matcher(self, matcher):
        self._curve_matchers.append(matcher)
        view = CurveMatcherView(matcher, self)

        def remove():
            self._curve_matchers.remove(matcher)
            self._curve_matcher_container.removeWidget(view)
            view.setParent(None)
            view.deleteLater()

        view.on_remove = remove
        self._curve_matcher_container.addWidget(view)

    def _update(self):
        # Processing messages
        while True:
            try:
                m = self._message_queue.get_nowait()
                self._process_message(m)
            except queue.Empty:
                break
        # Updating curves
        for curve in self._curves.values():
            if len(curve['x']):
                if len(curve['x']) > self.MAX_DATA_POINTS_PER_CURVE:
                    curve['x'] = curve['x'][-self.MAX_DATA_POINTS_PER_CURVE:]
                    curve['y'] = curve['y'][-self.MAX_DATA_POINTS_PER_CURVE:]
                assert len(curve['x']) == len(curve['y'])
                curve['plot'].setData(curve['x'], curve['y'])
                self._max_x = max(self._max_x, curve['x'][-1])
        # Updating view range
        if self._autoscroll.checkState():
            (xmin, xmax), _ = self._plot_widget.viewRange()
            diff = xmax - xmin
            xmax = self._max_x
            xmin = self._max_x - diff
            self._plot_widget.setRange(xRange=(xmin, xmax), padding=0)


    def _process_message(self, m):
        self._active_messages.add(m.data_type_name)
        for matcher in self._curve_matchers:
            if matcher.match(m):
                name, x, y = matcher.extract_curve_name_x_y(m)
                self._draw_curve(name, x, y)

    def _remove_all_curves(self):
        for curve in self._curves.values():
            self._plot_widget.removeItem(curve['plot'])
        self._plot_widget.clear()
        self._curves = {}
        self._color_index = 0
        self._legend.scene().removeItem(self._legend)
        self._legend = self._plot_widget.addLegend()

    def _draw_curve(self, name, x, y):
        if name not in self._curves:
            logging.info('Adding curve %r', name)
            color = self.COLORS[self._color_index % len(self.COLORS)]
            self._color_index += 1
            pen = mkPen(QColor(color), width=1)
            plot = self._plot_widget.plot(name=name, pen=pen)
            self._curves[name] = {'x': numpy.array([]), 'y': numpy.array([]), 'plot': plot}

        curve = self._curves[name]
        curve['x'] = numpy.append(curve['x'], [x] if isinstance(x, (float, int)) else x)
        curve['y'] = numpy.append(curve['y'], [y] if isinstance(y, (float, int)) else y)
        assert len(curve['x']) == len(curve['y'])

    def push_received_message(self, msg):
        self._message_queue.put_nowait(msg)
示例#13
0
class RealtimePlotWidget(QWidget):
    AUTO_RANGE_FRACTION = 0.99

    COLORS = [
        Qt.red, Qt.blue, Qt.green, Qt.magenta, Qt.cyan, Qt.darkRed,
        Qt.darkBlue, Qt.darkGreen, Qt.darkYellow, Qt.gray
    ]

    def __init__(self, display_measurements, parent):
        super(RealtimePlotWidget, self).__init__(parent)
        self.setAttribute(
            Qt.WA_DeleteOnClose)  # This is required to stop background timers!
        self._plot_widget = PlotWidget()
        self._plot_widget.setBackground((0, 0, 0))
        self._legend = self._plot_widget.addLegend()
        self._plot_widget.showButtons()
        self._plot_widget.showGrid(x=True, y=True, alpha=0.3)
        vbox = QVBoxLayout(self)
        vbox.addWidget(self._plot_widget)
        self.setLayout(vbox)

        self._last_update_ts = 0
        self._reset_required = False

        self._update_timer = QTimer(self)
        self._update_timer.setSingleShot(False)
        self._update_timer.timeout.connect(self._update)
        self._update_timer.start(200)

        self._color_index = 0
        self._curves = {}

        # Crosshair
        def _render_measurements(cur, ref):
            text = 'time %.6f sec,  y %.6f' % cur
            if ref is None:
                return text
            dt = cur[0] - ref[0]
            dy = cur[1] - ref[1]
            if abs(dt) > 1e-12:
                freq = '%.6f' % abs(1 / dt)
            else:
                freq = 'inf'
            display_measurements(text + ';' + ' ' * 4 +
                                 'dt %.6f sec,  freq %s Hz,  dy %.6f' %
                                 (dt, freq, dy))

        display_measurements(
            'Hover to sample Time/Y, click to set new reference')
        add_crosshair(self._plot_widget, _render_measurements)

        # Final reset
        self.reset()

    def _trigger_auto_reset_if_needed(self):
        ts = time.monotonic()
        dt = ts - self._last_update_ts
        self._last_update_ts = ts
        if dt > 2:
            self._reset_required = True

    def add_curve(self, curve_id, curve_name, data_x=[], data_y=[]):
        color = QColor(self.COLORS[self._color_index % len(self.COLORS)])
        self._color_index += 1
        pen = mkPen(color, width=1)
        plot = self._plot_widget.plot(name=curve_name, pen=pen)
        data_x = numpy.array(data_x)
        data_y = numpy.array(data_y)
        self._curves[curve_id] = {'data': (data_x, data_y), 'plot': plot}
        self._trigger_auto_reset_if_needed()

    def update_values(self, curve_id, x, y):
        curve = self._curves[curve_id]
        old_x, old_y = curve['data']
        curve['data'] = numpy.append(old_x, x), numpy.append(old_y, y)
        self._trigger_auto_reset_if_needed()

    def reset(self):
        for curve in self._curves.keys():
            self._plot_widget.removeItem(self._curves[curve]['plot'])

        self._curves = {}
        self._color_index = 0

        self._plot_widget.enableAutoRange(enable=self.AUTO_RANGE_FRACTION,
                                          x=self.AUTO_RANGE_FRACTION,
                                          y=self.AUTO_RANGE_FRACTION)

        self._legend.scene().removeItem(self._legend)
        self._legend = self._plot_widget.addLegend()

    def _update(self):
        if self._reset_required:
            self.reset()
            self._reset_required = False

        for curve in self._curves.values():
            if len(curve['data'][0]):
                curve['plot'].setData(*curve['data'])
示例#14
0
class Design(QWidget):
    colors = {"cathode": "#FF0000", "anode": "#0000FF", "full": "#000000"}
    symbols = {"cathode": "o", "anode": "s", "full": "t"}

    def __init__(self, parent):
        super(Design, self).__init__()
        self.parent = parent
        self.core = parent.core
        self.parameters = None
        self.datas = None
        self.renderWindow()
        self.initPlotView()
        self.setUpProcessUI()
        self.bindEvents()

    def bindEvents(self):
        self.generate.clicked.connect(self.genCurve)
        self.save.clicked.connect(self.saveCurve)
        self.Sheme.currentIndexChanged.connect(self.selectScheme)
        pass

    def renderWindow(self):
        #边框结构
        self.setGeometry(80, 80, 800, 420)
        size = self.geometry()
        screen = QDesktopWidget().screenGeometry()
        posX = (screen.width() - size.width()) / 2
        posY = (screen.height() - size.height()) / 2
        self.move(posX, posY)
        #标题
        self.setWindowTitle('Designer')
        self.setWindowIcon(QIcon('resource/curve.ico'))
        #布局
        layout = QGridLayout()
        self.graphicsView = QGridLayout()
        layout.addLayout(self.graphicsView, 0, 0, 1, 1)

        self.Process_Box = QGroupBox()
        self.Process_Box.setMinimumSize(240, 440)
        self.Process_Box.setFlat(True)
        layout.addWidget(self.Process_Box, 0, 1, 1, 1)

        self.setLayout(layout)

    def setUpProcessUI(self):
        layout = QGridLayout()
        layout.setContentsMargins(10, 10, 10, 10)
        layout.setSpacing(10)
        self.Process_Box.setLayout(layout)

        layout.addWidget(QLabel(self.translate('Standard Cathode')), 0, 0, 1,
                         3)
        layout.addWidget(QLabel(self.translate('Standard Anode')), 1, 0, 1, 3)
        layout.addWidget(QLabel(
            self.translate('Capacity Ratio of 0.01C/0.2C')), 2, 0, 1, 3)  #()
        layout.addWidget(QLabel(self.translate('Capacity of 0.2C')), 3, 0, 1,
                         3)
        layout.addWidget(QLabel(self.translate('1st Cycle Efficiency')), 4, 0,
                         1, 3)
        layout.addWidget(QLabel(self.translate('Cathode Area')), 5, 0, 1, 3)
        layout.addWidget(QLabel(self.translate('Cathode Coating Weight')), 6,
                         0, 1, 3)
        layout.addWidget(QLabel(self.translate('Cathode Loading')), 7, 0, 1, 3)
        layout.addWidget(QLabel(self.translate('Anode Coating Weight')), 8, 0,
                         1, 3)
        layout.addWidget(QLabel(self.translate("Anode Loading")), 9, 0, 1, 3)
        layout.addWidget(QLabel(self.translate("Sheme")), 10, 0, 1, 3)
        layout.addWidget(QLabel(self.translate("Discharge Start Voltage")), 11,
                         0, 1, 3)
        layout.addWidget(QLabel(self.translate("Discharge End Voltage")), 12,
                         0, 1, 3)

        self.Cathodes = QComboBox()
        self.Anodes = QComboBox()
        keys = list(self.core.datas.keys())
        keys.sort(key=lambda x: self.core.order.index(x))
        for k in keys:
            for tag in self.core.pos_tag:
                if isStartWith(k, tag):
                    self.Cathodes.addItem(k)
                    break
            for tag in self.core.neg_tag:
                if isStartWith(k, tag):
                    self.Anodes.addItem(k)
                    break
        self.Sheme = QComboBox()
        self.Sheme.addItems([
            self.translate("Discharge Start Voltage"),
            self.translate("Discharge End Voltage")
        ])
        self.Ratio = SpinBox(lower=0, upper=10, dec=4)
        self.Capacity = SpinBox(lower=0, upper=10000, val=0, dec=4)
        self.Efficiency = SpinBox(lower=0, upper=1, val=0.98, dec=4)
        self.CathodeArea = SpinBox(lower=0, upper=1E9, dec=4)
        self.CathodeCW = SpinBox(lower=0, upper=1E9, dec=4)
        self.CathodeLoading = SpinBox(lower=0, upper=1, dec=4)
        self.AnodeCW = SpinBox(lower=0, upper=1E9, dec=4)
        self.AnodeLoading = SpinBox(lower=0, upper=1, dec=4)
        self.StartVoltage = SpinBox(lower=0, upper=5, val=4.3, dec=4)
        self.EndVoltage = SpinBox(lower=0, upper=5, val=2.7, dec=4)

        layout.addWidget(self.Cathodes, 0, 3, 1, 3)
        layout.addWidget(self.Anodes, 1, 3, 1, 3)
        layout.addWidget(self.Ratio, 2, 3, 1, 3)
        layout.addWidget(self.Capacity, 3, 3, 1, 3)
        layout.addWidget(self.Efficiency, 4, 3, 1, 3)
        layout.addWidget(self.CathodeArea, 5, 3, 1, 3)
        layout.addWidget(self.CathodeCW, 6, 3, 1, 3)
        layout.addWidget(self.CathodeLoading, 7, 3, 1, 3)
        layout.addWidget(self.AnodeCW, 8, 3, 1, 3)
        layout.addWidget(self.AnodeLoading, 9, 3, 1, 3)
        layout.addWidget(self.Sheme, 10, 3, 1, 3)
        layout.addWidget(self.StartVoltage, 11, 3, 1, 3)
        layout.addWidget(self.EndVoltage, 12, 3, 1, 3)

        self.load = QPushButton(self.translate("Load"))
        self.generate = QPushButton(self.translate("Generate"))
        self.save = QPushButton(self.translate("Save"))
        self.load.setDisabled(True)
        scheme = int(self.defaultSetting("Design/Sheme", 0))
        self.Sheme.setCurrentIndex(scheme)
        self.selectScheme()
        layout.addWidget(self.load, 13, 0, 1, 2)
        layout.addWidget(self.generate, 13, 2, 1, 2)
        layout.addWidget(self.save, 13, 4, 1, 2)
        pass

    def initPlotView(self):
        self.plot = PlotWidget(enableAutoRange=True)
        self.plotLegand = self.plot.addLegend()
        self.graphicsView.addWidget(self.plot)
        self.setGraphViewStyle()

    def setGraphViewStyle(self):
        bgColor = self.defaultSetting('Graph/BackgroundColor', '#ffffff')
        gridAlpha = float(self.defaultSetting('Graph/GridAlpha', 0.25))
        axisColor = self.defaultSetting('Graph/AxisColor', '#000000')
        axisWidth = float(self.defaultSetting('Graph/AxisWidth', 1.5))
        self.plot.setAutoVisible(y=True)
        self.plot.setBackground(bgColor)
        self.plot.showGrid(x=True, y=True, alpha=gridAlpha)
        self.plot.getAxis('bottom').setPen(color=axisColor, width=axisWidth)
        self.plot.getAxis('left').setPen(color=axisColor, width=axisWidth)

    def drawCurve(self, x, y, text):
        self.plot.removeItem(text)
        curveType = self.defaultSetting('Curve/type', 'line')
        width = int(self.defaultSetting('Curve/width', 3))
        size = int(self.defaultSetting('Curve/size', 5))
        color = self.colors[text]
        symbol = self.symbols[text]
        pen = mkPen(color=color, width=width)
        text = self.translate(text)
        self.plotLegand.removeItem(text)
        if curveType == 'scatter':
            self.plot.plot(x,
                           y,
                           pen=pen,
                           symbolBrush=color,
                           symbolPen=color,
                           symbol=symbol,
                           symbolSize=size,
                           name=text)
        else:
            self.plot.plot(x, y, pen=pen, name=text)
        self.plot.show()

    def genCurve(self):
        posName = self.Cathodes.currentText()
        negName = self.Anodes.currentText()
        capacity = self.Ratio.value() * self.Capacity.value()
        posMass = self.CathodeArea.value() * self.CathodeCW.value(
        ) * self.CathodeLoading.value() / 1540.25
        negMass = self.CathodeArea.value() * self.AnodeCW.value(
        ) * self.AnodeLoading.value() / 1540.25
        efficiency = self.Efficiency.value()
        if capacity * posMass * negMass * efficiency == 0 or posName == "" or negName == "":
            self.critical("Invalid Parameters!")
            return
        posLoss = capacity * (1 / efficiency - 1)
        #print(posMass,posLoss)
        posData = self.core.get_data(posName).modify_x(posMass, 0)
        posLoss = posData.x_max - capacity - posLoss
        if posLoss < 0:
            return self.cathodeLess()
        posData = self.core.get_data(posName).modify_x(posMass, posLoss)
        #print(posMass,posLoss)
        if self.Sheme.currentIndex() == 1:
            terminal = posData.posValue(capacity)
            #print(terminal)
            if terminal == None:
                return self.cathodeLess()
            delta = terminal - self.EndVoltage.value()
            negData = self.core.get_data(negName).modify_x(negMass, 0)
            terminal = negData.invert().posValue(delta)
            if terminal == None or terminal < capacity:
                return self.anodeLess()
            negLoss = terminal - capacity
            negData = self.core.get_data(negName).modify_x(negMass, negLoss)
        else:
            terminal = posData.posValue(0)
            if terminal == None:
                return self.cathodeLess()
            delta = terminal - self.StartVoltage.value()
            negData = self.core.get_data(negName).modify_x(negMass, 0)
            #print(delta)
            terminal = negData.invert().posValue(delta)
            if terminal == None:
                return self.anodeLess()
            negLoss = terminal
            negData = self.core.get_data(negName).modify_x(negMass, negLoss)
            if negData.x_max < capacity:
                return self.anodeLess()
        fulDataX = np.linspace(0, capacity, 2000)
        fulDataY = posData.interpolate(fulDataX).y_data - negData.interpolate(
            fulDataX).y_data
        self.parameters = [posMass, posLoss, negMass, negLoss]
        self.datas = [posData, negData, posData.copy(fulDataX, fulDataY)]
        for x in self.plot.items():
            if isinstance(x, (ScatterPlotItem, PlotCurveItem, PlotDataItem)):
                self.plot.removeItem(x)
        self.drawCurve(*posData(), "cathode")
        self.drawCurve(*negData(), "anode")
        self.drawCurve(fulDataX, fulDataY, "full")

    def saveCurve(self):
        # (value, ok) = QInputDialog.getText(self, self.translate("Save Data"), self.translate("Please input data name"), QLineEdit.Normal, "")
        # if not ok:
        #     return
        # if self.parameters == None or self.datas == None:
        #     return
        # if value == "":
        #     self.critical("Data name can not be empty string!")
        #     return
        # elif re.match(r'(\:|\\|\/|\*|\?|\"|<|>|\|)',value):
        #     self.critical("There are invalid characters in the data name!")
        #     return
        # if (value + "_full") in self.core.datas or (value + "_anode") in self.core.datas or (value + "_cathode") in self.core.datas:
        #     ok = self.warnning("Data with the same name already exists!\nDo you want to override old datas?")
        #     if not ok:
        #         return
        # self.core.add_data((value + "_cathode"),self.datas[0],override=True)
        # self.core.add_data((value + "_anode"),self.datas[1],override=True)
        # self.core.add_data((value + "_full"),self.datas[2],override=True)
        self.core.max_capacity = self.Ratio.value() * self.Capacity.value()
        self.parent.setSetting('Core/MaxCapacity', self.core.max_capacity)
        self.core.auto_cal_param(self.parameters[0], 0)
        self.core.auto_cal_param(self.parameters[1], 1)
        self.core.auto_cal_param(self.parameters[2], 2)
        self.core.auto_cal_param(self.parameters[3], 3)
        self.core.for_fitting = [
            self.Cathodes.currentText(),
            self.Anodes.currentText(), ""
        ]
        self.core.triggle('change')
        self.core.triggle('fitting')

    def anodeLess(self):
        self.critical(
            "The theoretical maximum anode capacity is less than the design capacity!"
        )

    def cathodeLess(self):
        self.critical(
            "The theoretical maximum cathode capacity is less than the design capacity!"
        )

    def selectScheme(self, idx=0):
        if self.Sheme.currentIndex() == 0:
            self.EndVoltage.setDisabled(True)
            self.StartVoltage.setEnabled(True)
        else:
            self.StartVoltage.setDisabled(True)
            self.EndVoltage.setEnabled(True)
        self.setSetting("Design/Sheme", self.Sheme.currentIndex())

    def translate(self, text):
        if self.parent:
            self.langText = self.parent.langText
        else:
            self.langText = load(open('SCN.translation', encoding='utf-8'))
        if text in self.langText:
            return self.langText[text]
        return text

    def defaultSetting(self, key, value):
        if self.parent:
            return self.parent.defaultSetting(key, value)
        return value

    def setSetting(self, key, value):
        if self.parent:
            return self.parent.setSetting(key, value)

    def critical(self, text):
        QMessageBox.critical(self, self.translate("Critical"),
                             self.translate(text), QMessageBox.Yes)

    def warnning(self, text):
        return QMessageBox.warning(self, self.translate("Warning"),
                                   self.translate(text),
                                   QMessageBox.No | QMessageBox.Yes)
class PyQtGraphDataPlot(QWidget):

    limits_changed = Signal()

    def __init__(self, parent=None):
        super(PyQtGraphDataPlot, self).__init__(parent)
        self._plot_widget = PlotWidget()
        self._plot_widget.getPlotItem().addLegend()
        self._plot_widget.setBackground((255, 255, 255))
        self._plot_widget.setXRange(0, 10, padding=0)
        vbox = QVBoxLayout()
        vbox.addWidget(self._plot_widget)
        self.setLayout(vbox)
        self._plot_widget.getPlotItem().sigRangeChanged.connect(self.limits_changed)

        self._curves = {}
        self._current_vline = None

    def add_curve(self, curve_id, curve_name, curve_color=QColor(Qt.blue), markers_on=False):
        pen = mkPen(curve_color, width=1)
        symbol = "o"
        symbolPen = mkPen(QColor(Qt.black))
        symbolBrush = mkBrush(curve_color)
        # this adds the item to the plot and legend
        if markers_on:
            plot = self._plot_widget.plot(name=curve_name, pen=pen, symbol=symbol, symbolPen=symbolPen, symbolBrush=symbolBrush, symbolSize=4)
        else:
            plot = self._plot_widget.plot(name=curve_name, pen=pen)
        self._curves[curve_id] = plot

    def remove_curve(self, curve_id):
        curve_id = str(curve_id)
        if curve_id in self._curves:
            self._plot_widget.removeItem(self._curves[curve_id])
            del self._curves[curve_id]
            self._update_legend()
           
    def _update_legend(self):
        # clear and rebuild legend (there is no remove item method for the legend...)
        self._plot_widget.clear()
        self._plot_widget.getPlotItem().legend.items = []
        for curve in self._curves.values():
            self._plot_widget.addItem(curve)
        if self._current_vline:
            self._plot_widget.addItem(self._current_vline)
 
    def redraw(self):
        pass

    def set_values(self, curve_id, data_x, data_y):
        curve = self._curves[curve_id]
        curve.setData(data_x, data_y)

    def vline(self, x, color):
        if self._current_vline:
            self._plot_widget.removeItem(self._current_vline)
        self._current_vline = self._plot_widget.addLine(x=x, pen=color)

    def set_xlim(self, limits):
        # TODO: this doesn't seem to handle fast updates well
        self._plot_widget.setXRange(limits[0], limits[1], padding=0)

    def set_ylim(self, limits):
        self._plot_widget.setYRange(limits[0], limits[1], padding=0)

    def get_xlim(self):
        x_range, _ = self._plot_widget.viewRange()
        return x_range

    def get_ylim(self):
        _, y_range = self._plot_widget.viewRange()
        return y_range
class MainWindow(QWidget):
    def __init__(self, appinst, profilecon, settingscon, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.app = appinst
        self.profilecon = profilecon  # connector class instance for reading/writing profile settings
        self.settingscon = settingscon
        self.gcode = None
        self.machine = None

        self.backgroundTask = None
        self.postBackgroundTask = None

        self.coord_plot_items = list(
        )  # list of all plot items added to the coord plot

        self.mainlayout = QVBoxLayout(self)
        self.mainlayout.setContentsMargins(0, 0, 0, 0)

        self.toolBar = QToolBar()
        self.toolBar.setStyleSheet("""QToolBar {background-color: white;
                                                border-top: 1px solid black}"""
                                   )
        self.mainlayout.addWidget(self.toolBar)

        self.add_toolbar_action("./res/folder.svg", "Open",
                                self.open_file_dialog)
        self.add_toolbar_action("./res/x-square.svg", "Close", self.close_file)
        self.add_toolbar_action("./res/save.svg", "Export", self.export)
        self.toolBar.addSeparator()
        self.add_toolbar_action("./res/sliders.svg", "Settings",
                                self.open_settings_dialog)
        self.add_toolbar_action("./res/play.svg", "Simulate",
                                self.start_simulation)
        self.toolBar.addSeparator()
        self.add_toolbar_action("./res/maximize.svg", "Fit to View",
                                self.fit_plot_to_window)
        self.add_toolbar_action("./res/maximize-2.svg", "Reset View",
                                self.reset_plot_view)
        self.toolBar.addSeparator()
        self.profileSelector = QComboBox()
        for name in self.profilecon.list_profiles():
            self.profileSelector.addItem(name)
        self.profileSelector.setCurrentText(
            self.settingscon.get_value("Current_Profile"))
        self.profilecon.select_profile(
            self.settingscon.get_value("Current_Profile"))
        self.toolBar.addWidget(self.profileSelector)
        self.profileSelector.currentTextChanged.connect(
            self.selected_profile_changed)
        divider = QWidget()
        divider.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.toolBar.addWidget(divider)
        self.add_toolbar_action("./res/info.svg", "About",
                                self.open_about_dialog)

        self.contentLayout = QHBoxLayout()
        self.contentLayout.setContentsMargins(10, 10, 10, 10)
        self.mainlayout.addLayout(self.contentLayout)

        self.layerSlider = QSlider()
        self.layerSlider.setMinimum(0)
        self.layerSlider.setValue(0)
        self.layerSlider.setDisabled(True)
        self.layerSlider.valueChanged.connect(self.show_layer)
        self.contentLayout.addWidget(self.layerSlider)

        self.coordPlot = PlotWidget()
        self.coordPlot.setAspectLocked(True)
        # self.coordPlot.setLimits(xMin=0, yMin=0)
        self.configure_plot(
        )  # is done in a seperate funciton because values need to be updated after settings are changed
        self.contentLayout.addWidget(self.coordPlot)

        self.sidebarlayout = QVBoxLayout()
        self.contentLayout.addLayout(self.sidebarlayout)

        self.sidebarheader = QLabel("Options")
        self.sidebarheader.setFixedSize(300, 50)
        self.sidebarlayout.addWidget(self.sidebarheader)

    def configure_plot(self):
        self.coordPlot.invertX(
            self.profilecon.get_value("invert_x")
        )  # needs to be done before setting the axis ranges because
        self.coordPlot.invertY(
            self.profilecon.get_value("invert_y")
        )  # inverting does not update the viewbox, but setting the range does
        self.coordPlot.setXRange(self.profilecon.get_value("bed_min_x"),
                                 self.profilecon.get_value("bed_max_x"))
        self.coordPlot.setYRange(self.profilecon.get_value("bed_min_y"),
                                 self.profilecon.get_value("bed_max_y"))

    def selected_profile_changed(self, new_profile):
        # select the new profile in the settings connector and update the ui accordingly
        self.profilecon.select_profile(new_profile)
        self.settingscon.set_value("Current_Profile",
                                   new_profile)  # remember selected profile
        self.settingscon.save_to_file()
        self.configure_plot()

    def add_toolbar_action(self, icon, text, function):
        # wrapper function for adding a toolbar button and connecting it to trigger a function
        open_icon = QIcon(icon)
        action = self.toolBar.addAction(open_icon, text)
        action.triggered.connect(function)

    def finish_background_task(self):
        # function is called when a background task finishes
        if self.postBackgroundTask:
            # run cleanup task (i.e. ui update); runs on main ui thread!
            self.postBackgroundTask()
        # reset variables
        self.postBackgroundTask = None
        self.backgroundTask = None

    def run_in_background(self, task, after=None, args=None):
        # wrapper function for creating and starting a thread to run a function in the background
        # arguments can be passed to the function in the thread and a cleanup function can be specified
        # which is run on the main ui thread when the background task is finished
        self.backgroundTask = BackgroundTask(task)
        if args:
            self.backgroundTask.set_arguments(args)
        self.backgroundTask.finished.connect(self.finish_background_task)
        self.postBackgroundTask = after
        self.backgroundTask.start()

    def open_file_dialog(self):
        # in case a file is open already, close it properly first
        if self.machine:
            ret = self.close_file()
            if not ret:
                # user canceled closing of current file; can't open new one
                return

        # open dialog for selecting a gcode file to be loaded
        dialog = QFileDialog(self)
        dialog.setFileMode(QFileDialog.ExistingFile)
        filters = ["G-code (*.gcode)", "Any files (*)"]
        dialog.setNameFilters(filters)
        dialog.selectNameFilter(filters[0])
        dialog.setViewMode(QFileDialog.Detail)

        filename = None
        if dialog.exec_():
            filename = dialog.selectedFiles()

        if filename:
            self.run_in_background(self.load_data,
                                   after=self.show_layer,
                                   args=filename)

    def open_settings_dialog(self):
        # open a dialog with settings
        dialog = SettingsDialog(self, self.profilecon)
        dialog.exec()

        # update settings
        self.configure_plot()

    def open_about_dialog(self):
        # open the about dialog
        dialog = QDialog()
        dialog.setWindowTitle("About...")

        layout = QVBoxLayout()
        dialog.setLayout(layout)

        text = QLabel(strings.about)
        layout.addWidget(text)

        dialog.exec()

    def close_file(self):
        # close the current gcode file, discard all data
        # Before, ask for user confirmation
        cfmsgbox = QMessageBox()
        cfmsgbox.setWindowTitle("Close file?")
        cfmsgbox.setText(
            "Are you sure you want to close the current file and discard all unsaved data?"
        )
        cfmsgbox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        cfmsgbox.setDefaultButton(QMessageBox.No)
        ret = cfmsgbox.exec()

        if ret == QMessageBox.Yes:
            for item in self.coord_plot_items:
                self.coordPlot.removeItem(item)

            self.machine = None
            self.gcode = None
            # TODO: fix: this will not terminate a running background process
            return True

        return False

    def export(self):
        pass

    def start_simulation(self):
        pass

    def fit_plot_to_window(self):
        x, y = self.machine.get_path_coordinates(
            layer_number=self.layerSlider.value())
        self.coordPlot.setRange(xRange=(min(x), max(x)),
                                yRange=(min(y), max(y)))

    def reset_plot_view(self):
        self.coordPlot.setXRange(self.profilecon.get_value("bed_min_x"),
                                 self.profilecon.get_value("bed_max_x"))
        self.coordPlot.setYRange(self.profilecon.get_value("bed_min_y"),
                                 self.profilecon.get_value("bed_max_y"))

    def load_data(self, filename):
        # initalizes a virtual machine from the gcode in the file given
        # all path data for this gcode is calculated; this is a cpu intensive task!
        self.gcode = GCode()
        self.gcode.load_file(filename)
        self.machine = Machine(self.gcode, self.profilecon)
        self.machine.create_path()

        # set the layer sliders maximum to represent the given amount of layers and enable the slider
        self.layerSlider.setMaximum(len(self.machine.layers) - 1)
        self.layerSlider.setEnabled(True)

    def show_layer(self):
        # plot path for the layer selected by the layer slider
        x, y = self.machine.get_path_coordinates(
            layer_number=self.layerSlider.value())
        pltitm = self.coordPlot.plot(x, y, clear=True)
        self.coord_plot_items.append(pltitm)
示例#17
0
class PyQtGraphDataPlot(QWidget):

    limits_changed = Signal()

    def __init__(self, parent=None):
        super(PyQtGraphDataPlot, self).__init__(parent)
        self._plot_widget = PlotWidget()
        self._plot_widget.getPlotItem().addLegend()
        self._plot_widget.setBackground((255, 255, 255))
        self._plot_widget.setXRange(0, 10, padding=0)
        vbox = QVBoxLayout()
        vbox.addWidget(self._plot_widget)
        self.setLayout(vbox)
        self._plot_widget.getPlotItem().sigRangeChanged.connect(
            self.limits_changed)

        self._curves = {}
        self._current_vline = None

    def add_curve(self,
                  curve_id,
                  curve_name,
                  curve_color=QColor(Qt.blue),
                  markers_on=False):
        pen = mkPen(curve_color, width=1)
        symbol = "o"
        symbolPen = mkPen(QColor(Qt.black))
        symbolBrush = mkBrush(curve_color)
        # this adds the item to the plot and legend
        if markers_on:
            plot = self._plot_widget.plot(name=curve_name,
                                          pen=pen,
                                          symbol=symbol,
                                          symbolPen=symbolPen,
                                          symbolBrush=symbolBrush,
                                          symbolSize=4)
        else:
            plot = self._plot_widget.plot(name=curve_name, pen=pen)
        self._curves[curve_id] = plot

    def remove_curve(self, curve_id):
        curve_id = str(curve_id)
        if curve_id in self._curves:
            self._plot_widget.removeItem(self._curves[curve_id])
            del self._curves[curve_id]
            self._update_legend()

    def _update_legend(self):
        # clear and rebuild legend (there is no remove item method for the legend...)
        self._plot_widget.clear()
        self._plot_widget.getPlotItem().legend.items = []
        for curve in self._curves.values():
            self._plot_widget.addItem(curve)
        if self._current_vline:
            self._plot_widget.addItem(self._current_vline)

    def redraw(self):
        pass

    def set_values(self, curve_id, data_x, data_y):
        curve = self._curves[curve_id]
        curve.setData(data_x, data_y)

    def vline(self, x, color):
        if self._current_vline:
            self._plot_widget.removeItem(self._current_vline)
        self._current_vline = self._plot_widget.addLine(x=x, pen=color)

    def set_xlim(self, limits):
        # TODO: this doesn't seem to handle fast updates well
        self._plot_widget.setXRange(limits[0], limits[1], padding=0)

    def set_ylim(self, limits):
        self._plot_widget.setYRange(limits[0], limits[1], padding=0)

    def get_xlim(self):
        x_range, _ = self._plot_widget.viewRange()
        return x_range

    def get_ylim(self):
        _, y_range = self._plot_widget.viewRange()
        return y_range
示例#18
0
class Plotter(QWidget):
    MAX_DATA_POINTS_PER_CURVE = 200000

    COLORS = [
        Qt.red,
        Qt.green,
        Qt.blue,  # RGB - http://ux.stackexchange.com/questions/79561
        Qt.yellow,
        Qt.cyan,
        Qt.magenta,  # Close to RGB
        Qt.darkRed,
        Qt.darkGreen,
        Qt.darkBlue,  # Darker RGB
        Qt.darkYellow,
        Qt.darkCyan,
        Qt.darkMagenta,  # Close to RGB
        Qt.gray,
        Qt.darkGray
    ]  # Leftovers

    INITIAL_X_RANGE = 60

    def __init__(self, parent=None):
        # Parent
        super(Plotter, self).__init__(parent)
        self.setWindowTitle('UAVCAN Plotter')
        self.setWindowIcon(APP_ICON)

        # Redraw timer
        self._update_timer = QTimer()
        self._update_timer.timeout.connect(self._update)
        self._update_timer.setSingleShot(False)
        self._update_timer.start(30)

        # PyQtGraph
        self._plot_widget = PlotWidget()
        self._plot_widget.setBackground((0, 0, 0))
        self._legend = self._plot_widget.addLegend()
        self._plot_widget.setRange(xRange=(0, self.INITIAL_X_RANGE), padding=0)
        self._plot_widget.showButtons()
        self._plot_widget.enableAutoRange()
        self._plot_widget.showGrid(x=True, y=True, alpha=0.4)

        # Controls
        # https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
        button_add_matcher = QtGui.QPushButton('New matcher', self)
        button_add_matcher.setIcon(QtGui.QIcon.fromTheme('list-add'))
        button_add_matcher.setToolTip('Add new curve matcher')
        button_add_matcher.clicked.connect(lambda: NewCurveMatcherWindow(
            self, lambda: sorted(self._active_messages), self.
            _add_curve_matcher).show())

        button_clear_plots = QtGui.QPushButton('Clear plots', self)
        button_clear_plots.setIcon(QtGui.QIcon.fromTheme('edit-clear'))
        button_clear_plots.setToolTip('Clear the plotting area')
        button_clear_plots.clicked.connect(lambda: self._remove_all_curves())

        def delete_all_matchers():
            self._curve_matchers = []
            for i in reversed(range(self._curve_matcher_container.count())):
                self._curve_matcher_container.itemAt(i).widget().deleteLater()
            self._remove_all_curves()

        button_delete_all_matchers = QtGui.QPushButton('Delete matchers', self)
        button_delete_all_matchers.setIcon(
            QtGui.QIcon.fromTheme('edit-delete'))
        button_delete_all_matchers.setToolTip('Delete all matchers')
        button_delete_all_matchers.clicked.connect(delete_all_matchers)

        self._autoscroll = QtGui.QCheckBox('Autoscroll', self)
        self._autoscroll.setChecked(True)
        self._max_x = self.INITIAL_X_RANGE

        # Layout
        control_panel = QHBoxLayout()
        control_panel.addWidget(button_add_matcher)
        control_panel.addWidget(button_clear_plots)
        control_panel.addWidget(self._autoscroll)
        control_panel.addStretch()
        control_panel.addWidget(button_delete_all_matchers)

        self._curve_matcher_container = QVBoxLayout()

        layout = QVBoxLayout()
        layout.addWidget(self._plot_widget, 1)
        layout.addLayout(control_panel)
        layout.addLayout(self._curve_matcher_container)
        self.setLayout(layout)

        # Logic
        self._color_index = 0
        self._curves = {}
        self._message_queue = multiprocessing.Queue()
        self._active_messages = set()  # set(data type name)
        self._curve_matchers = []

        # Defaults
        self._add_curve_matcher(
            CurveMatcher('uavcan.protocol.debug.KeyValue', 'value',
                         [('key', None)]))

    def _add_curve_matcher(self, matcher):
        self._curve_matchers.append(matcher)
        view = CurveMatcherView(matcher, self)

        def remove():
            self._curve_matchers.remove(matcher)
            self._curve_matcher_container.removeWidget(view)
            view.setParent(None)
            view.deleteLater()

        view.on_remove = remove
        self._curve_matcher_container.addWidget(view)

    def _update(self):
        # Processing messages
        while True:
            try:
                m = self._message_queue.get_nowait()
                self._process_message(m)
            except queue.Empty:
                break
        # Updating curves
        for curve in self._curves.values():
            if len(curve['x']):
                if len(curve['x']) > self.MAX_DATA_POINTS_PER_CURVE:
                    curve['x'] = curve['x'][-self.MAX_DATA_POINTS_PER_CURVE:]
                    curve['y'] = curve['y'][-self.MAX_DATA_POINTS_PER_CURVE:]
                assert len(curve['x']) == len(curve['y'])
                curve['plot'].setData(curve['x'], curve['y'])
                self._max_x = max(self._max_x, curve['x'][-1])
        # Updating view range
        if self._autoscroll.checkState():
            (xmin, xmax), _ = self._plot_widget.viewRange()
            diff = xmax - xmin
            xmax = self._max_x
            xmin = self._max_x - diff
            self._plot_widget.setRange(xRange=(xmin, xmax), padding=0)

    def _process_message(self, m):
        self._active_messages.add(m.data_type_name)
        for matcher in self._curve_matchers:
            if matcher.match(m):
                name, x, y = matcher.extract_curve_name_x_y(m)
                self._draw_curve(name, x, y)

    def _remove_all_curves(self):
        for curve in self._curves.values():
            self._plot_widget.removeItem(curve['plot'])
        self._plot_widget.clear()
        self._curves = {}
        self._color_index = 0
        self._legend.scene().removeItem(self._legend)
        self._legend = self._plot_widget.addLegend()

    def _draw_curve(self, name, x, y):
        if name not in self._curves:
            logging.info('Adding curve %r', name)
            color = self.COLORS[self._color_index % len(self.COLORS)]
            self._color_index += 1
            pen = mkPen(QColor(color), width=1)
            plot = self._plot_widget.plot(name=name, pen=pen)
            self._curves[name] = {
                'x': numpy.array([]),
                'y': numpy.array([]),
                'plot': plot
            }

        curve = self._curves[name]
        curve['x'] = numpy.append(curve['x'],
                                  [x] if isinstance(x, (float, int)) else x)
        curve['y'] = numpy.append(curve['y'],
                                  [y] if isinstance(y, (float, int)) else y)
        assert len(curve['x']) == len(curve['y'])

    def push_received_message(self, msg):
        self._message_queue.put_nowait(msg)
示例#19
0
class MesCourbes():
    def __init__(self):
        self.plot_widget = PlotWidget()
        self.plot_widget.showGrid(x=True, y=True)
        # self.plot_widget.getPlotItem().addLegend()
        self.plot_widget.setBackground((0, 0, 0))
        #  dictionary with all the curve and their data
        # curve 0 is dedicated to the live/acquisition plot
        self.curves = {}

    def add_curve(self, curve_id, curve_color, markers_on=False):
        curve_name = curve_id
        pen = pg.mkPen(curve_color, width=3)
        symbol = "o"
        symbolPen = pg.mkPen(0, 0, 0)
        symbolBrush = curve_color
        symbolSize = 8
        # this adds the item to the plot and legend
        if markers_on:
            plot = self.plot_widget.plot(name=curve_name,
                                         pen=pen,
                                         symbol=symbol,
                                         symbolPen=symbolPen,
                                         symbolBrush=symbolBrush,
                                         symbolSize=symbolSize)
        else:
            plot = self.plot_widget.plot(name=curve_name, pen=pen)
        self.curves[curve_id] = {
            'plot': plot,
            'data': {
                'X': [],
                'Y': [],
                'R': [],
                'Phi': []
            }
        }

    def clear_data(self, curve_id):
        for k in ['Time', 'X', 'Y', 'R', 'Phi']:
            self.curves[curve_id]['data'][k] = []

    # def display(self,curve_id,what):
    #     X = self.curves[curve_id]['data']['Freq']
    #     Y = self.curves[curve_id]['data'][what]
    #     # during acquisition freq is longer than the others datas
    #     # so it is useful to reduce it
    #     if len(X) != len(Y):
    #         X = self.curves[curve_id]['data']['Freq'][0:len(Y)]
    #     self.set_values(curve_id,X,Y)

    def remove_curve(self, curve_id):
        curve_id = str(curve_id)
        if curve_id in self.curves:
            self.plot_widget.removeItem(self.curves[curve_id]['plot'])
            del self.curves[curve_id]

    def set_values(self, curve_id, data_x, data_y):
        curve = self.curves[curve_id]['plot']
        curve.setData(data_x, data_y)

    def update_X_Y_R_Phi(self, curve_id, A):
        self.curves[curve_id]['data']['X'] = np.append(
            self.curves[curve_id]['data']['X'], A[0])
        self.curves[curve_id]['data']['Y'] = np.append(
            self.curves[curve_id]['data']['Y'], A[1])
        self.curves[curve_id]['data']['R'] = np.append(
            self.curves[curve_id]['data']['R'], A[2])
        self.curves[curve_id]['data']['Phi'] = np.append(
            self.curves[curve_id]['data']['Phi'], A[3])
示例#20
0
class RealtimePlotWidget(QWidget):
    AUTO_RANGE_FRACTION = 0.99

    COLORS = [Qt.red, Qt.blue, Qt.green, Qt.magenta, Qt.cyan,
              Qt.darkRed, Qt.darkBlue, Qt.darkGreen, Qt.darkYellow, Qt.gray]

    def __init__(self, display_measurements, parent):
        super(RealtimePlotWidget, self).__init__(parent)
        self.setAttribute(Qt.WA_DeleteOnClose)              # This is required to stop background timers!
        self._plot_widget = PlotWidget()
        self._plot_widget.setBackground((0, 0, 0))
        self._legend = self._plot_widget.addLegend()
        self._plot_widget.showButtons()
        self._plot_widget.showGrid(x=True, y=True, alpha=0.3)
        vbox = QVBoxLayout(self)
        vbox.addWidget(self._plot_widget)
        self.setLayout(vbox)

        self._last_update_ts = 0
        self._reset_required = False

        self._update_timer = QTimer(self)
        self._update_timer.setSingleShot(False)
        self._update_timer.timeout.connect(self._update)
        self._update_timer.start(200)

        self._color_index = 0
        self._curves = {}

        # Crosshair
        def _render_measurements(cur, ref):
            text = 'time %.6f sec,  y %.6f' % cur
            if ref is None:
                return text
            dt = cur[0] - ref[0]
            dy = cur[1] - ref[1]
            if abs(dt) > 1e-12:
                freq = '%.6f' % abs(1 / dt)
            else:
                freq = 'inf'
            display_measurements(text + ';' + ' ' * 4 + 'dt %.6f sec,  freq %s Hz,  dy %.6f' % (dt, freq, dy))

        display_measurements('Hover to sample Time/Y, click to set new reference')
        add_crosshair(self._plot_widget, _render_measurements)

        # Final reset
        self.reset()

    def _trigger_auto_reset_if_needed(self):
        ts = time.monotonic()
        dt = ts - self._last_update_ts
        self._last_update_ts = ts
        if dt > 2:
            self._reset_required = True

    def add_curve(self, curve_id, curve_name, data_x=[], data_y=[]):
        color = QColor(self.COLORS[self._color_index % len(self.COLORS)])
        self._color_index += 1
        pen = mkPen(color, width=1)
        plot = self._plot_widget.plot(name=curve_name, pen=pen)
        data_x = numpy.array(data_x)
        data_y = numpy.array(data_y)
        self._curves[curve_id] = {'data': (data_x, data_y), 'plot': plot}
        self._trigger_auto_reset_if_needed()

    def update_values(self, curve_id, x, y):
        curve = self._curves[curve_id]
        old_x, old_y = curve['data']
        curve['data'] = numpy.append(old_x, x), numpy.append(old_y, y)
        self._trigger_auto_reset_if_needed()

    def reset(self):
        for curve in self._curves.keys():
            self._plot_widget.removeItem(self._curves[curve]['plot'])

        self._curves = {}
        self._color_index = 0

        self._plot_widget.enableAutoRange(enable=self.AUTO_RANGE_FRACTION,
                                          x=self.AUTO_RANGE_FRACTION,
                                          y=self.AUTO_RANGE_FRACTION)

        self._legend.scene().removeItem(self._legend)
        self._legend = self._plot_widget.addLegend()

    def _update(self):
        if self._reset_required:
            self.reset()
            self._reset_required = False

        for curve in self._curves.values():
            if len(curve['data'][0]):
                curve['plot'].setData(*curve['data'])