Esempio n. 1
0
class IMUChartView(QChartView, BaseGraph):
    def __init__(self, parent=None):
        super().__init__(parent=parent)

        # Render on OpenGL
        self.setViewport(QOpenGLWidget())

        self.xvalues = {}

        self.chart = QChart()

        self.setChart(self.chart)
        self.chart.legend().setVisible(True)
        self.chart.legend().setAlignment(Qt.AlignTop)
        self.ncurves = 0
        self.setRenderHint(QPainter.Antialiasing)

        # Cursor (with chart as parent)
        self.cursor = QGraphicsLineItem(self.chart)

        self.cursor.setZValue(100.0)
        # self.scene().addItem(self.cursor)

        # Selection features
        # self.setRubberBand(QChartView.HorizontalRubberBand)
        self.selectionBand = QRubberBand(QRubberBand.Rectangle, self)
        self.selecting = False
        self.initialClick = QPoint()

        # Track mouse
        self.setMouseTracking(True)

        self.labelValue = QLabel(self)
        self.labelValue.setStyleSheet(
            "background-color: rgba(255,255,255,75%); color: black;")
        self.labelValue.setAlignment(Qt.AlignCenter)
        self.labelValue.setMargin(5)
        self.labelValue.setVisible(False)

        self.build_style()

        self.selection_start_time = None
        self.selection_stop_time = None
        self.cursor_time = None

    def build_style(self):
        self.setStyleSheet("QLabel{color:blue;}")
        self.chart.setTheme(QChart.ChartThemeBlueCerulean)
        self.setBackgroundBrush(QBrush(Qt.darkGray))
        self.chart.setPlotAreaBackgroundBrush(QBrush(Qt.black))
        self.chart.setPlotAreaBackgroundVisible(True)

    def save_as_png(self, file_path):
        pixmap = self.grab()

        child = self.findChild(QOpenGLWidget)

        painter = QPainter(pixmap)
        if child is not None:
            d = child.mapToGlobal(QPoint()) - self.mapToGlobal(QPoint())
            painter.setCompositionMode(QPainter.CompositionMode_SourceAtop)
            painter.drawImage(d, child.grabFramebuffer())

        painter.end()
        pixmap.save(file_path, 'PNG')

    #  def closeEvent(self, event):
    #     self.aboutToClose.emit(self)

    @staticmethod
    def decimate(xdata, ydata):
        # assert(len(xdata) == len(ydata))

        # Decimate only if we have too much data
        decimate_factor = len(xdata) / 100000.0
        # decimate_factor = 1.0

        if decimate_factor > 1.0:
            decimate_factor = int(np.floor(decimate_factor))
            # print('decimate factor', decimate_factor)

            x = np.ndarray(int(len(xdata) / decimate_factor), dtype=np.float64)
            y = np.ndarray(int(len(ydata) / decimate_factor), dtype=np.float64)
            # for i in range(len(x)):
            for i, _ in enumerate(x):
                index = i * decimate_factor
                # assert(index < len(xdata))
                x[i] = xdata[index]
                y[i] = ydata[index]
                if x[i] < x[0]:
                    print('timestamp error', x[i], x[0])
            return x, y
        else:
            return xdata, ydata

    @pyqtSlot(float, float)
    def axis_range_changed(self, min_value, max_value):
        # print('axis_range_changed', min, max)
        for axis in self.chart.axes():
            axis.applyNiceNumbers()

    def update_axes(self):

        # Get and remove all axes
        for axis in self.chart.axes():
            self.chart.removeAxis(axis)

        # Create new axes
        # Create axis X
        axisx = QDateTimeAxis()
        axisx.setTickCount(5)
        axisx.setFormat("dd MMM yyyy hh:mm:ss")
        axisx.setTitleText("Date")
        self.chart.addAxis(axisx, Qt.AlignBottom)

        # Create axis Y
        axisY = QValueAxis()
        axisY.setTickCount(5)
        axisY.setLabelFormat("%.3f")
        axisY.setTitleText("Values")
        self.chart.addAxis(axisY, Qt.AlignLeft)
        # axisY.rangeChanged.connect(self.axis_range_changed)

        ymin = None
        ymax = None

        # Attach axes to series, find min-max
        for series in self.chart.series():
            series.attachAxis(axisx)
            series.attachAxis(axisY)
            vect = series.pointsVector()
            # for i in range(len(vect)):
            for i, _ in enumerate(vect):
                if ymin is None:
                    ymin = vect[i].y()
                    ymax = vect[i].y()
                else:
                    ymin = min(ymin, vect[i].y())
                    ymax = max(ymax, vect[i].y())

        # Update range
        # print('min max', ymin, ymax)
        if ymin is not None:
            axisY.setRange(ymin, ymax)

        # Make the X,Y axis more readable
        # axisx.applyNiceNumbers()
        # axisY.applyNiceNumbers()

    def add_data(self, xdata, ydata, color=None, legend_text=None):
        curve = QLineSeries()
        pen = curve.pen()
        if color is not None:
            pen.setColor(color)
        pen.setWidthF(1.5)

        curve.setPen(pen)
        # curve.setPointsVisible(True)

        # curve.setUseOpenGL(True)

        self.total_samples = max(self.total_samples, len(xdata))

        # Decimate
        xdecimated, ydecimated = self.decimate(xdata, ydata)

        # Data must be in ms since epoch
        # curve.append(self.series_to_polyline(xdecimated * 1000.0, ydecimated))
        # self.reftime = datetime.datetime.fromtimestamp(xdecimated[0])

        # if len(xdecimated) > 0:
        #    xdecimated = xdecimated - xdecimated[0]

        xdecimated *= 1000  # No decimal expected
        points = []
        for i, _ in enumerate(xdecimated):
            # TODO hack
            # curve.append(QPointF(xdecimated[i], ydecimated[i]))
            points.append(QPointF(xdecimated[i], ydecimated[i]))

        curve.replace(points)
        points.clear()

        if legend_text is not None:
            curve.setName(legend_text)

        # Needed for mouse events on series
        self.chart.setAcceptHoverEvents(True)
        self.xvalues[self.ncurves] = np.array(xdecimated)

        # connect signals / slots
        # curve.clicked.connect(self.lineseries_clicked)
        # curve.hovered.connect(self.lineseries_hovered)

        # Add series
        self.chart.addSeries(curve)
        self.ncurves += 1
        self.update_axes()

    def update_data(self, xdata, ydata, series_id):
        if series_id < len(self.chart.series()):
            # Find start time to replace data
            current_series = self.chart.series()[series_id]
            current_points = current_series.pointsVector()

            # Find start and end indexes
            start_index = -1
            try:
                start_index = current_points.index(
                    QPointF(xdata[0] * 1000, ydata[0]))  # Right on the value!
                # print("update_data: start_index found exact match.")
            except ValueError:
                # print("update_data: start_index no exact match - scanning deeper...")
                for i, value in enumerate(current_points):
                    # print(str(current_points[i].x()) + " == " + str(xdata[0]*1000))
                    if current_points[i].x() == xdata[0] * 1000 or (
                            i > 0 and current_points[i - 1].x() <
                            xdata[0] * 1000 < current_points[i].x()):
                        start_index = i
                        # print("update_data: start_index found approximative match.")
                        break

            end_index = -1
            try:
                end_index = current_points.index(
                    QPointF(xdata[len(xdata) - 1] * 1000,
                            ydata[len(ydata) - 1]))  # Right on!
                # print("update_data: start_index found exact match.")
            except ValueError:
                # print("update_data: start_index no exact match - scanning deeper...")
                for i, value in enumerate(current_points):
                    # print(str(current_points[i].x()) + " == " + str(xdata[0]*1000))
                    if current_points[i].x(
                    ) == xdata[len(xdata) - 1] * 1000 or (
                            i > 0 and
                            current_points[i - 1].x() < xdata[len(xdata) - 1] *
                            1000 < current_points[i].x()):
                        end_index = i
                        # print("update_data: start_index found approximative match.")
                        break

            if start_index < 0 or end_index < 0:
                return

            # Decimate, if needed
            xdata, ydata = self.decimate(xdata, ydata)

            # Check if we have the same number of points for that range. If not, remove and replace!
            target_points = current_points[start_index:end_index]
            if len(target_points) != len(xdata):
                points = []
                for i, value in enumerate(xdata):
                    # TODO improve
                    points.append(QPointF(value * 1000, ydata[i]))

                new_points = current_points[0:start_index] + points[0:len(points)-1] + \
                             current_points[end_index:len(current_points)-1]

                current_series.replace(new_points)
                new_points.clear()

                # self.xvalues[series_id] = np.array(xdata)

        return

    @classmethod
    def set_title(cls, title):
        # print('Setting title: ', title)
        # self.chart.setTitle(title)
        return

    @staticmethod
    def series_to_polyline(xdata, ydata):
        """Convert series data to QPolygon(F) polyline

        This code is derived from PythonQwt's function named
        `qwt.plot_curve.series_to_polyline`"""

        # print('series_to_polyline types:', type(xdata[0]), type(ydata[0]))
        size = len(xdata)
        polyline = QPolygonF(size)

        # for i in range(0, len(xdata)):
        #   polyline[i] = QPointF(xdata[i] - xdata[0], ydata[i])
        for i, data in enumerate(xdata):
            polyline[i] = QPointF(data - xdata[0], ydata[i])

        # pointer = polyline.data()
        # dtype, tinfo = np.float, np.finfo  # integers: = np.int, np.iinfo
        # pointer.setsize(2*polyline.size()*tinfo(dtype).dtype.itemsize)
        # memory = np.frombuffer(pointer, dtype)
        # memory[:(size-1)*2+1:2] = xdata
        # memory[1:(size-1)*2+2:2] = ydata
        return polyline

    def add_test_data(self):

        # 100Hz, one day accelerometer values
        npoints = 1000 * 60 * 24

        xdata = np.linspace(0., 10., npoints)
        self.add_data(xdata, np.sin(xdata), color=Qt.red, legend_text='Acc. X')
        # self.add_data(xdata, np.cos(xdata), color=Qt.green, legend_text='Acc. Y')
        # self.add_data(xdata, np.cos(2 * xdata), color=Qt.blue, legend_text='Acc. Z')
        self.set_title(
            "Simple example with %d curves of %d points (OpenGL Accelerated Series)"
            % (self.ncurves, npoints))

    def mouseMoveEvent(self, e: QMouseEvent):
        if self.selecting:

            clicked_x = max(
                self.chart.plotArea().x(),
                min(
                    self.mapToScene(e.pos()).x(),
                    self.chart.plotArea().x() + self.chart.plotArea().width()))

            clicked_y = max(
                self.chart.plotArea().y(),
                min(
                    self.mapToScene(e.pos()).y(),
                    self.chart.plotArea().y() +
                    self.chart.plotArea().height()))

            current_pos = QPoint(clicked_x, clicked_y)  # e.pos()

            self.selection_stop_time = self.chart.mapToValue(
                QPointF(clicked_x, 0)).x()

            self.setCursorPosition(clicked_x, True)

            if self.interaction_mode == GraphInteractionMode.SELECT:
                if current_pos.x() < self.initialClick.x():
                    start_x = current_pos.x()
                    width = self.initialClick.x() - start_x
                else:
                    start_x = self.initialClick.x()
                    width = current_pos.x() - self.initialClick.x()

                self.selectionBand.setGeometry(
                    QRect(start_x,
                          self.chart.plotArea().y(), width,
                          self.chart.plotArea().height()))
                if self.selection_rec:
                    self.selection_rec.setRect(
                        QRectF(start_x,
                               self.chart.plotArea().y(), width,
                               self.chart.plotArea().height()))

            if self.interaction_mode == GraphInteractionMode.MOVE:
                new_pos = current_pos - self.initialClick
                self.chart.scroll(-new_pos.x(), new_pos.y())
                self.initialClick = current_pos

    def mousePressEvent(self, e: QMouseEvent):
        # Handling rubberbands
        # super().mousePressEvent(e)
        # Verify if click is inside plot area
        # if self.chart.plotArea().contains(e.pos()):

        self.selecting = True
        clicked_x = max(
            self.chart.plotArea().x(),
            min(
                self.mapToScene(e.pos()).x(),
                self.chart.plotArea().x() + self.chart.plotArea().width()))

        clicked_y = max(
            self.chart.plotArea().y(),
            min(
                self.mapToScene(e.pos()).y(),
                self.chart.plotArea().y() + self.chart.plotArea().height()))

        self.initialClick = QPoint(clicked_x, clicked_y)  # e.pos()

        self.selection_start_time = self.chart.mapToValue(QPointF(
            clicked_x, 0)).x()

        self.setCursorPosition(clicked_x, True)

        if self.interaction_mode == GraphInteractionMode.SELECT:
            self.selectionBand.setGeometry(
                QRect(self.initialClick.x(),
                      self.chart.plotArea().y(), 1,
                      self.chart.plotArea().height()))
            self.selectionBand.show()

        if self.interaction_mode == GraphInteractionMode.MOVE:
            QGuiApplication.setOverrideCursor(Qt.ClosedHandCursor)
            self.labelValue.setVisible(False)

    def mouseReleaseEvent(self, e: QMouseEvent):

        # Assure if click is inside plot area
        # Handling rubberbands (with min / max x)
        clicked_x = max(
            self.chart.plotArea().x(),
            min(
                self.mapToScene(e.pos()).x(),
                self.chart.plotArea().x() + self.chart.plotArea().width()))

        if self.interaction_mode == GraphInteractionMode.SELECT:
            self.selectionBand.hide()
            if clicked_x != self.mapToScene(self.initialClick).x():
                mapped_x = self.mapToScene(self.initialClick).x()
                if self.initialClick.x() < clicked_x:
                    self.setSelectionArea(mapped_x, clicked_x, True)
                else:
                    self.setSelectionArea(clicked_x, mapped_x, True)

        if self.interaction_mode == GraphInteractionMode.MOVE:
            QGuiApplication.restoreOverrideCursor()

        self.selecting = False

    def clearSelectionArea(self, emit_signal=False):
        if self.selection_rec:
            self.scene().removeItem(self.selection_rec)
            self.selection_rec = None

        if emit_signal:
            self.clearedSelectionArea.emit()

    def setSelectionArea(self, start_pos, end_pos, emit_signal=False):
        selection_brush = QBrush(QColor(153, 204, 255, 128))
        selection_pen = QPen(Qt.transparent)

        if self.selection_rec:
            self.scene().removeItem(self.selection_rec)

        self.selection_rec = self.scene().addRect(
            start_pos,
            self.chart.plotArea().y(), end_pos - start_pos,
            self.chart.plotArea().height(), selection_pen, selection_brush)
        if emit_signal:
            self.selectedAreaChanged.emit(
                self.chart.mapToValue(QPointF(start_pos, 0)).x(),
                self.chart.mapToValue(QPointF(end_pos, 0)).x())

    def setSelectionAreaFromTime(self,
                                 start_time,
                                 end_time,
                                 emit_signal=False):
        # Convert times to x values
        if isinstance(start_time, datetime.datetime):
            start_time = start_time.timestamp() * 1000
        if isinstance(end_time, datetime.datetime):
            end_time = end_time.timestamp() * 1000

        start_pos = self.chart.mapToPosition(QPointF(start_time, 0)).x()
        end_pos = self.chart.mapToPosition(QPointF(end_time, 0)).x()

        self.setSelectionArea(start_pos, end_pos)

    def setCursorPosition(self, pos, emit_signal=False):

        self.cursor_time = self.chart.mapToValue(QPointF(pos, 0)).x()

        # print (pos)
        pen = self.cursor.pen()
        pen.setColor(Qt.cyan)
        pen.setWidthF(1.0)
        self.cursor.setPen(pen)
        # On Top
        self.cursor.setZValue(100.0)

        area = self.chart.plotArea()
        x = pos
        y1 = area.y()
        y2 = area.y() + area.height()

        # self.cursor.set
        self.cursor.setLine(x, y1, x, y2)
        self.cursor.show()

        xmap_initial = self.chart.mapToValue(QPointF(pos, 0)).x()
        display = ''
        # '<i>' + (datetime.datetime.fromtimestamp(xmap + self.reftime.timestamp())).strftime('%d-%m-%Y %H:%M:%S') +
        # '</i><br />'
        ypos = 10
        last_val = None
        for i in range(self.ncurves):
            # Find nearest point
            idx = (np.abs(self.xvalues[i] - xmap_initial)).argmin()
            ymap = self.chart.series()[i].at(idx).y()
            xmap = self.chart.series()[i].at(idx).x()
            if i == 0:
                display += "<i>" + (datetime.datetime.fromtimestamp(xmap_initial/1000)).strftime('%d-%m-%Y %H:%M:%S:%f') + \
                           "</i>"

            # Compute where to display label
            if last_val is None or ymap > last_val:
                last_val = ymap
                ypos = self.chart.mapToPosition(QPointF(xmap, ymap)).y()
            if display != '':
                display += '<br />'

            display += self.chart.series()[i].name(
            ) + ': <b>' + '%.3f' % ymap + '</b>'

        self.labelValue.setText(display)
        self.labelValue.setGeometry(pos, ypos, 100, 100)
        self.labelValue.adjustSize()
        self.labelValue.setVisible(True)

        if emit_signal:
            self.cursorMoved.emit(xmap_initial)

        self.update()

    def setCursorPositionFromTime(self, timestamp, emit_signal=False):
        # Find nearest point
        if isinstance(timestamp, datetime.datetime):
            timestamp = timestamp.timestamp()
        pos = self.get_pos_from_time(timestamp)
        self.setCursorPosition(pos, emit_signal)

    def get_pos_from_time(self, timestamp):
        px = timestamp
        idx1 = (np.abs(self.xvalues[0] - px)).argmin()
        x1 = self.chart.series()[0].at(idx1).x()
        pos1 = self.chart.mapToPosition(QPointF(x1, 0)).x()
        idx2 = idx1 + 1
        if idx2 < len(self.chart.series()[0]):
            x2 = self.chart.series()[0].at(idx2).x()
            if x2 != x1:
                pos2 = self.chart.mapToPosition(QPointF(x2, 0)).x()
                x2 /= 1000
                x1 /= 1000
                pos = (((px - x1) / (x2 - x1)) * (pos2 - pos1)) + pos1
            else:
                pos = pos1
        else:
            pos = pos1
        return pos

    def resizeEvent(self, e: QResizeEvent):
        super().resizeEvent(e)

        # oldSize = e.oldSize()
        # newSize = e.size()

        # Update cursor from time
        if self.cursor_time:
            self.setCursorPositionFromTime(self.cursor_time / 1000.0)

        # Update selection
        if self.selection_rec:
            self.setSelectionAreaFromTime(self.selection_start_time,
                                          self.selection_stop_time)

    def zoom_in(self):
        self.chart.zoomIn()
        self.update_axes()

    def zoom_out(self):
        self.chart.zoomOut()
        self.update_axes()

    def zoom_area(self):
        if self.selection_rec:
            zoom_rec = self.selection_rec.rect()
            zoom_rec.setY(0)
            zoom_rec.setHeight(self.chart.plotArea().height())
            self.chart.zoomIn(zoom_rec)
            self.clearSelectionArea(True)
            self.update_axes()

    def zoom_reset(self):
        self.chart.zoomReset()
        self.update_axes()

    def get_displayed_start_time(self):
        min_x = self.chart.mapToScene(self.chart.plotArea()).boundingRect().x()
        xmap = self.chart.mapToValue(QPointF(min_x, 0)).x()
        return datetime.datetime.fromtimestamp(xmap / 1000)

    def get_displayed_end_time(self):
        max_x = self.chart.mapToScene(self.chart.plotArea()).boundingRect().x()
        max_x += self.chart.mapToScene(
            self.chart.plotArea()).boundingRect().width()
        xmap = self.chart.mapToValue(QPointF(max_x, 0)).x()
        return datetime.datetime.fromtimestamp(xmap / 1000)

    @property
    def is_zoomed(self):
        return self.chart.isZoomed()
Esempio n. 2
0
class MainWindow(QMainWindow):
    BASE_DIR = os.path.dirname(os.path.abspath(__file__))
    MAIN_UI_FILE = os.path.join(BASE_DIR, "main.ui")
    NEW_DISH_POPUP_UI_FILE = os.path.join(BASE_DIR, "new_dish_popup.ui")
    NEW_DISH_MULTI_POPUP_UI_FILE = os.path.join(BASE_DIR,
                                                "new_dish_multi_popup.ui")
    NEW_DISH_DATA_POPUP_UI_FILE = os.path.join(BASE_DIR,
                                               "new_dish_data_popup.ui")
    MODIFY_DISH_POPUP_UI_FILE = os.path.join(BASE_DIR, "modify_dish_popup.ui")
    DB_FILE = os.path.join(BASE_DIR, "restaurant.db")

    def __init__(self):
        super(MainWindow, self).__init__()
        # Initialize variable
        self.db_connection = None
        self.new_dish_popup = QWidget()
        self.new_dish_multi_popup = QWidget()
        self.new_dish_data_popup = QWidget()
        self.modify_dish_popup = QWidget()
        self.dish_table_model = QStandardItemModel(0, 6)
        self.dish_table_proxy = TableFilter()
        self.dish_data_table_model = QStandardItemModel(0, 6)
        self.dish_data_table_proxy = TableFilter()
        self.graph_chart = None
        self.graph_series = {}

        # Load UI designs
        uic.loadUi(self.MAIN_UI_FILE, self)
        uic.loadUi(self.NEW_DISH_POPUP_UI_FILE, self.new_dish_popup)
        uic.loadUi(self.NEW_DISH_MULTI_POPUP_UI_FILE,
                   self.new_dish_multi_popup)
        uic.loadUi(self.NEW_DISH_DATA_POPUP_UI_FILE, self.new_dish_data_popup)
        uic.loadUi(self.MODIFY_DISH_POPUP_UI_FILE, self.modify_dish_popup)
        self.init_dish_table()
        self.init_dish_data_table()
        self.init_graph()

        # Connect to database
        self.init_db_connection()

        # MainWindow Bind action triggers
        self.action_new_dish.triggered.connect(self.show_new_dish_popup)
        self.action_new_dish_multi.triggered.connect(
            self.show_new_dish_multi_popup)
        self.action_new_data_multi.triggered.connect(
            lambda: self.modify_new_dish_data_popup_table(show=True))
        self.tabWidget.currentChanged.connect(self.update_graph)

        # Dish Table filter bind
        self.dish_lineEdit.textChanged.connect(
            lambda text, col_idx=1: self.dish_table_proxy.set_col_regex_filter(
                col_idx, text))
        self.lower_price_doubleSpinBox.valueChanged.connect(
            lambda value, col_idx=2: self.dish_table_proxy.
            set_col_number_filter(col_idx, value, -1))
        self.higher_price_doubleSpinBox.valueChanged.connect(
            lambda value, col_idx=2: self.dish_table_proxy.
            set_col_number_filter(col_idx, -1, value))
        self.lower_week_sell_spinBox.valueChanged.connect(
            lambda value, col_idx=3: self.dish_table_proxy.
            set_col_number_filter(col_idx, value, -1))
        self.higher_week_sell_spinBox.valueChanged.connect(
            lambda value, col_idx=3: self.dish_table_proxy.
            set_col_number_filter(col_idx, -1, value))

        # Dish Data Table filter bind
        self.lower_data_dateEdit.dateChanged.connect(
            lambda date, col_idx=1: self.dish_data_table_proxy.
            set_col_date_filter(col_idx, date, -1))
        self.higher_data_dateEdit.dateChanged.connect(
            lambda date, col_idx=1: self.dish_data_table_proxy.
            set_col_date_filter(col_idx, -1, date))
        self.data_lineEdit.textChanged.connect(
            lambda text, col_idx=2: self.dish_data_table_proxy.
            set_col_regex_filter(col_idx, text))
        self.lower_data_doubleSpinBox.valueChanged.connect(
            lambda value, col_idx=3: self.dish_data_table_proxy.
            set_col_number_filter(col_idx, value, -1))
        self.higher_data_doubleSpinBox.valueChanged.connect(
            lambda value, col_idx=3: self.dish_data_table_proxy.
            set_col_number_filter(col_idx, -1, value))
        self.lower_data_spinBox.valueChanged.connect(
            lambda value, col_idx=4: self.dish_data_table_proxy.
            set_col_number_filter(col_idx, value, -1))
        self.higher_data_spinBox.valueChanged.connect(
            lambda value, col_idx=4: self.dish_data_table_proxy.
            set_col_number_filter(col_idx, -1, value))
        self.data_all_check_checkBox.stateChanged.connect(
            lambda state, col_idx=5: self.data_table_check_state(
                state, col_idx))
        self.dish_data_table_model.itemChanged.connect(self.update_series)

        # Popup bind action triggers
        self.new_dish_popup.create_new_dish_btn.clicked.connect(
            self.create_new_dish)
        self.new_dish_multi_popup.pushButton_ok.clicked.connect(
            self.create_new_dish_multi)
        self.new_dish_data_popup.dateEdit.dateChanged.connect(
            self.modify_new_dish_data_popup_table)
        self.new_dish_data_popup.pushButton_ok.clicked.connect(
            self.create_new_dish_data)

        # Get current dishes
        self.load_dish_table()
        self.load_dish_data_table()
        self.new_dish_data_popup.dateEdit.setDate(QtCore.QDate.currentDate())

    def init_dish_table(self):
        self.dish_tableView.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        # Set Header data and stretch
        for col, col_name in enumerate(
            ["ID", "菜品", "价格", "近7天总售出", "操作", "备注"]):
            self.dish_table_model.setHeaderData(col, Qt.Horizontal, col_name,
                                                Qt.DisplayRole)
        self.dish_table_proxy.setSourceModel(self.dish_table_model)
        self.dish_tableView.setModel(self.dish_table_proxy)
        self.dish_tableView.setColumnHidden(0, True)
        for (col, method) in [(1, "Regex"), (2, "Number"), (3, "Number"),
                              (5, "Regex")]:
            self.dish_table_proxy.filter_method[col] = method

    def init_dish_data_table(self):
        self.data_tableView.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        for col, col_name in enumerate(
            ["Dish_ID", "日期", "菜品", "价格", "售出", "选择"]):
            self.dish_data_table_model.setHeaderData(col, Qt.Horizontal,
                                                     col_name, Qt.DisplayRole)
        self.dish_data_table_proxy.setSourceModel(self.dish_data_table_model)
        self.data_tableView.setModel(self.dish_data_table_proxy)
        self.data_tableView.setColumnHidden(0, True)
        for (col, method) in [(1, "Date"), (2, "Regex"), (3, "Number"),
                              (4, "Number")]:
            self.dish_data_table_proxy.filter_method[col] = method

    def init_graph(self):
        self.graph_chart = QChart(title="售出图")
        self.graph_chart.legend().setVisible(True)
        self.graph_chart.setAcceptHoverEvents(True)

        graph_view = QChartView(self.graph_chart)
        graph_view.setRenderHint(QPainter.Antialiasing)
        self.gridLayout_5.addWidget(graph_view)

    def init_db_connection(self):
        self.db_connection = sqlite3.connect(self.DB_FILE)
        cursor = self.db_connection.cursor()
        # check create table if not exist
        sql_create_dish_table = """ CREATE TABLE IF NOT EXISTS dish (
                                        id integer PRIMARY KEY,
                                        name text NOT NULL,
                                        price numeric Not NULL,
                                        remarks text,
                                        UNIQUE (name, price)
                                    ); """
        sql_create_dish_data_table = """ CREATE TABLE IF NOT EXISTS dish_data (
                                            dish_id integer NOT NULL REFERENCES dish(id) ON DELETE CASCADE,
                                            date date,
                                            sell_num integer DEFAULT 0,
                                            PRIMARY KEY (dish_id, date),
                                            CONSTRAINT dish_fk 
                                                FOREIGN KEY (dish_id) 
                                                REFERENCES dish (id) ON DELETE CASCADE 
                                        ); """
        sql_trigger = """
            CREATE TRIGGER IF NOT EXISTS place_holder_data
            AFTER INSERT ON dish
            BEGIN
                INSERT INTO dish_data (dish_id, date, sell_num) VALUES(new.id, null, 0);
            END;
        """
        cursor.execute(sql_create_dish_table)
        cursor.execute(sql_create_dish_data_table)
        cursor.execute("PRAGMA FOREIGN_KEYS = on")
        cursor.execute(sql_trigger)

        cursor.close()

    def load_dish_table(self):
        today = datetime.today()
        sql_select_query = """
            SELECT dish.id, dish.name, dish.price, COALESCE(SUM(dish_data.sell_num), 0), dish.remarks
            FROM dish LEFT JOIN dish_data 
            ON dish.id = dish_data.dish_id
            WHERE dish_data.date IS NULL OR dish_data.date BETWEEN date('{}') and date('{}')
            GROUP BY dish.id
            ORDER BY dish.name, dish.price;""".format(
            (today - timedelta(days=7)).strftime("%Y-%m-%d"),
            today.strftime("%Y-%m-%d"))
        cursor = self.db_connection.cursor()
        cursor.execute(sql_select_query)
        records = cursor.fetchall()
        for row_idx, record in enumerate(records):
            self.dish_table_model.appendRow(create_dish_table_row(*record))
        cursor.close()
        self.dish_tableView.setItemDelegateForColumn(
            4,
            DishTableDelegateCell(self.show_modify_dish_popup,
                                  self.delete_dish, self.dish_tableView))

    def load_dish_data_table(self):
        sql_select_query = """
            SELECT dish_data.dish_id, dish_data.date, dish.name, dish.price, dish_data.sell_num
            FROM dish_data LEFT JOIN dish  
            ON dish_data.dish_id = dish.id
            WHERE dish_data.date IS NOT NULL
            ORDER BY dish_data.date DESC, dish.name, dish.price, dish_data.sell_num;"""
        cursor = self.db_connection.cursor()
        cursor.execute(sql_select_query)
        records = cursor.fetchall()
        for row_idx, record in enumerate(records):
            self.dish_data_table_model.appendRow(
                create_dish_data_table_row(*record))
        cursor.close()
        self.lower_data_dateEdit.setDate(QDate.currentDate().addDays(-7))
        self.higher_data_dateEdit.setDate(QDate.currentDate())
        self.data_tableView.setItemDelegateForColumn(
            5, DishDataTableDelegateCell(self.data_tableView))

    def data_table_check_state(self, state, col):
        for row in range(self.dish_data_table_proxy.rowCount()):
            index = self.dish_data_table_proxy.mapToSource(
                self.dish_data_table_proxy.index(row, col))
            if index.isValid():
                self.dish_data_table_model.setData(index, str(state),
                                                   Qt.DisplayRole)

    def show_new_dish_popup(self):
        # Move popup to center
        point = self.rect().center()
        global_point = self.mapToGlobal(point)
        self.new_dish_popup.move(
            global_point - QtCore.QPoint(self.new_dish_popup.width() // 2,
                                         self.new_dish_popup.height() // 2))
        self.new_dish_popup.show()

    def show_new_dish_multi_popup(self):
        file_name = QFileDialog().getOpenFileName(None, "选择文件", "",
                                                  self.tr("CSV文件 (*.csv)"))[0]
        self.new_dish_multi_popup.tableWidget.setRowCount(0)
        if file_name:
            with open(file_name, "r") as file:
                csv_reader = csv.reader(file, delimiter=",")
                for idx, row_data in enumerate(csv_reader):
                    if len(row_data) == 2:
                        name, price = row_data
                        remark = ""
                    elif len(row_data) == 3:
                        name, price, remark = row_data
                    else:
                        QMessageBox.warning(
                            self, "格式错误",
                            self.tr('格式为"菜品 价格"或者"菜品 价格 备注"\n第{}行输入有误'.format(
                                idx)))
                        return
                    self.new_dish_multi_popup.tableWidget.insertRow(
                        self.new_dish_multi_popup.tableWidget.rowCount())
                    self.new_dish_multi_popup.tableWidget.setItem(
                        idx, 0, QTableWidgetItem(name))
                    price_type = str_type(price)
                    if price_type == str or (isinstance(
                            price_type, (float, int)) and float(price) < 0):
                        QMessageBox.warning(
                            self, "格式错误",
                            self.tr('第{}行价格输入有误'.format(idx + 1)))
                        return
                    self.new_dish_multi_popup.tableWidget.setItem(
                        idx, 1,
                        QTableWidgetItem("{:.2f}".format(float(price))))
                    self.new_dish_multi_popup.tableWidget.setItem(
                        idx, 2, QTableWidgetItem(remark))
            self.new_dish_multi_popup.show()

    def modify_new_dish_data_popup_table(self, *args, show=False):
        sql_select_query = """
                    SELECT id, name, price, dish_data.sell_num
                    FROM dish LEFT JOIN dish_data
                    ON dish.id=dish_data.dish_id
                    WHERE dish_data.date IS NULL OR dish_data.date = date('{}')
                    GROUP BY id, name, price
                    ORDER BY dish.name, dish.price;""".format(
            self.new_dish_data_popup.dateEdit.date().toString("yyyy-MM-dd"))

        cursor = self.db_connection.cursor()
        cursor.execute(sql_select_query)
        records = cursor.fetchall()
        self.new_dish_data_popup.tableWidget.setRowCount(len(records))
        self.new_dish_data_popup.tableWidget.setColumnHidden(0, True)
        for row_idx, record in enumerate(records):
            dish_id, name, price, sell_num = record
            self.new_dish_data_popup.tableWidget.setItem(
                row_idx, 0, QTableWidgetItem(str(dish_id)))
            self.new_dish_data_popup.tableWidget.setItem(
                row_idx, 1, QTableWidgetItem(name))
            self.new_dish_data_popup.tableWidget.setItem(
                row_idx, 2, QTableWidgetItem("{:.2f}".format(price)))
            spin_box = QSpinBox()
            spin_box.setMaximum(9999)
            spin_box.setValue(sell_num)
            self.new_dish_data_popup.tableWidget.setCellWidget(
                row_idx, 3, spin_box)
        cursor.close()
        if show:
            self.new_dish_data_popup.show()

    def create_new_dish(self):
        cursor = self.db_connection.cursor()
        sql_insert = """ INSERT INTO dish(name, price, remarks)
                         VALUES(?,?,?)"""
        dish_name = self.new_dish_popup.dish_name.text()
        dish_price = self.new_dish_popup.dish_price.value()
        dish_remark = self.new_dish_popup.dish_remark.toPlainText()
        try:
            cursor.execute(sql_insert, (dish_name, dish_price, dish_remark))
            new_dish_id = cursor.lastrowid
            cursor.close()
            self.db_connection.commit()
            # Update dish table and dish comboBox in UI
            self.dish_table_model.appendRow(
                create_dish_table_row(new_dish_id, dish_name, dish_price, 0,
                                      dish_remark))
            self.new_dish_popup.hide()
        except sqlite3.Error:
            cursor.close()
            QMessageBox.warning(self, "菜品价格重复", self.tr('菜品价格组合重复,请检查'))

    def create_new_dish_multi(self):
        cursor = self.db_connection.cursor()
        sql_insert = """ 
            INSERT INTO dish(name, price, remarks)     
            VALUES (?, ?, ?)"""
        for row in range(self.new_dish_multi_popup.tableWidget.rowCount()):
            dish_name = self.new_dish_multi_popup.tableWidget.item(row,
                                                                   0).text()
            dish_price = float(
                self.new_dish_multi_popup.tableWidget.item(row, 1).text())
            dish_remark = self.new_dish_multi_popup.tableWidget.item(row,
                                                                     2).text()
            try:
                cursor.execute(sql_insert,
                               (dish_name, dish_price, dish_remark))
                new_dish_id = cursor.lastrowid
                self.dish_table_model.appendRow(
                    create_dish_table_row(new_dish_id, dish_name, dish_price,
                                          0, dish_remark))
            except sqlite3.Error:
                cursor.close()
                QMessageBox.warning(
                    self, "菜品价格重复",
                    self.tr('前{}行已插入。\n第{}行菜品价格组合重复,请检查'.format(row, row + 1)))
                return
        cursor.close()
        self.db_connection.commit()
        self.new_dish_multi_popup.hide()

    def create_new_dish_data(self):
        current_date = self.new_dish_data_popup.dateEdit.date().toString(
            "yyyy-MM-dd")
        table_filter = TableFilter()
        table_filter.setSourceModel(self.dish_data_table_model)
        table_filter.set_col_regex_filter(1, current_date)
        for row in range(table_filter.rowCount()):
            index = table_filter.mapToSource(table_filter.index(0, 1))
            if index.isValid():
                self.dish_data_table_model.removeRow(index.row())
        del table_filter
        cursor = self.db_connection.cursor()
        sql_insert = """ 
            INSERT OR REPLACE INTO dish_data(dish_id, date, sell_num)     
            VALUES (?, ?, ?)"""
        for row in range(self.new_dish_data_popup.tableWidget.rowCount()):
            dish_id = int(
                self.new_dish_data_popup.tableWidget.item(row, 0).text())
            name = self.new_dish_data_popup.tableWidget.item(row, 1).text()
            price = float(
                self.new_dish_data_popup.tableWidget.item(row, 2).text())
            sell_num = self.new_dish_data_popup.tableWidget.cellWidget(
                row, 3).value()
            cursor.execute(sql_insert, (dish_id, current_date, sell_num))
            self.dish_data_table_model.appendRow(
                create_dish_data_table_row(dish_id, current_date, name, price,
                                           sell_num))
        cursor.close()
        self.db_connection.commit()
        self.new_dish_data_popup.hide()

    def delete_dish(self, dish_id):
        cursor = self.db_connection.cursor()
        sql_delete = """ DELETE FROM dish WHERE id=?"""
        cursor.execute(sql_delete, tuple([dish_id]))
        cursor.close()
        self.db_connection.commit()

        # Update dish table and dish comboBox in UI
        for row in self.dish_data_table_model.findItems(str(dish_id)):
            index = row.index()
            if index.isValid():
                self.dish_data_table_model.removeRow(index.row())

        for row in self.dish_table_model.findItems(str(dish_id)):
            index = row.index()
            if index.isValid():
                self.dish_table_model.removeRow(index.row())

    def show_modify_dish_popup(self, dish_id):
        point = self.rect().center()
        global_point = self.mapToGlobal(point)
        self.modify_dish_popup.move(
            global_point - QtCore.QPoint(self.modify_dish_popup.width() // 2,
                                         self.modify_dish_popup.height() // 2))
        # Find the row and get necessary info
        index = self.dish_table_model.match(self.dish_table_model.index(0, 0),
                                            Qt.DisplayRole, str(dish_id))
        if index:
            row_idx = index[0]
            dish_name = self.dish_table_model.data(row_idx.siblingAtColumn(1))
            dish_price = self.dish_table_model.data(row_idx.siblingAtColumn(2))
            dish_remark = self.dish_table_model.data(
                row_idx.siblingAtColumn(5))
            self.modify_dish_popup.dish_name.setText(dish_name)
            self.modify_dish_popup.dish_price.setValue(float(dish_price))
            self.modify_dish_popup.dish_remark.setText(dish_remark)

            try:
                self.modify_dish_popup.modify_dish_btn.clicked.disconnect()
            except TypeError:
                pass
            self.modify_dish_popup.modify_dish_btn.clicked.connect(
                lambda: self.modify_dish(row_idx, dish_id))
            self.modify_dish_popup.show()

    def modify_dish(self, row, dish_id):
        cursor = self.db_connection.cursor()
        sql_update = """ UPDATE dish
                         SET name = ?, price = ?, remarks = ?
                         WHERE id=?"""
        dish_name = self.modify_dish_popup.dish_name.text()
        dish_price = self.modify_dish_popup.dish_price.value()
        dish_remark = self.modify_dish_popup.dish_remark.toPlainText()
        cursor.execute(sql_update,
                       (dish_name, dish_price, dish_remark, dish_id))
        cursor.close()
        self.db_connection.commit()
        self.modify_dish_popup.hide()

        # Update dish table and dish comboBox in UI
        old_name = self.dish_table_model.data(row.siblingAtColumn(1))
        old_price = self.dish_table_model.data(row.siblingAtColumn(2))
        sell_num = self.dish_table_model.data(row.siblingAtColumn(3))
        row_idx = row.row()
        self.dish_table_model.removeRow(row_idx)
        self.dish_table_model.insertRow(
            row_idx,
            create_dish_table_row(dish_id, dish_name, dish_price, sell_num,
                                  dish_remark))

        for row in self.dish_data_table_model.findItems(str(dish_id)):
            index = row.index()
            if index.isValid():
                self.dish_data_table_model.setData(index.siblingAtColumn(2),
                                                   dish_name)
                self.dish_data_table_model.setData(index.siblingAtColumn(3),
                                                   "{:.2f}".format(dish_price))
        old_key = old_name + '(' + old_price + ')'
        if old_key in self.graph_line_series:
            self.graph_line_series[dish_name + '(' + str(dish_price) +
                                   ')'] = self.graph_line_series[old_key]
            del self.graph_line_series[old_key]

    def update_series(self, item: QStandardItem):
        if item.column() == 5:  # check for checkbox column
            item_idx = item.index()
            date = self.dish_data_table_model.data(item_idx.siblingAtColumn(1))
            dish_name = self.dish_data_table_model.data(
                item_idx.siblingAtColumn(2))
            dish_price = self.dish_data_table_model.data(
                item_idx.siblingAtColumn(3))
            sell_num = self.dish_data_table_model.data(
                item_idx.siblingAtColumn(4))
            set_name = dish_name + "(" + dish_price + ")"
            key = str(
                QDateTime(QDate.fromString(date,
                                           "yyyy-MM-dd")).toSecsSinceEpoch())
            if key not in self.graph_series:
                self.graph_series[key] = {}

            if int(item.text()) == 0:
                if set_name in self.graph_series[key]:
                    del self.graph_series[key][set_name]
                if not self.graph_series[key]:
                    del self.graph_series[key]
            else:
                self.graph_series[key][set_name] = int(sell_num)

    def update_graph(self, index):
        if index == 2:
            self.graph_chart.removeAllSeries()

            axis_x = QBarCategoryAxis()
            axis_x.setTitleText("日期")
            if self.graph_chart.axisX():
                self.graph_chart.removeAxis(self.graph_chart.axisX())
            self.graph_chart.addAxis(axis_x, Qt.AlignBottom)

            axis_y = QValueAxis()
            axis_y.setLabelFormat("%i")
            axis_y.setTitleText("售出量")
            if self.graph_chart.axisY():
                self.graph_chart.removeAxis(self.graph_chart.axisY())
            self.graph_chart.addAxis(axis_y, Qt.AlignLeft)

            max_num = 0
            total_date = 0
            set_dict = {}
            for key, data in sorted(self.graph_series.items(),
                                    key=lambda i: int(i[0])):
                axis_x.append(
                    QDateTime.fromSecsSinceEpoch(
                        int(key)).toString("yyyy年MM月dd日"))
                for set_name, value in data.items():
                    if set_name not in set_dict:
                        set_dict[set_name] = QBarSet(set_name)
                        for _ in range(total_date):
                            set_dict[set_name].append(0)
                    set_dict[set_name].append(value)
                    max_num = max(max_num, value)
                total_date += 1
                for _, bar_set in set_dict.items():
                    if bar_set.count() < total_date:
                        bar_set.append(0)
            bar_series = QBarSeries()
            for _, bar_set in set_dict.items():
                bar_series.append(bar_set)
            bar_series.hovered.connect(self.graph_tooltip)
            axis_y.setMax(max_num + 1)
            axis_y.setMin(0)
            self.graph_chart.addSeries(bar_series)
            bar_series.attachAxis(axis_x)
            bar_series.attachAxis(axis_y)

    def graph_tooltip(self, status, index, bar_set: QBarSet):
        if status:
            QToolTip.showText(
                QCursor.pos(),
                "{}\n日期: {}\n售出: {}".format(bar_set.label(),
                                            self.graph_chart.axisX().at(index),
                                            int(bar_set.at(index))))
Esempio n. 3
0
class IMUChartView(QChartView):

    aboutToClose = pyqtSignal(QObject)
    cursorMoved = pyqtSignal(datetime.datetime)

    def __init__(self, parent=None):
        super(QChartView, self).__init__(parent=parent)

        #self.setFixedHeight(400)
        #self.setMinimumHeight(500)
        """self.setMaximumHeight(700)
        self.setFixedHeight(700)
        self.setMinimumWidth(1500)
        self.setSizePolicy(QSizePolicy.Fixed,QSizePolicy.Fixed)"""

        self.reftime = datetime.datetime.now()
        self.cursor = QGraphicsLineItem()
        self.scene().addItem(self.cursor)
        self.decim_factor = 1

        # self.setScene(QGraphicsScene())
        self.chart = QChart()
        # self.scene().addItem(self.chart)
        self.setChart(self.chart)
        self.chart.legend().setVisible(True)
        self.chart.legend().setAlignment(Qt.AlignTop)
        self.ncurves = 0
        self.setRenderHint(QPainter.Antialiasing)
        self.setRubberBand(QChartView.HorizontalRubberBand)

        # X, Y label on bottom
        # self.xTextItem = QGraphicsSimpleTextItem(self.chart)
        # self.xTextItem.setText('X: ')
        # self.yTextItem = QGraphicsSimpleTextItem(self.chart)
        # self.yTextItem.setText('Y: ')
        # self.update_x_y_coords()

        # Track mouse
        self.setMouseTracking(True)

        # Top Widgets
        newWidget = QWidget(self)
        newLayout = QHBoxLayout()
        newLayout.setContentsMargins(0, 0, 0, 0)
        newWidget.setLayout(newLayout)
        #labelx = QLabel(self)
        #labelx.setText('X:')
        #self.labelXValue = QLabel(self)
        #labely = QLabel(self)
        #labely.setText('Y:')
        #self.labelYValue = QLabel(self)

        # Test buttons
        #newLayout.addWidget(QToolButton(self))
        #newLayout.addWidget(QToolButton(self))
        #newLayout.addWidget(QToolButton(self))

        # Spacer
        #newLayout.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding, QSizePolicy.Minimum))

        # Labels
        """newLayout.addWidget(labelx)
        newLayout.addWidget(self.labelXValue)
        self.labelXValue.setMinimumWidth(200)
        self.labelXValue.setMaximumWidth(200)
        newLayout.addWidget(labely)
        newLayout.addWidget(self.labelYValue)
        self.labelYValue.setMinimumWidth(200)
        self.labelYValue.setMaximumWidth(200)
        """
        """if parent is not None:
            parent.layout().setMenuBar(newWidget)
        """
        # self.layout()

        self.build_style()

    def build_style(self):
        self.setStyleSheet("QLabel{color:blue;}")

        self.setBackgroundBrush(QBrush(Qt.darkGray))
        self.chart.setPlotAreaBackgroundBrush(QBrush(Qt.black))
        self.chart.setPlotAreaBackgroundVisible(True)

    def save_as_png(self, file_path):
        pixmap = self.grab()

        child = self.findChild(QOpenGLWidget)

        painter = QPainter(pixmap)
        if child is not None:
            d = child.mapToGlobal(QPoint()) - self.mapToGlobal(QPoint())
            painter.setCompositionMode(QPainter.CompositionMode_SourceAtop)
            painter.drawImage(d, child.grabFramebuffer())

        painter.end()
        pixmap.save(file_path, 'PNG')

    def closeEvent(self, QCloseEvent):
        self.aboutToClose.emit(self)

    @pyqtSlot(QPointF)
    def lineseries_clicked(self, point):
        print('lineseries clicked', point)

    @pyqtSlot(QPointF)
    def lineseries_hovered(self, point):
        print('lineseries hovered', point)

    def update_x_y_coords(self):
        pass
        # self.xTextItem.setPos(self.chart.size().width() / 2 - 100, self.chart.size().height() - 40)
        # self.yTextItem.setPos(self.chart.size().width() / 2 + 100, self.chart.size().height() - 40)

    def decimate(self, xdata, ydata):
        assert (len(xdata) == len(ydata))

        # Decimate only if we have too much data
        decimate_factor = len(xdata) / 100000.0

        if decimate_factor > 1.0:
            decimate_factor = int(np.floor(decimate_factor))
            #print('decimate factor', decimate_factor)
            # x = decimate(xdata, decimate_factor)
            # y = decimate(ydata, decimate_factor)
            self.decim_factor = decimate_factor
            x = np.ndarray(int(len(xdata) / decimate_factor), dtype=np.float64)
            y = np.ndarray(int(len(ydata) / decimate_factor), dtype=np.float64)
            for i in range(len(x)):
                index = i * decimate_factor
                assert (index < len(xdata))
                x[i] = xdata[index]
                y[i] = ydata[index]
                if x[i] < x[0]:
                    print('timestamp error', x[i], x[0])

            #print('return size', len(x), len(y), 'timestamp', x[0])
            return x, y
        else:
            return xdata, ydata

    @pyqtSlot(float, float)
    def axis_range_changed(self, min, max):
        #print('axis_range_changed', min, max)
        for axis in self.chart.axes():
            axis.applyNiceNumbers()

    def update_axes(self):

        # Get and remove all axes
        for axis in self.chart.axes():
            self.chart.removeAxis(axis)

        # Create new axes
        # Create axis X
        # axisX = QDateTimeAxis()
        # axisX.setTickCount(5)
        # axisX.setFormat("dd MMM yyyy")
        # axisX.setTitleText("Date")
        # self.chart.addAxis(axisX, Qt.AlignBottom)
        # axisX.rangeChanged.connect(self.axis_range_changed)

        axisX = QValueAxis()
        axisX.setTickCount(10)
        axisX.setLabelFormat("%li")
        axisX.setTitleText("Seconds")
        self.chart.addAxis(axisX, Qt.AlignBottom)
        # axisX.rangeChanged.connect(self.axis_range_changed)

        # Create axis Y
        axisY = QValueAxis()
        axisY.setTickCount(5)
        axisY.setLabelFormat("%.3f")
        axisY.setTitleText("Values")
        self.chart.addAxis(axisY, Qt.AlignLeft)
        # axisY.rangeChanged.connect(self.axis_range_changed)

        ymin = None
        ymax = None

        # Attach axes to series, find min-max
        for series in self.chart.series():
            series.attachAxis(axisX)
            series.attachAxis(axisY)
            vect = series.pointsVector()
            for i in range(len(vect)):
                if ymin is None:
                    ymin = vect[i].y()
                    ymax = vect[i].y()
                else:
                    ymin = min(ymin, vect[i].y())
                    ymax = max(ymax, vect[i].y())

        # Update range
        # print('min max', ymin, ymax)
        if ymin is not None:
            axisY.setRange(ymin, ymax)

        # Make the X,Y axis more readable
        axisX.applyNiceNumbers()
        # axisY.applyNiceNumbers()

    def add_data(self, xdata, ydata, color=None, legend_text=None):
        curve = QLineSeries()
        pen = curve.pen()
        if color is not None:
            pen.setColor(color)
        pen.setWidthF(1.5)
        curve.setPen(pen)

        #curve.setUseOpenGL(True)

        # Decimate
        xdecimated, ydecimated = self.decimate(xdata, ydata)

        # Data must be in ms since epoch
        # curve.append(self.series_to_polyline(xdecimated * 1000.0, ydecimated))
        for i in range(len(xdecimated)):
            # TODO hack
            x = xdecimated[i] - xdecimated[0]
            curve.append(QPointF(x, ydecimated[i]))

        self.reftime = datetime.datetime.fromtimestamp(xdecimated[0])

        if legend_text is not None:
            curve.setName(legend_text)

        # Needed for mouse events on series
        self.chart.setAcceptHoverEvents(True)

        # connect signals / slots
        # curve.clicked.connect(self.lineseries_clicked)
        # curve.hovered.connect(self.lineseries_hovered)

        # Add series
        self.chart.addSeries(curve)
        self.ncurves += 1
        self.update_axes()

    def set_title(self, title):
        # print('Setting title: ', title)
        #self.chart.setTitle(title)
        pass

    def series_to_polyline(self, xdata, ydata):
        """Convert series data to QPolygon(F) polyline

        This code is derived from PythonQwt's function named
        `qwt.plot_curve.series_to_polyline`"""

        # print('series_to_polyline types:', type(xdata[0]), type(ydata[0]))
        size = len(xdata)
        polyline = QPolygonF(size)

        for i in range(0, len(xdata)):
            polyline[i] = QPointF(xdata[i] - xdata[0], ydata[i])

        # pointer = polyline.data()
        # dtype, tinfo = np.float, np.finfo  # integers: = np.int, np.iinfo
        # pointer.setsize(2*polyline.size()*tinfo(dtype).dtype.itemsize)
        # memory = np.frombuffer(pointer, dtype)
        # memory[:(size-1)*2+1:2] = xdata
        # memory[1:(size-1)*2+2:2] = ydata
        return polyline

    def add_test_data(self):

        # 100Hz, one day accelerometer values
        npoints = 1000 * 60 * 24

        xdata = np.linspace(0., 10., npoints)
        self.add_data(xdata, np.sin(xdata), color=Qt.red, legend_text='Acc. X')
        # self.add_data(xdata, np.cos(xdata), color=Qt.green, legend_text='Acc. Y')
        # self.add_data(xdata, np.cos(2 * xdata), color=Qt.blue, legend_text='Acc. Z')
        self.set_title("Simple example with %d curves of %d points " \
                          "(OpenGL Accelerated Series)" \
                          % (self.ncurves, npoints))

    def mouseMoveEvent(self, e: QMouseEvent):
        # Handling rubberbands
        super().mouseMoveEvent(e)

        # Go back to seconds (instead of ms)
        """xmap = self.chart.mapToValue(e.pos()).x()
        ymap = self.chart.mapToValue(e.pos()).y()

        self.labelXValue.setText(str(datetime.datetime.fromtimestamp(xmap + self.reftime.timestamp())))
        self.labelYValue.setText(str(ymap))"""

        # self.xTextItem.setText('X: ' + str(datetime.datetime.fromtimestamp(xmap + self.reftime.timestamp())))
        # self.yTextItem.setText('Y: ' + str(ymap))

    def mousePressEvent(self, e: QMouseEvent):
        # Handling rubberbands
        super().mousePressEvent(e)

        self.setCursorPosition(e.pos().x(), True)

        pass

    def setCursorPosition(self, pos, emit_signal=False):
        # print (pos)
        pen = self.cursor.pen()
        pen.setColor(Qt.cyan)
        pen.setWidthF(1.0)
        self.cursor.setPen(pen)
        # On Top
        self.cursor.setZValue(100.0)

        area = self.chart.plotArea()
        x = pos
        y1 = area.y()
        y2 = area.y() + area.height()

        # self.cursor.set
        self.cursor.setLine(x, y1, x, y2)
        self.cursor.show()

        xmap = self.chart.mapToValue(QPointF(pos, 0)).x()
        ymap = self.chart.mapToValue(QPointF(pos, 0)).y()

        #self.labelXValue.setText(str(datetime.datetime.fromtimestamp(xmap + self.reftime.timestamp())))
        #self.labelYValue.setText(str(ymap))

        if emit_signal:
            self.cursorMoved.emit(
                datetime.datetime.fromtimestamp(xmap +
                                                self.reftime.timestamp()))

        self.update()

    def setCursorPositionFromTime(self, timestamp, emit_signal=False):
        # Converts timestamp to x value
        pos = self.chart.mapToPosition(
            QPointF((timestamp - self.reftime).total_seconds(), 0)).x()
        self.setCursorPosition(pos, emit_signal)

    def mouseReleaseEvent(self, e: QMouseEvent):
        # Handling rubberbands
        super().mouseReleaseEvent(e)
        pass

    def resizeEvent(self, e: QResizeEvent):
        super().resizeEvent(e)

        # Update cursor height
        area = self.chart.plotArea()
        line = self.cursor.line()
        self.cursor.setLine(line.x1(), area.y(), line.x2(),
                            area.y() + area.height())

        # self.scene().setSceneRect(0, 0, e.size().width(), e.size().height())
        # Need to reposition X,Y labels
        self.update_x_y_coords()
Esempio n. 4
0
class MyWidget(QWidget):
    def __init__(self):
        super().__init__()
        uic.loadUi('ws_ui.ui', self)
        self.con = sqlite3.connect("ws_database.db")
        self.setWindowFlags(QtCore.Qt.FramelessWindowHint)

        self.create_linechart()
        self.tmp, self.hmd, self.prs = 0, 0, 0
        self.make_measure()

        self.measure_timer = QTimer(self)
        self.measure_timer.setInterval(MEASURE_FREQUENCIES * 1000)
        self.measure_timer.timeout.connect(self.make_measure)
        self.measure_timer.start()

        self.update_timer = QTimer(self)
        self.update_timer.setInterval(1000)
        self.update_timer.timeout.connect(self.update_labels)
        self.update_timer.start()

        self.update_labels()

    def quit(self):
        self.destroy()
        quit()

    def make_measure(self):
        sns = sensor_measure()

        if sns[0] != ERROR_CODE:
            self.tmp = sns[0]

        else:
            print('tmp error')

        if sns[1] != ERROR_CODE:
            self.hmd = sns[1]

        else:
            print('hmd error')

        if sns[2] != ERROR_CODE:
            self.prs = sns[2]

        else:
            print('prs error')

        time = int(dt.datetime.now().timestamp())

        req = """
              INSERT INTO short_term_data(tmp, hmd, prs, time_from_epoch)
              VALUES(?,?,?,?)
              """

        self.con.execute(req, (self.tmp, self.hmd, self.prs, time))
        self.con.commit()

        self.update_linechart()

    def update_labels(self):
        deg = u'\N{DEGREE SIGN}'
        hpa = 'ʰᴾᵃ'
        self.time_label.setText(dt.datetime.now().strftime('%H:%M'))
        self.tmp_label.setText('{} {}C'.format(self.tmp, deg))
        self.hmd_label.setText('{} %'.format(self.hmd))
        self.prs_label.setText('{} {}'.format(self.prs, hpa))

    def create_linechart(self):
        self.chart = QChart()
        self.chart.legend().hide()

        self.series = QLineSeries()

        self.axisValue = QValueAxis()
        self.axisCurrentTime = QValueAxis()
        self.axisTime = QDateTimeAxis()
        self.axisTime.setFormat("hh:mm")

        self.chartview = QChartView(self.chart, self.groupBox)
        self.chartview.resize(540, 460)
        self.chartview.move(0, 0)
        self.chartview.setRenderHint(QPainter.Antialiasing)

    def update_linechart(self):
        if self.axisTime in self.chart.axes():
            self.chart.removeAxis(self.axisTime)

        if self.axisCurrentTime in self.chart.axes():
            self.chart.removeAxis(self.axisCurrentTime)

        if self.axisValue in self.chart.axes():
            self.chart.removeAxis(self.axisValue)

        if self.series in self.chart.series():
            self.chart.removeSeries(self.series)

        self.series.clear()

        self.axisValue.setMax(50)
        self.axisValue.setMin(-50)

        req = """
              SELECT tmp, time_from_epoch
              FROM short_term_data
              WHERE (time_from_epoch - ?) < 86400 AND NOT tmp = ?
              """

        cur = self.con.cursor()
        result = list(cur.execute(req, (int(dt.datetime.now().timestamp()), ERROR_CODE)))

        for measure in result:
            self.series.append(measure[1] * 1000, measure[0])

        self.axisTime.setMin(QDateTime.fromMSecsSinceEpoch(int(dt.datetime.now().timestamp()) * 1000 - 86390000))
        self.axisTime.setMax(QDateTime.fromMSecsSinceEpoch(int(dt.datetime.now().timestamp()) * 1000))

        self.chart.addSeries(self.series)
        self.chart.addAxis(self.axisTime, Qt.AlignBottom)
        self.series.attachAxis(self.axisTime)
        self.chart.addAxis(self.axisValue, Qt.AlignLeft)
        self.series.attachAxis(self.axisValue)

        self.chart.setTitle('Температура')