Пример #1
0
class ChartView(QChartView):

    def __init__(self, *args, **kwargs):
        super(ChartView, self).__init__(*args, **kwargs)
        self.resize(800, 600)
        self.setRenderHint(QPainter.Antialiasing)  # 抗锯齿
        self.initChart()

        self.toolTipWidget = GraphicsProxyWidget(self._chart)

        # line
        self.lineItem = QGraphicsLineItem(self._chart)
        self.lineItem.setZValue(998)
        self.lineItem.hide()
        
        # 一些固定计算,减少mouseMoveEvent中的计算量
        # 获取x和y轴的最小最大值
        axisX, axisY = self._chart.axisX(), self._chart.axisY()
        self.min_x, self.max_x = axisX.min(), axisX.max()
        self.min_y, self.max_y = axisY.min(), axisY.max()
        # 坐标系中左上角顶点
        self.point_top = self._chart.mapToPosition(QPointF(self.min_x, self.max_y))
        # 坐标原点坐标
        self.point_bottom = self._chart.mapToPosition(QPointF(self.min_x, self.min_y))
        self.step_x = (self.max_x - self.min_x) / (axisX.tickCount() - 1)
#         self.step_y = (self.max_y - self.min_y) / (axisY.tickCount() - 1)

    def mouseMoveEvent(self, event):
        super(ChartView, self).mouseMoveEvent(event)
        # 把鼠标位置所在点转换为对应的xy值
        x = self._chart.mapToValue(event.pos()).x()
        y = self._chart.mapToValue(event.pos()).y()
        index = round((x - self.min_x) / self.step_x)
        pos_x = self._chart.mapToPosition(
            QPointF(index * self.step_x + self.min_x, self.min_y))
#         print(x, pos_x, index, index * self.step_x + self.min_x)
        # 得到在坐标系中的所有series的类型和点
        points = [(serie, serie.at(index))
                  for serie in self._chart.series() if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y]
        if points:
            # 永远在轴上的黑线条
            self.lineItem.setLine(pos_x.x(), self.point_top.y(),
                                  pos_x.x(), self.point_bottom.y())
            self.lineItem.show()
            self.toolTipWidget.show("", points, event.pos() + QPoint(20, 20))
        else:
            self.toolTipWidget.hide()
            self.lineItem.hide()

    def onSeriesHoverd(self, point, state):
        if state:
            try:
                name = self.sender().name()
            except:
                name = ""
            QToolTip.showText(QCursor.pos(), "%s\nx: %s\ny: %s" % 
                              (name, point.x(), point.y()))

    def initChart(self):
        self._chart = QChart(title="Line Chart")
        self._chart.setAcceptHoverEvents(True)
        dataTable = [
            [120, 132, 101, 134, 90, 230, 210],
            [220, 182, 191, 234, 290, 330, 310],
            [150, 232, 201, 154, 190, 330, 410],
            [320, 332, 301, 334, 390, 330, 320],
            [820, 932, 901, 934, 1290, 1330, 1320]
        ]
        for i, data_list in enumerate(dataTable):
            series = QLineSeries(self._chart)
            for j, v in enumerate(data_list):
                series.append(j, v)
            series.setName("Series " + str(i))
            series.setPointsVisible(True)  # 显示原点
            series.hovered.connect(self.onSeriesHoverd)
            self._chart.addSeries(series)
        self._chart.createDefaultAxes()  # 创建默认的轴
        self._chart.axisX().setTickCount(7)  # x轴设置7个刻度
        self._chart.axisY().setTickCount(7)  # y轴设置7个刻度
        self._chart.axisY().setRange(0, 1500)  # 设置y轴范围
        self.setChart(self._chart)
Пример #2
0
class ChartView(QChartView):

    def __init__(self, *args, **kwargs):
        super(ChartView, self).__init__(*args, **kwargs)
        self.resize(800, 600)
        self.setRenderHint(QPainter.Antialiasing)  # 抗锯齿
        self.initChart()

        # 提示widget
        self.toolTipWidget = GraphicsProxyWidget(self._chart)

        # line 宽度需要调整
        self.lineItem = QGraphicsLineItem(self._chart)
        pen = QPen(Qt.gray)
        self.lineItem.setPen(pen)
        self.lineItem.setZValue(998)
        self.lineItem.hide()

        # 一些固定计算,减少mouseMoveEvent中的计算量
        # 获取x和y轴的最小最大值
        axisX, axisY = self._chart.axisX(), self._chart.axisY()
        self.category_len = len(axisX.categories())
        self.min_x, self.max_x = -0.5, self.category_len - 0.5
        self.min_y, self.max_y = axisY.min(), axisY.max()
        # 坐标系中左上角顶点
        self.point_top = self._chart.mapToPosition(
            QPointF(self.min_x, self.max_y))

    def mouseMoveEvent(self, event):
        super(ChartView, self).mouseMoveEvent(event)
        pos = event.pos()
        # 把鼠标位置所在点转换为对应的xy值
        x = self._chart.mapToValue(pos).x()
        y = self._chart.mapToValue(pos).y()
        index = round(x)
        # 得到在坐标系中的所有bar的类型和点
        serie = self._chart.series()[0]
        bars = [(bar, bar.at(index))
                for bar in serie.barSets() if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y]
        if bars:
            right_top = self._chart.mapToPosition(
                QPointF(self.max_x, self.max_y))
            # 等分距离比例
            step_x = round(
                (right_top.x() - self.point_top.x()) / self.category_len)
            posx = self._chart.mapToPosition(QPointF(x, self.min_y))
            self.lineItem.setLine(posx.x(), self.point_top.y(),
                                  posx.x(), posx.y())
            self.lineItem.show()
            try:
                title = self.categories[index]
            except:
                title = ""
            t_width = self.toolTipWidget.width()
            t_height = self.toolTipWidget.height()
            # 如果鼠标位置离右侧的距离小于tip宽度
            x = pos.x() - t_width if self.width() - \
                pos.x() - 20 < t_width else pos.x()
            # 如果鼠标位置离底部的高度小于tip高度
            y = pos.y() - t_height if self.height() - \
                pos.y() - 20 < t_height else pos.y()
            self.toolTipWidget.show(
                title, bars, QPoint(x, y))
        else:
            self.toolTipWidget.hide()
            self.lineItem.hide()

    def handleMarkerClicked(self):
        marker = self.sender()  # 信号发送者
        if not marker:
            return
        bar = marker.barset()
        if not bar:
            return
        # bar透明度
        brush = bar.brush()
        color = brush.color()
        alpha = 0.0 if color.alphaF() == 1.0 else 1.0
        color.setAlphaF(alpha)
        brush.setColor(color)
        bar.setBrush(brush)
        # marker
        brush = marker.labelBrush()
        color = brush.color()
        alpha = 0.4 if color.alphaF() == 1.0 else 1.0
        # 设置label的透明度
        color.setAlphaF(alpha)
        brush.setColor(color)
        marker.setLabelBrush(brush)
        # 设置marker的透明度
        brush = marker.brush()
        color = brush.color()
        color.setAlphaF(alpha)
        brush.setColor(color)
        marker.setBrush(brush)

    def handleMarkerHovered(self, status):
        # 设置bar的画笔宽度
        marker = self.sender()  # 信号发送者
        if not marker:
            return
        bar = marker.barset()
        if not bar:
            return
        pen = bar.pen()
        if not pen:
            return
        pen.setWidth(pen.width() + (1 if status else -1))
        bar.setPen(pen)

    def handleBarHoverd(self, status, index):
        # 设置bar的画笔宽度
        bar = self.sender()  # 信号发送者
        pen = bar.pen()
        if not pen:
            return
        pen.setWidth(pen.width() + (1 if status else -1))
        bar.setPen(pen)

    def initChart(self):
        self._chart = QChart(title="柱状图堆叠")
        self._chart.setAcceptHoverEvents(True)
        # Series动画
        self._chart.setAnimationOptions(QChart.SeriesAnimations)
        self.categories = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
        # names = ["邮件营销", "联盟广告", "视频广告", "直接访问", "搜索引擎"]
        names = ["邮件营销", ]
        series = QBarSeries(self._chart)
        for name in names:
            bar = QBarSet(name)
            # 随机数据
            for _ in range(7):
                bar.append(randint(0, 10))
                
            series.append(bar)
            bar.hovered.connect(self.handleBarHoverd)  # 鼠标悬停
        self._chart.addSeries(series)
        self._chart.createDefaultAxes()  # 创建默认的轴
        # x轴
        axis_x = QBarCategoryAxis(self._chart)
        axis_x.append(self.categories)
        self._chart.setAxisX(axis_x, series)
        # chart的图例
        legend = self._chart.legend()
        legend.setVisible(True)
        # 遍历图例上的标记并绑定信号
        for marker in legend.markers():
            # 点击事件
            marker.clicked.connect(self.handleMarkerClicked)
            # 鼠标悬停事件
            marker.hovered.connect(self.handleMarkerHovered)
        self.setChart(self._chart)
Пример #3
0
class ImageViewer(QGraphicsView, QObject):
    points_selection_sgn = pyqtSignal(list)
    key_press_sgn = pyqtSignal(QtGui.QKeyEvent)

    def __init__(self, parent=None):
        super(ImageViewer, self).__init__(parent)
        self.setDragMode(QGraphicsView.ScrollHandDrag)
        self.setRenderHints(QPainter.Antialiasing
                            | QPainter.SmoothPixmapTransform)
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)

        self._scene = ImageViewerScene(self)
        self.setScene(self._scene)
        self._image = None
        self._image_original = None
        self._pixmap = None
        self._img_contrast = 1.0
        self._img_brightness = 50.0
        self._img_gamma = 1.0
        self._create_grid()
        self._channels = []
        self._current_tool = SELECTION_TOOL.POINTER
        self._dataset = None

        # create grid lines
        pen_color = QColor(255, 255, 255, 255)
        pen = QPen(pen_color)
        pen.setWidth(2)
        pen.setStyle(QtCore.Qt.DotLine)
        self.vline = QGraphicsLineItem()
        self.vline.setVisible(False)
        self.vline.setPen(pen)
        self.hline = QGraphicsLineItem()
        self.hline.setVisible(False)
        self.hline.setPen(pen)
        self._scene.addItem(self.vline)
        self._scene.addItem(self.hline)
        self._current_label = None

        # rectangle selection tool
        self._rectangle_tool_origin = QPoint()
        self._rectangle_tool_picker = QRubberBand(QRubberBand.Rectangle, self)

        # polygon selection tool
        app = QApplication.instance()
        color = app.palette().color(QPalette.Highlight)
        self._polygon_guide_line_pen = QPen(color)
        self._polygon_guide_line_pen.setWidth(3)
        self._polygon_guide_line_pen.setStyle(QtCore.Qt.DotLine)
        self._polygon_guide_line = QGraphicsLineItem()
        self._polygon_guide_line.setVisible(False)
        self._polygon_guide_line.setPen(self._polygon_guide_line_pen)
        self._scene.addItem(self._polygon_guide_line)
        self._current_polygon = None

        # circle
        self._current_ellipse = None

        # free selection tool
        self._current_free_path = None
        self._is_drawing = False
        self._last_point_drawn = QPoint()
        self._last_click_point = None
        self._free_Path_pen = QPen(color)
        self._free_Path_pen.setWidth(10)

        self._extreme_points = Queue(maxsize=4)

    @property
    def current_label(self):
        return self._current_label

    @current_label.setter
    def current_label(self, value):
        self._current_label = value
        if self._current_label:
            color = QColor(self._current_label.color)
            self._free_Path_pen.setColor(color)
            self._polygon_guide_line_pen.setColor(color)
            self._polygon_guide_line.setPen(self._polygon_guide_line_pen)

    @property
    def dataset(self):
        return self._dataset

    @dataset.setter
    def dataset(self, value):
        self._dataset = value

    @property
    def img_contrast(self):
        return self._img_contrast

    @img_contrast.setter
    def img_contrast(self, value):
        self._img_contrast = value

    @property
    def img_gamma(self):
        return self._img_gamma

    @img_gamma.setter
    def img_gamma(self, value):
        self._img_gamma = value

    @property
    def img_brightness(self):
        return self._img_brightness

    @img_brightness.setter
    def img_brightness(self, value):
        self._img_brightness = value

    @property
    def image(self):
        return self._image

    @image.setter
    def image(self, value):
        self._image = value
        self._image_original = value.copy()
        self.update_viewer()

    @property
    def pixmap(self) -> ImagePixmap:
        return self._pixmap

    @gui_exception
    def update_viewer(self, fit_image=True):
        rgb = cv2.cvtColor(self._image, cv2.COLOR_BGR2RGB)
        rgb = ImageUtilities.adjust_image(rgb, self._img_contrast,
                                          self._img_brightness)
        rgb = ImageUtilities.adjust_gamma(rgb, self._img_gamma)
        pil_image = Image.fromarray(rgb)
        qppixmap_image = pil_image.toqpixmap()
        x, y = -qppixmap_image.width() / 2, -qppixmap_image.height() / 2

        if self._pixmap:
            self._pixmap.resetTransform()
            self._pixmap.setPixmap(qppixmap_image)
            self._pixmap.setOffset(x, y)
        else:
            self._pixmap = ImagePixmap()
            self._pixmap.setPixmap(qppixmap_image)
            self._pixmap.setOffset(x, y)
            self._scene.addItem(self._pixmap)
            self._pixmap.signals.hoverEnterEventSgn.connect(
                self.pixmap_hoverEnterEvent_slot)
            self._pixmap.signals.hoverLeaveEventSgn.connect(
                self.pixmap_hoverLeaveEvent_slot)
            self._pixmap.signals.hoverMoveEventSgn.connect(
                self.pixmap_hoverMoveEvent_slot)
        self._hide_guide_lines()
        if fit_image:
            self.fit_to_window()

    @gui_exception
    def reset_viewer(self):
        self._img_contrast = 1.0
        self._img_brightness = 50.0
        self._img_gamma = 1.0
        self._image = self._image_original.copy()

    @gui_exception
    def equalize_histogram(self):
        self._image = ImageUtilities.histogram_equalization(self._image)

    @gui_exception
    def correct_lightness(self):
        self._image = ImageUtilities.correct_lightness(self._image)

    def clusterize(self, k):
        self._image = ImageUtilities.kmeans(self._image.copy(), k)

    @property
    def current_tool(self):
        return self._current_tool

    @current_tool.setter
    def current_tool(self, value):
        self._polygon_guide_line.hide()
        self._current_polygon = None
        self._current_free_path = None
        self._current_ellipse = None
        self._is_drawing = value == SELECTION_TOOL.FREE
        self._current_tool = value
        self.clear_extreme_points()
        if value == SELECTION_TOOL.POINTER:
            self.enable_items(True)
        else:
            self.enable_items(False)

    def fit_to_window(self):
        if not self._pixmap or not self._pixmap.pixmap():
            return
        self.resetTransform()
        self.setTransform(QtGui.QTransform())
        self.fitInView(self._pixmap, QtCore.Qt.KeepAspectRatio)

    def _create_grid(self, gridSize=15):
        app: QApplication = QApplication.instance()
        curr_theme = "dark"
        if app:
            curr_theme = app.property("theme")
        if curr_theme == "light":
            color1 = QtGui.QColor("white")
            color2 = QtGui.QColor(237, 237, 237)
        else:
            color1 = QtGui.QColor(20, 20, 20)
            color2 = QtGui.QColor(0, 0, 0)
        backgroundPixmap = QtGui.QPixmap(gridSize * 2, gridSize * 2)
        backgroundPixmap.fill(color1)
        painter = QtGui.QPainter(backgroundPixmap)
        painter.fillRect(0, 0, gridSize, gridSize, color2)
        painter.fillRect(gridSize, gridSize, gridSize, gridSize, color2)
        painter.end()
        self._scene.setBackgroundBrush(QtGui.QBrush(backgroundPixmap))

    def wheelEvent(self, event: QWheelEvent):
        adj = (event.angleDelta().y() / 120) * 0.1
        self.scale(1 + adj, 1 + adj)

    @gui_exception
    def keyPressEvent(self, event: QKeyEvent):
        if event.key() == QtCore.Qt.Key_Space:
            image_rect: QRectF = self._pixmap.sceneBoundingRect()
            if self.current_tool == SELECTION_TOOL.POLYGON and self._current_polygon:
                points = self._current_polygon.points
                self._polygon_guide_line.hide()
                self.setDragMode(QGraphicsView.ScrollHandDrag)
                if len(points) <= 2:
                    self._current_polygon.delete_item()
                self.current_tool = SELECTION_TOOL.POINTER
            elif self.current_tool == SELECTION_TOOL.EXTREME_POINTS and \
                    self._extreme_points.full():
                points = []
                image_offset = QPointF(image_rect.width() / 2,
                                       image_rect.height() / 2)
                for pt in self._extreme_points.queue:
                    pt: EditablePolygonPoint
                    center = pt.sceneBoundingRect().center()
                    x = math.floor(center.x() + image_offset.x())
                    y = math.floor(center.y() + image_offset.y())
                    points.append([x, y])
                self.points_selection_sgn.emit(points)
                self.current_tool = SELECTION_TOOL.POINTER
        else:
            event.ignore()

    # guide lines events
    def _show_guide_lines(self):
        if self.hline and self.vline:
            self.hline.show()
            self.vline.show()

    def _hide_guide_lines(self):
        if self.hline and self.vline:
            self.hline.hide()
            self.vline.hide()

    def _update_guide_lines(self, x, y):
        bbox: QRect = self._pixmap.boundingRect()
        offset = QPointF(bbox.width() / 2, bbox.height() / 2)
        self.vline.setLine(x, -offset.y(), x, bbox.height() - offset.y())
        self.vline.setZValue(1)
        self.hline.setLine(-offset.x(), y, bbox.width() - offset.x(), y)
        self.hline.setZValue(1)

    def pixmap_hoverMoveEvent_slot(self, evt: QGraphicsSceneHoverEvent, x, y):
        self._update_guide_lines(x, y)

    def pixmap_hoverEnterEvent_slot(self):
        self._show_guide_lines()

    def pixmap_hoverLeaveEvent_slot(self):
        self._hide_guide_lines()

    def delete_polygon_slot(self, polygon: EditablePolygon):
        self._current_polygon = None
        self.current_tool = SELECTION_TOOL.POINTER
        self._polygon_guide_line.hide()

    @gui_exception
    def mousePressEvent(self, evt: QtGui.QMouseEvent) -> None:
        image_rect: QRectF = self._pixmap.boundingRect()
        mouse_pos = self.mapToScene(evt.pos())
        if evt.buttons() == QtCore.Qt.LeftButton:
            if self.current_tool == SELECTION_TOOL.BOX:
                # create rectangle
                self.setDragMode(QGraphicsView.NoDrag)
                self._rectangle_tool_origin = evt.pos()
                geometry = QRect(self._rectangle_tool_origin, QSize())
                self._rectangle_tool_picker.setGeometry(geometry)
                self._rectangle_tool_picker.show()
            elif self.current_tool == SELECTION_TOOL.POLYGON:
                if image_rect.contains(mouse_pos):
                    if self._current_polygon is None:
                        self._current_polygon = EditablePolygon()
                        self._current_polygon.label = self._current_label
                        self._current_polygon.tag = self._dataset
                        self._current_polygon.signals.deleted.connect(
                            self.delete_polygon_slot)
                        self._scene.addItem(self._current_polygon)
                        self._current_polygon.addPoint(mouse_pos)
                    else:
                        self._current_polygon.addPoint(mouse_pos)
            elif self.current_tool == SELECTION_TOOL.ELLIPSE:
                if image_rect.contains(mouse_pos):
                    self.setDragMode(QGraphicsView.NoDrag)
                    ellipse_rec = QtCore.QRectF(mouse_pos.x(), mouse_pos.y(),
                                                0, 0)
                    self._current_ellipse = EditableEllipse()
                    self._current_ellipse.tag = self.dataset
                    self._current_ellipse.label = self._current_label
                    self._current_ellipse.setRect(ellipse_rec)
                    self._scene.addItem(self._current_ellipse)

            elif self.current_tool == SELECTION_TOOL.FREE:
                # consider only the points into the image
                if image_rect.contains(mouse_pos):
                    self.setDragMode(QGraphicsView.NoDrag)
                    self._last_point_drawn = mouse_pos
                    self._current_free_path = QGraphicsPathItem()
                    self._current_free_path.setOpacity(0.6)
                    self._current_free_path.setPen(self._free_Path_pen)
                    painter = QPainterPath()
                    painter.moveTo(self._last_point_drawn)
                    self._current_free_path.setPath(painter)
                    self._scene.addItem(self._current_free_path)

            elif self.current_tool == SELECTION_TOOL.EXTREME_POINTS:
                if image_rect.contains(mouse_pos):
                    if not self._extreme_points.full():

                        def delete_point(idx):
                            del self._extreme_points.queue[idx]

                        idx = self._extreme_points.qsize()
                        editable_pt = EditablePolygonPoint(idx)
                        editable_pt.signals.deleted.connect(delete_point)
                        editable_pt.setPos(mouse_pos)
                        self._scene.addItem(editable_pt)
                        self._extreme_points.put(editable_pt)

        else:
            self.setDragMode(QGraphicsView.ScrollHandDrag)
        super(ImageViewer, self).mousePressEvent(evt)

    @gui_exception
    def mouseMoveEvent(self, evt: QtGui.QMouseEvent) -> None:
        mouse_pos = self.mapToScene(evt.pos())
        image_rect: QRectF = self._pixmap.boundingRect()
        if self.current_tool == SELECTION_TOOL.BOX:
            if not self._rectangle_tool_origin.isNull():
                geometry = QRect(self._rectangle_tool_origin,
                                 evt.pos()).normalized()
                self._rectangle_tool_picker.setGeometry(geometry)
        elif self.current_tool == SELECTION_TOOL.POLYGON:
            if self._current_polygon and image_rect.contains(mouse_pos):
                if self._current_polygon.count > 0:
                    last_point: QPointF = self._current_polygon.last_point
                    self._polygon_guide_line.setZValue(1)
                    self._polygon_guide_line.show()
                    mouse_pos = self.mapToScene(evt.pos())
                    self._polygon_guide_line.setLine(last_point.x(),
                                                     last_point.y(),
                                                     mouse_pos.x(),
                                                     mouse_pos.y())
            else:
                self._polygon_guide_line.hide()
        elif self.current_tool == SELECTION_TOOL.ELLIPSE:
            if self._current_ellipse and image_rect.contains(mouse_pos):
                ellipse_rect = self._current_ellipse.rect()
                ellipse_pos = QPointF(ellipse_rect.x(), ellipse_rect.y())
                distance = math.hypot(mouse_pos.x() - ellipse_pos.x(),
                                      mouse_pos.y() - ellipse_pos.y())
                ellipse_rect.setWidth(distance)
                ellipse_rect.setHeight(distance)
                self._current_ellipse.setRect(ellipse_rect)
        elif self.current_tool == SELECTION_TOOL.FREE and evt.buttons(
        ) and QtCore.Qt.LeftButton:
            if self._current_free_path and image_rect.contains(mouse_pos):
                painter: QPainterPath = self._current_free_path.path()
                self._last_point_drawn = self.mapToScene(evt.pos())
                painter.lineTo(self._last_point_drawn)
                self._current_free_path.setPath(painter)
        super(ImageViewer, self).mouseMoveEvent(evt)

    @gui_exception
    def mouseReleaseEvent(self, evt: QtGui.QMouseEvent) -> None:
        image_rect: QRectF = self._pixmap.boundingRect()
        if self.current_tool == SELECTION_TOOL.BOX:
            roi: QRect = self._rectangle_tool_picker.geometry()
            roi: QRectF = self.mapToScene(roi).boundingRect()
            self._rectangle_tool_picker.hide()
            if image_rect == roi.united(image_rect):
                rect = EditableBox(roi)
                rect.label = self.current_label
                rect.tag = self._dataset
                self._scene.addItem(rect)
                self.current_tool = SELECTION_TOOL.POINTER
                self.setDragMode(QGraphicsView.ScrollHandDrag)

        elif self.current_tool == SELECTION_TOOL.ELLIPSE and self._current_ellipse:
            roi: QRect = self._current_ellipse.boundingRect()
            if image_rect == roi.united(image_rect):
                self.current_tool = SELECTION_TOOL.POINTER
                self.setDragMode(QGraphicsView.ScrollHandDrag)
            else:
                self._current_ellipse.delete_item()
        elif self.current_tool == SELECTION_TOOL.FREE and self._current_free_path:
            # create polygon
            self._current_free_path: QGraphicsPathItem
            path_rect = self._current_free_path.boundingRect()
            if image_rect == path_rect.united(image_rect):
                path = self._current_free_path.path()
                path_polygon = EditablePolygon()
                path_polygon.tag = self.dataset
                path_polygon.label = self.current_label
                self._scene.addItem(path_polygon)
                for i in range(0, path.elementCount(), 10):
                    x, y = path.elementAt(i).x, path.elementAt(i).y
                    path_polygon.addPoint(QPointF(x, y))
            self._scene.removeItem(self._current_free_path)
            self.current_tool = SELECTION_TOOL.POINTER
            self.setDragMode(QGraphicsView.ScrollHandDrag)
        super(ImageViewer, self).mouseReleaseEvent(evt)

    def remove_annotations(self):
        for item in self._scene.items():
            if isinstance(item, EditableItem):
                item.delete_item()

    def remove_annotations_by_label(self, label_name):
        for item in self._scene.items():
            if isinstance(item, EditableItem):
                if item.label and item.label.name == label_name:
                    item.delete_item()

    def enable_items(self, value):
        for item in self._scene.items():
            if isinstance(item, EditableItem):
                item.setEnabled(value)

    def clear_extreme_points(self):
        if self._extreme_points.qsize() > 0:
            for pt in self._extreme_points.queue:
                self._scene.removeItem(pt)
            self._extreme_points.queue.clear()
Пример #4
0
class ChartView(QChartView):
    def __init__(self, *args, **kwargs):
        super(ChartView, self).__init__(*args, **kwargs)
        self.resize(800, 600)
        self.setRenderHint(QPainter.Antialiasing)  # 抗锯齿
        # 自定义x轴label
        self.category = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
        self.initChart()

        # 提示widget
        self.toolTipWidget = GraphicsProxyWidget(self._chart)

        # line
        self.lineItem = QGraphicsLineItem(self._chart)
        pen = QPen(Qt.gray)
        pen.setWidth(1)

        self.lineItem.setPen(pen)
        self.lineItem.setZValue(996)
        self.lineItem.hide()

        self.lineItemX = QGraphicsLineItem(self._chart)
        penx = QPen(Qt.gray)
        penx.setWidth(1)
        self.lineItemX.setPen(penx)
        self.lineItemX.setZValue(997)
        self.lineItemX.hide()

        # 一些固定计算,减少mouseMoveEvent中的计算量
        # 获取x和y轴的最小最大值
        axisX, axisY = self._chart.axisX(), self._chart.axisY()
        self.min_x, self.max_x = axisX.min(), axisX.max()
        self.min_y, self.max_y = axisY.min(), axisY.max()

    def resizeEvent(self, event):
        super(ChartView, self).resizeEvent(event)
        # 当窗口大小改变时需要重新计算
        # 坐标系中左上角顶点
        self.point_top = self._chart.mapToPosition(
            QPointF(self.min_x, self.max_y))
        # 坐标右上点
        self.point_right_top = self._chart.mapToPosition(
            QPointF(self.max_x, self.max_y))
        # 坐标右下点
        self.point_right_bottom = self._chart.mapToPosition(
            QPointF(self.max_x, self.min_y))
        # 坐标原点坐标
        self.point_bottom = self._chart.mapToPosition(
            QPointF(self.min_x, self.min_y))
        self.step_x = (self.max_x - self.min_x) / \
            (self._chart.axisX().tickCount() - 1)

    def mouseMoveEvent(self, event):
        super(ChartView, self).mouseMoveEvent(event)
        pos = event.pos()
        # 把鼠标位置所在点转换为对应的xy值
        x = self._chart.mapToValue(pos).x()
        y = self._chart.mapToValue(pos).y()

        # 根据间隔来确定鼠标当前所在的索引
        index = round((x - self.min_x) / self.step_x)
        # 得到在坐标系中的所有正常显示的series的类型和点
        points = [
            (serie, serie.at(index)) for serie in self._chart.series()
            if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y
        ]
        if points:
            # 根据鼠标的点获取对应曲线的坐标点,
            # pos_x = self._chart.mapToPosition(
            #     QPointF(index * self.step_x + self.min_x, self.min_y))
            # 设置鼠标的垂直线
            self.lineItem.setLine(pos.x(), self.point_top.y(), pos.x(),
                                  self.point_bottom.y())
            self.lineItem.show()

            # 设置鼠标的水平线
            self.lineItemX.setLine(self.point_top.x(), pos.y(),
                                   self.point_right_top.x(), pos.y())
            self.lineItemX.show()

            try:
                title = self.category[index]
            except:
                title = ""
            t_width = self.toolTipWidget.width()
            t_height = self.toolTipWidget.height()
            # 如果鼠标位置离右侧的距离小于tip宽度
            x = pos.x() - t_width if self.width() - \
                pos.x() - 20 < t_width else pos.x()
            # 如果鼠标位置离底部的高度小于tip高度
            y = pos.y() - t_height if self.height() - \
                pos.y() - 20 < t_height else pos.y()
            self.toolTipWidget.show(title, points, QPoint(x, y))
        else:
            self.toolTipWidget.hide()
            self.lineItem.hide()
            self.lineItemX.hide()

    def handleMarkerClicked(self):
        marker = self.sender()  # 信号发送者
        if not marker:
            return
        visible = not marker.series().isVisible()
        # 隐藏或显示series
        marker.series().setVisible(visible)
        marker.setVisible(True)  # 要保证marker一直显示
        # 透明度
        alpha = 1.0 if visible else 0.4
        # 设置label的透明度
        brush = marker.labelBrush()
        color = brush.color()
        color.setAlphaF(alpha)
        brush.setColor(color)
        marker.setLabelBrush(brush)
        # 设置marker的透明度
        brush = marker.brush()
        color = brush.color()
        color.setAlphaF(alpha)
        brush.setColor(color)
        marker.setBrush(brush)
        # 设置画笔透明度
        pen = marker.pen()
        color = pen.color()
        color.setAlphaF(alpha)
        pen.setColor(color)
        marker.setPen(pen)

    def handleMarkerHovered(self, status):
        # 设置series的画笔宽度
        marker = self.sender()  # 信号发送者
        if not marker:
            return
        series = marker.series()
        if not series:
            return
        pen = series.pen()
        if not pen:
            return
        pen.setWidth(pen.width() + (1 if status else -1))
        series.setPen(pen)

    def handleSeriesHoverd(self, point, state):
        # 设置series的画笔宽度
        series = self.sender()  # 信号发送者
        pen = series.pen()
        if not pen:
            return
        pen.setWidth(pen.width() + (1 if state else -1))
        series.setPen(pen)

    def initChart(self):
        self._chart = QChart(title="折线图堆叠")
        self._chart.setAcceptHoverEvents(True)
        # Series动画
        self._chart.setAnimationOptions(QChart.SeriesAnimations)
        dataTable = [["邮件营销", [120, 132, 101, 134, 90, 230, 210]],
                     ["联盟广告", [220, 182, 191, 234, 290, 330, 310]],
                     ["视频广告", [150, 232, 201, 154, 190, 330, 410]],
                     ["直接访问", [320, 332, 301, 334, 390, 330, 320]],
                     ["搜索引擎", [820, 932, 901, 934, 1290, 1330, 1320]]]
        for series_name, data_list in dataTable:
            series = QLineSeries(self._chart)
            for j, v in enumerate(data_list):
                series.append(j, v)
            series.setName(series_name)
            series.setPointsVisible(True)  # 显示圆点
            series.hovered.connect(self.handleSeriesHoverd)  # 鼠标悬停
            self._chart.addSeries(series)
        self._chart.createDefaultAxes()  # 创建默认的轴
        axisX = self._chart.axisX()  # x轴
        axisX.setTickCount(7)  # x轴设置7个刻度
        axisX.setGridLineVisible(False)  # 隐藏从x轴往上的线条
        axisY = self._chart.axisY()
        axisY.setTickCount(7)  # y轴设置7个刻度
        axisY.setRange(0, 1500)  # 设置y轴范围
        # 自定义x轴
        axis_x = QCategoryAxis(
            self._chart,
            labelsPosition=QCategoryAxis.AxisLabelsPositionOnValue)
        axis_x.setTickCount(7)
        axis_x.setGridLineVisible(False)
        min_x = axisX.min()
        max_x = axisX.max()
        step = (max_x - min_x) / (7 - 1)  # 7个tick
        for i in range(0, 7):
            axis_x.append(self.category[i], min_x + i * step)
        self._chart.setAxisX(axis_x, self._chart.series()[-1])
        # self._chart.addAxis(axis_x, Qt.AlignBottom)
        # chart的图例
        legend = self._chart.legend()
        # 设置图例由Series来决定样式
        legend.setMarkerShape(QLegend.MarkerShapeFromSeries)
        # 遍历图例上的标记并绑定信号
        for marker in legend.markers():
            # 点击事件
            marker.clicked.connect(self.handleMarkerClicked)
            # 鼠标悬停事件
            marker.hovered.connect(self.handleMarkerHovered)
        self.setChart(self._chart)
Пример #5
0
class AbstractSliceTool(QGraphicsObject):
    """Summary

    Attributes:
        angles (TYPE): Description
        FILTER_NAME (str): Description
        is_started (bool): Description
        manager (TYPE): Description
        part_item (TYPE): Description
        sgv (TYPE): Description
        vectors (TYPE): Description
    """
    _RADIUS = styles.SLICE_HELIX_RADIUS
    _CENTER_OF_HELIX = QPointF(_RADIUS, _RADIUS)
    FILTER_NAME = 'virtual_helix'
    # _CENTER_OF_HELIX = QPointF(0. 0.)
    """Abstract base class to be subclassed by all other pathview tools."""
    def __init__(self, manager):
        """Summary

        Args:
            manager (TYPE): Description
        """
        super(AbstractSliceTool, self).__init__(parent=manager.viewroot)
        """ Pareting to viewroot to prevent orphan _line_item from occuring
        """
        self.sgv = None
        self.manager = manager
        self._active = False
        self._last_location = None
        self._line_item = QGraphicsLineItem(self)
        self._line_item.hide()
        self._vhi = None

        self.hide()
        self.is_started = False
        self.angles = [math.radians(x) for x in range(0, 360, 30)]
        self.vectors = self.setVectors()
        self.part_item = None

        self.vhi_hint_item = QGraphicsEllipseItem(_DEFAULT_RECT, self)
        self.vhi_hint_item.setPen(_MOD_PEN)
        self.vhi_hint_item.setZValue(styles.ZPARTITEM)
    # end def

    ######################## Drawing #######################################
    def setVectors(self):
        """Summary

        Returns:
            TYPE: Description
        """
        rad = self._RADIUS
        return [QLineF(rad, rad,
                       rad*(1. + 2.*math.cos(x)), rad*(1. + 2.*math.sin(x))
                       ) for x in self.angles]
    # end def

    def setVirtualHelixItem(self, virtual_helix_item):
        """Summary

        Args:
            virtual_helix_item (cadnano.gui.views.sliceview.virtualhelixitem.VirtualHelixItem): Description

        Returns:
            TYPE: Description
        """
        rad = self._RADIUS
        self._vhi = virtual_helix_item
        li = self._line_item
        li.setParentItem(virtual_helix_item)
        li.setLine(rad, rad, rad, rad)
        # li.setLine(0., 0., 0., 0.)
    # end def

    def setSelectionFilter(self, filter_name_list):
        if 'virtual_helix' in filter_name_list:
            self.vhi_hint_item.setPen(_MOD_PEN)
        else:
            self.vhi_hint_item.setPen(_INACTIVE_PEN)
    # end def

    def resetTool(self):
        """Summary

        Returns:
            TYPE: Description
        """
        self._line_item.setParentItem(self)

    def idNum(self):
        """Summary

        Returns:
            TYPE: Description
        """
        if self._vhi is not None:
            return self._vhi.idNum()

    def setPartItem(self, part_item):
        """Summary

        Args:
            part_item (TYPE): Description

        Returns:
            TYPE: Description
        """
        self.vhi_hint_item.setParentItem(part_item)
        self.part_item = part_item
    # end def

    def boundingRect(self):
        """Required to prevent NotImplementedError()
        """
        return QRectF()

    def eventToPosition(self, part_item, event):
        """take an event and return a position as a QPointF
        update widget as well

        Args:
            part_item (TYPE): Description
            event (TYPE): Description
        """
        if self.is_started:
            pos = self.findNearestPoint(part_item, event.scenePos())
        else:
            pos =  event.pos()
        self.vhi_hint_item.setPos(  pos -
                                    QPointF(_RADIUS - DELTA, _RADIUS - DELTA))
        return pos
    # end def

    def setHintPos(self, pos):
        self.vhi_hint_item.setPos(  pos -
                                    QPointF(_RADIUS - DELTA, _RADIUS - DELTA))
    # end def

    def findNearestPoint(self, part_item, target_scenepos):
        """
        Args:
            part_item (TYPE): Description
            target_scenepos (TYPE): Description
        """
        li = self._line_item
        pos = li.mapFromScene(target_scenepos)

        line = li.line()
        mouse_point_vec = QLineF(self._CENTER_OF_HELIX, pos)

        # Check if the click happened on the origin VH
        if mouse_point_vec.length() < self._RADIUS:
            # return part_item.mapFromScene(target_scenepos)
            return None

        angle_min = 9999
        direction_min = None
        for vector in self.vectors:
            angle_new = mouse_point_vec.angleTo(vector)
            if angle_new < angle_min:
                direction_min = vector
                angle_min = angle_new
        if direction_min is not None:
            li.setLine(direction_min)
            return part_item.mapFromItem(li, direction_min.p2())
        else:
            print("default point")
            line.setP2(pos)
            li.setLine(line)
            return part_item.mapFromItem(li, pos)
    # end def

    def findNextPoint(self, part_item, target_part_pos):
        """
        Args:
            part_item (TYPE): Description
            target_part_pos (TYPE): Description
        """
        li = self._line_item
        pos = li.mapFromItem(part_item, target_part_pos)
        for i, vector in enumerate(self.vectors):
            if vector.p2() == pos:
                return part_item.mapFromItem(li, self.vectors[i - 1].p2())
        # origin VirtualHelixItem is overlapping destination VirtualHelixItem
        return part_item.mapFromItem(li, self.vectors[0].p2())
    # end def

    def hideLineItem(self):
        """Summary

        Returns:
            TYPE: Description
        """
        self.vhi_hint_item.hide()
        li = self._line_item
        li.hide()
        li.setParentItem(self)
        line = li.line()
        line.setP2(self._CENTER_OF_HELIX)
        li.setLine(line)
        # li.hide()
        self.is_started = False
    # end def

    # def hoverEnterEvent(self, event):
    #     self.vhi_hint_item.show()
    #     #print("Slice VHI hoverEnterEvent")

    # # def hoverMoveEvent(self, event):
    #     # print("Slice VHI hoverMoveEvent")

    # def hoverLeaveEvent(self, event):
    #     # self.vhi_hint_item.hide()
    #     #print("Slice VHI hoverLeaveEvent")


    def hoverMoveEvent(self, part_item, event):
        """Summary

        Args:
            part_item (TYPE): Description
            event (TYPE): Description

        Returns:
            TYPE: Description
        """
        # self.vhi_hint_item.setPos(  event.pos()-
        #                             QPointF(_RADIUS - DELTA, _RADIUS - DELTA))
        pos = self.eventToPosition(part_item, event)
        return pos
    # end def

    def setActive(self, will_be_active, old_tool=None):
        """
        Called by SliceToolManager.setActiveTool when the tool becomes
        active. Used, for example, to show/hide tool-specific ui elements.

        Args:
            will_be_active (TYPE): Description
            old_tool (None, optional): Description
        """
        if self._active and not will_be_active:
            self.deactivate()
        self._active = will_be_active
        self.sgv = self.manager.window.slice_graphics_view
        if hasattr(self, 'getCustomContextMenu'):
            # print("connecting ccm")
            try:    # Hack to prevent multiple connections
                self.sgv.customContextMenuRequested.disconnect()
            except:
                pass
            self.sgv.customContextMenuRequested.connect(self.getCustomContextMenu)
    # end def

    def deactivate(self):
        """Summary

        Returns:
            TYPE: Description
        """
        if hasattr(self, 'getCustomContextMenu'):
            # print("disconnecting ccm")
            self.sgv.customContextMenuRequested.disconnect(self.getCustomContextMenu)
        self.sgv = None
        self.is_started = False
        self.hideLineItem()
        self._vhi = None
        self.part_item = None
        self.hide()
        self._active = False
    # end def

    def isActive(self):
        """Returns isActive
        """
        return self._active
Пример #6
0
class AbstractSliceTool(QGraphicsObject):
    """Summary

    Attributes:
        angles (TYPE): Description
        FILTER_NAME (str): Description
        is_started (bool): Description
        manager (TYPE): Description
        part_item (TYPE): Description
        sgv (TYPE): Description
        vectors (TYPE): Description
    """
    _RADIUS = styles.SLICE_HELIX_RADIUS
    _CENTER_OF_HELIX = QPointF(_RADIUS, _RADIUS)
    FILTER_NAME = 'virtual_helix'
    # _CENTER_OF_HELIX = QPointF(0. 0.)
    """Abstract base class to be subclassed by all other pathview tools."""
    def __init__(self, manager):
        """Summary

        Args:
            manager (TYPE): Description
        """
        super(AbstractSliceTool, self).__init__(parent=manager.viewroot)
        """ Pareting to viewroot to prevent orphan _line_item from occuring
        """
        self.sgv = None
        self.manager = manager
        self._active = False
        self._last_location = None
        self._line_item = QGraphicsLineItem(self)
        self._line_item.hide()
        self._vhi = None

        self.hide()
        self.is_started = False
        self.angles = [math.radians(x) for x in range(0, 360, 30)]
        self.vectors = self.setVectors()
        self.part_item = None

    # end def

    ######################## Drawing #######################################
    def setVectors(self):
        """Summary

        Returns:
            TYPE: Description
        """
        rad = self._RADIUS
        return [
            QLineF(rad, rad, rad * (1. + 2. * math.cos(x)),
                   rad * (1. + 2. * math.sin(x))) for x in self.angles
        ]

    # end def

    def setVirtualHelixItem(self, virtual_helix_item):
        """Summary

        Args:
            virtual_helix_item (cadnano.gui.views.sliceview.virtualhelixitem.VirtualHelixItem): Description

        Returns:
            TYPE: Description
        """
        rad = self._RADIUS
        self._vhi = virtual_helix_item
        li = self._line_item
        li.setParentItem(virtual_helix_item)
        li.setLine(rad, rad, rad, rad)
        # li.setLine(0., 0., 0., 0.)

    # end def

    def resetTool(self):
        """Summary

        Returns:
            TYPE: Description
        """
        self._line_item.setParentItem(self)

    def idNum(self):
        """Summary

        Returns:
            TYPE: Description
        """
        if self._vhi is not None:
            return self._vhi.idNum()

    def setPartItem(self, part_item):
        """Summary

        Args:
            part_item (TYPE): Description

        Returns:
            TYPE: Description
        """
        self.part_item = part_item

    # end def

    def boundingRect(self):
        """Required to prevent NotImplementedError()
        """
        return QRectF()

    def eventToPosition(self, part_item, event):
        """take an event and return a position as a QPointF
        update widget as well

        Args:
            part_item (TYPE): Description
            event (TYPE): Description
        """
        if self.is_started:
            return self.findNearestPoint(part_item, event.scenePos())
        else:
            return event.pos()

    # end def

    def findNearestPoint(self, part_item, target_scenepos):
        """
        Args:
            part_item (TYPE): Description
            target_scenepos (TYPE): Description
        """
        li = self._line_item
        pos = li.mapFromScene(target_scenepos)

        line = li.line()
        mouse_point_vec = QLineF(self._CENTER_OF_HELIX, pos)

        # Check if the click happened on the origin VH
        if mouse_point_vec.length() < self._RADIUS:
            # return part_item.mapFromScene(target_scenepos)
            return None

        angle_min = 9999
        direction_min = None
        for vector in self.vectors:
            angle_new = mouse_point_vec.angleTo(vector)
            if angle_new < angle_min:
                direction_min = vector
                angle_min = angle_new
        if direction_min is not None:
            li.setLine(direction_min)
            return part_item.mapFromItem(li, direction_min.p2())
        else:
            print("default point")
            line.setP2(pos)
            li.setLine(line)
            return part_item.mapFromItem(li, pos)

    # end def

    def findNextPoint(self, part_item, target_part_pos):
        """
        Args:
            part_item (TYPE): Description
            target_part_pos (TYPE): Description
        """
        li = self._line_item
        pos = li.mapFromItem(part_item, target_part_pos)
        for i, vector in enumerate(self.vectors):
            if vector.p2() == pos:
                return part_item.mapFromItem(li, self.vectors[i - 1].p2())
        # origin VirtualHelixItem is overlapping destination VirtualHelixItem
        return part_item.mapFromItem(li, self.vectors[0].p2())

    # end def

    def hideLineItem(self):
        """Summary

        Returns:
            TYPE: Description
        """
        li = self._line_item
        li.setParentItem(self)
        line = li.line()
        line.setP2(self._CENTER_OF_HELIX)
        li.setLine(line)
        li.hide()
        self.is_started = False

    # end def

    def hoverMoveEvent(self, part_item, event):
        """Summary

        Args:
            part_item (TYPE): Description
            event (TYPE): Description

        Returns:
            TYPE: Description
        """
        return self.eventToPosition(part_item, event)

    # end def

    def setActive(self, will_be_active, old_tool=None):
        """
        Called by SliceToolManager.setActiveTool when the tool becomes
        active. Used, for example, to show/hide tool-specific ui elements.

        Args:
            will_be_active (TYPE): Description
            old_tool (None, optional): Description
        """
        if self._active and not will_be_active:
            self.deactivate()
        self._active = will_be_active
        self.sgv = self.manager.window.slice_graphics_view
        if hasattr(self, 'getCustomContextMenu'):
            # print("connecting ccm")
            try:  # Hack to prevent multiple connections
                self.sgv.customContextMenuRequested.disconnect()
            except:
                pass
            self.sgv.customContextMenuRequested.connect(
                self.getCustomContextMenu)

    # end def

    def deactivate(self):
        """Summary

        Returns:
            TYPE: Description
        """
        if hasattr(self, 'getCustomContextMenu'):
            # print("disconnecting ccm")
            self.sgv.customContextMenuRequested.disconnect(
                self.getCustomContextMenu)
        self.sgv = None
        self.is_started = False
        self.hideLineItem()
        self._vhi = None
        self.part_item = None
        self.hide()
        self._active = False

    # end def

    def isActive(self):
        """Returns isActive
        """
        return self._active
Пример #7
0
class RotaryDialHoverRegion(QGraphicsEllipseItem):
    def __init__(self, rect, parent=None):
        # setup DNA line
        super(QGraphicsEllipseItem, self).__init__(rect, parent)
        self._parent = parent
        self.setPen(QPen(Qt.NoPen))
        self.setBrush(_HOVER_BRUSH)
        self.setAcceptHoverEvents(True)

        # hover marker
        self._hoverLine = QGraphicsLineItem(-_ROTARY_DELTA_WIDTH/2, 0, _ROTARY_DELTA_WIDTH/2, 0, self)
        self._hoverLine.setPen(QPen(QColor(204, 0, 0), .5))
        self._hoverLine.hide()

        self._startPos = None
        self._startAngle = None  # save selection start
        self._clockwise = None
        self.dummy = RotaryDialDeltaItem(0, 0, parent)
        self.dummy.hide()

    def updateRect(self, rect):
        self.setRect(rect)

    def hoverEnterEvent(self, event):
        self.updateHoverLine(event)
        self._hoverLine.show()
    # end def

    def hoverMoveEvent(self, event):
        self.updateHoverLine(event)
    # end def

    def hoverLeaveEvent(self, event):
        self._hoverLine.hide()
    # end def

    def mousePressEvent(self, event):
        r = _RADIUS
        self.updateHoverLine(event)
        pos = self._hoverLine.pos()
        aX, aY, angle = self.snapPosToCircle(pos, r)
        if angle != None:
            self._startPos = QPointF(aX, aY)
            self._startAngle = self.updateHoverLine(event)
            self.dummy.updateAngle(self._startAngle, 0)
            self.dummy.show()
        # mark the start
        # f = QGraphicsEllipseItem(pX, pY, 2, 2, self)
        # f.setPen(QPen(Qt.NoPen))
        # f.setBrush(QBrush(QColor(204, 0, 0)))
    # end def

    def mouseMoveEvent(self, event):
        eventAngle = self.updateHoverLine(event)
        # Record initial direction before calling getSpanAngle
        if self._clockwise is None:
            self._clockwise = False if eventAngle > self._startAngle else True
        spanAngle = self.getSpanAngle(eventAngle)
        self.dummy.updateAngle(self._startAngle, spanAngle)
    # end def

    def mouseReleaseEvent(self, event):
        self.dummy.hide()
        endAngle = self.updateHoverLine(event)
        spanAngle = self.getSpanAngle(endAngle)
        old_angle = self._parent.virtualHelix().getProperty('eulerZ')
        new_angle = round((old_angle - spanAngle) % 360,0)
        self._parent.virtualHelix().setProperty('eulerZ', new_angle)

        # mark the end
        # x = self._hoverLine.x()
        # y = self._hoverLine.y()
        # f = QGraphicsEllipseItem(x, y, 6, 6, self)
        # f.setPen(QPen(Qt.NoPen))
        # f.setBrush(QBrush(QColor(204, 0, 0, 128)))
    # end def

    def updateHoverLine(self, event):
        """
        Moves red line to point (aX,aY) on RotaryDialLine closest to event.pos.
        Returns the angle of aX, aY, using the Qt arc coordinate system
        (0 = east, 90 = north, 180 = west, 270 = south).
        """
        r = _RADIUS
        aX, aY, angle = self.snapPosToCircle(event.pos(), r)
        if angle != None:
            self._hoverLine.setPos(aX, aY)
            self._hoverLine.setRotation(-angle)
        return angle
    # end def

    def snapPosToCircle(self, pos, radius):
        """Given x, y and radius, return x,y of nearest point on circle, and its angle"""
        pX = pos.x()
        pY = pos.y()
        cX = cY = radius
        vX = pX - cX
        vY = pY - cY
        magV = sqrt(vX*vX + vY*vY)
        if magV == 0:
            return (None, None, None)
        aX = cX + vX / magV * radius
        aY = cY + vY / magV * radius
        angle = (atan2(aY-cY, aX-cX))
        deg = -degrees(angle) if angle < 0 else 180+(180-degrees(angle))
        return (aX, aY, deg)
    # end def

    def getSpanAngle(self, angle):
        """
        Return the spanAngle angle by checking the initial direction of the selection.
        Selections that cross 0° must be handed as an edge case.
        """
        if self._clockwise: # spanAngle is negative
            if angle < self._startAngle:
                spanAngle = angle - self._startAngle
            else:
                spanAngle = -(self._startAngle + (360-angle))
        else: # counterclockwise, spanAngle is positive
            if angle > self._startAngle:
                spanAngle = angle - self._startAngle
            else:
                spanAngle = (360-self._startAngle) + angle
        return spanAngle
Пример #8
0
class ImageViewer(QGraphicsView, QObject):
    def __init__(self, parent=None):
        super(ImageViewer, self).__init__(parent)
        self.setRenderHints(QPainter.Antialiasing
                            | QPainter.SmoothPixmapTransform)
        #self.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setDragMode(QGraphicsView.ScrollHandDrag)
        #self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        #self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        self._scene = ImageViewerScene(self)
        self.setScene(self._scene)
        self._create_grid()
        self._create_grid_lines()
        self._pixmap = None
        self._selection_mode = SELECTION_MODE.NONE

        # polygon selection
        _polygon_guide_line_pen = QPen(QtGui.QColor(235, 72, 40))
        _polygon_guide_line_pen.setWidth(2)
        _polygon_guide_line_pen.setStyle(QtCore.Qt.DotLine)
        self._polygon_guide_line = QGraphicsLineItem()
        self._polygon_guide_line.setVisible(False)
        self._polygon_guide_line.setPen(_polygon_guide_line_pen)
        self._scene.addItem(self._polygon_guide_line)
        self._current_polygon = None
        # rectangle selection
        self._box_origin = QPoint()
        self._box_picker = QRubberBand(QRubberBand.Rectangle, self)

        # free selection
        self._current_free_path = None
        self._is_drawing = False
        self._last_point_drawn = QPoint()
        self._current_label = None

    @property
    def current_label(self):
        return self._current_label

    @current_label.setter
    def current_label(self, value):
        self._current_label = value

    @property
    def pixmap(self) -> ImagePixmap:
        return self._pixmap

    @pixmap.setter
    def pixmap(self, value: QPixmap):
        self.selection_mode = SELECTION_MODE.NONE
        self.resetTransform()
        if self.pixmap:
            self._scene.removeItem(self._pixmap)
        self.remove_annotations()
        self._pixmap = ImagePixmap()
        self._pixmap.setPixmap(value)
        self._pixmap.setOffset(-value.width() / 2, -value.height() / 2)
        self._pixmap.setTransformationMode(QtCore.Qt.SmoothTransformation)
        self._pixmap.signals.hoverEnterEventSgn.connect(
            self.pixmap_hoverEnterEvent_slot)
        self._pixmap.signals.hoverLeaveEventSgn.connect(
            self.pixmap_hoverLeaveEvent_slot)
        self._pixmap.signals.hoverMoveEventSgn.connect(
            self.pixmap_hoverMoveEvent_slot)
        self._scene.addItem(self._pixmap)
        # rect=self._scene.addRect(QtCore.QRectF(0,0,100,100), QtGui.QPen(QtGui.QColor("red")))
        # rect.setZValue(1.0)
        self.fit_to_window()

    @property
    def selection_mode(self):
        return self._selection_mode

    @selection_mode.setter
    def selection_mode(self, value):
        self._polygon_guide_line.hide()
        self._current_polygon = None
        self._current_free_path = None
        self._is_drawing = value == SELECTION_MODE.FREE
        if value == SELECTION_MODE.NONE:
            self.enable_items(True)
        else:
            self.enable_items(False)
        self._selection_mode = value

    def remove_annotations(self):
        for item in self._scene.items():
            if isinstance(item, EditableBox):
                self._scene.removeItem(item)
            elif isinstance(item, EditablePolygon):
                item.delete_polygon()

    def remove_annotations_by_label(self, label_name):
        for item in self._scene.items():
            if isinstance(item, EditableBox):
                if item.label and item.label.name == label_name:
                    self._scene.removeItem(item)
            elif isinstance(item, EditablePolygon):
                if item.label and item.label.name == label_name:
                    item.delete_polygon()

    def enable_items(self, value):
        for item in self._scene.items():
            if isinstance(item, EditablePolygon) or isinstance(
                    item, EditableBox):
                item.setEnabled(value)

    def _create_grid(self):
        gridSize = 15
        backgroundPixmap = QtGui.QPixmap(gridSize * 2, gridSize * 2)
        #backgroundPixmap.fill(QtGui.QColor("white"))
        backgroundPixmap.fill(QtGui.QColor(20, 20, 20))
        #backgroundPixmap.fill(QtGui.QColor("powderblue"))
        painter = QtGui.QPainter(backgroundPixmap)
        #backgroundColor=QtGui.QColor("palegoldenrod")
        #backgroundColor=QtGui.QColor(237,237,237)
        backgroundColor = QtGui.QColor(0, 0, 0)
        painter.fillRect(0, 0, gridSize, gridSize, backgroundColor)
        painter.fillRect(gridSize, gridSize, gridSize, gridSize,
                         backgroundColor)
        painter.end()
        self._scene.setBackgroundBrush(QtGui.QBrush(backgroundPixmap))

    def _create_grid_lines(self):
        pen_color = QColor(255, 255, 255, 255)
        pen = QPen(pen_color)
        pen.setWidth(2)
        pen.setStyle(QtCore.Qt.DotLine)
        self.vline = QGraphicsLineItem()
        self.vline.setVisible(False)
        self.vline.setPen(pen)
        self.hline = QGraphicsLineItem()
        self.hline.setVisible(False)
        self.hline.setPen(pen)
        self._scene.addItem(self.vline)
        self._scene.addItem(self.hline)

    def wheelEvent(self, event: QWheelEvent):
        adj = (event.angleDelta().y() / 120) * 0.1
        self.scale(1 + adj, 1 + adj)

    def fit_to_window(self):
        """Fit image within view."""
        if not self.pixmap or not self._pixmap.pixmap():
            return
        #self._pixmap.setTransformationMode(QtCore.Qt.SmoothTransformation)
        self.fitInView(self._pixmap, QtCore.Qt.KeepAspectRatio)

    def show_guide_lines(self):
        if self.hline and self.vline:
            self.hline.show()
            self.vline.show()

    def hide_guide_lines(self):
        if self.hline and self.vline:
            self.hline.hide()
            self.vline.hide()

    def pixmap_hoverEnterEvent_slot(self):
        self.show_guide_lines()

    def pixmap_hoverLeaveEvent_slot(self):
        self.hide_guide_lines()

    def pixmap_hoverMoveEvent_slot(self, evt: QGraphicsSceneHoverEvent, x, y):
        bbox: QRect = self._pixmap.boundingRect()
        offset = QPointF(bbox.width() / 2, bbox.height() / 2)
        self.vline.setLine(x, -offset.y(), x, bbox.height() - offset.y())
        self.vline.setZValue(1)
        self.hline.setLine(-offset.x(), y, bbox.width() - offset.x(), y)
        self.hline.setZValue(1)

    def mouseMoveEvent(self, evt: QtGui.QMouseEvent) -> None:
        if self.selection_mode == SELECTION_MODE.BOX:
            if not self._box_origin.isNull():
                self._box_picker.setGeometry(
                    QRect(self._box_origin, evt.pos()).normalized())
        elif self.selection_mode == SELECTION_MODE.POLYGON:
            if self._current_polygon:
                if self._current_polygon.count > 0:
                    last_point: QPointF = self._current_polygon.last_point
                    self._polygon_guide_line.setZValue(1)
                    self._polygon_guide_line.show()
                    mouse_pos = self.mapToScene(evt.pos())
                    self._polygon_guide_line.setLine(last_point.x(),
                                                     last_point.y(),
                                                     mouse_pos.x(),
                                                     mouse_pos.y())
            else:
                self._polygon_guide_line.hide()

        elif self.selection_mode == SELECTION_MODE.FREE and evt.buttons(
        ) and QtCore.Qt.LeftButton:
            if self._current_free_path:
                painter: QPainterPath = self._current_free_path.path()
                self._last_point_drawn = self.mapToScene(evt.pos())
                painter.lineTo(self._last_point_drawn)
                self._current_free_path.setPath(painter)

        super(ImageViewer, self).mouseMoveEvent(evt)

    def mousePressEvent(self, evt: QtGui.QMouseEvent) -> None:

        if evt.buttons() == QtCore.Qt.LeftButton:
            if self.selection_mode == SELECTION_MODE.BOX:
                self.setDragMode(QGraphicsView.NoDrag)
                self._box_origin = evt.pos()
                self._box_picker.setGeometry(QRect(self._box_origin, QSize()))
                self._box_picker.show()

            elif self._selection_mode == SELECTION_MODE.POLYGON:
                pixmap_rect: QRectF = self._pixmap.boundingRect()
                new_point = self.mapToScene(evt.pos())
                # consider only the points intothe image
                if pixmap_rect.contains(new_point):
                    if self._current_polygon is None:
                        self._current_polygon = EditablePolygon()
                        self._current_polygon.signals.deleted.connect(
                            self.delete_polygon_slot)
                        self._scene.addItem(self._current_polygon)
                        self._current_polygon.addPoint(new_point)
                    else:
                        self._current_polygon.addPoint(new_point)

            elif self._selection_mode == SELECTION_MODE.FREE:
                # start drawing
                new_point = self.mapToScene(evt.pos())
                pixmap_rect: QRectF = self._pixmap.boundingRect()
                # consider only the points intothe image
                if pixmap_rect.contains(new_point):
                    self.setDragMode(QGraphicsView.NoDrag)
                    pen = QPen(QtGui.QColor(235, 72, 40))
                    pen.setWidth(10)
                    self._last_point_drawn = new_point
                    self._current_free_path = QGraphicsPathItem()
                    self._current_free_path.setOpacity(0.6)
                    self._current_free_path.setPen(pen)
                    painter = QPainterPath()
                    painter.moveTo(self._last_point_drawn)
                    self._current_free_path.setPath(painter)
                    self._scene.addItem(self._current_free_path)
        else:
            self.setDragMode(QGraphicsView.ScrollHandDrag)

        super(ImageViewer, self).mousePressEvent(evt)

    def mouseReleaseEvent(self, evt: QtGui.QMouseEvent) -> None:
        if evt.button() == QtCore.Qt.LeftButton:
            if self.selection_mode == SELECTION_MODE.BOX:
                roi: QRect = self._box_picker.geometry()
                roi: QRectF = self.mapToScene(roi).boundingRect()
                pixmap_rect = self._pixmap.boundingRect()
                self._box_picker.hide()
                if pixmap_rect == roi.united(pixmap_rect):
                    rect = EditableBox(roi)
                    rect.label = self.current_label
                    self._scene.addItem(rect)
                    self.selection_mode = SELECTION_MODE.NONE
                    self.setDragMode(QGraphicsView.ScrollHandDrag)

            elif self.selection_mode == SELECTION_MODE.FREE and self._current_free_path:
                # create polygon
                self._current_free_path: QGraphicsPathItem
                path_rect = self._current_free_path.boundingRect()
                pixmap_rect = self._pixmap.boundingRect()
                if pixmap_rect == path_rect.united(pixmap_rect):
                    path = self._current_free_path.path()
                    path_polygon = EditablePolygon()
                    path_polygon.label = self.current_label
                    self._scene.addItem(path_polygon)
                    for i in range(0, path.elementCount(), 10):
                        x, y = path.elementAt(i).x, path.elementAt(i).y
                        path_polygon.addPoint(QPointF(x, y))
                self._scene.removeItem(self._current_free_path)
                self.selection_mode = SELECTION_MODE.NONE
                self.setDragMode(QGraphicsView.ScrollHandDrag)

        super(ImageViewer, self).mouseReleaseEvent(evt)

    def keyPressEvent(self, event: QtGui.QKeyEvent) -> None:
        if self._current_polygon and event.key() == QtCore.Qt.Key_Space:
            points = self._current_polygon.points
            self._current_polygon.label = self.current_label
            self._current_polygon = None
            self.selection_mode = SELECTION_MODE.NONE
            self._polygon_guide_line.hide()
            self.setDragMode(QGraphicsView.ScrollHandDrag)
        super(ImageViewer, self).keyPressEvent(event)

    def delete_polygon_slot(self, polygon: EditablePolygon):
        self._current_polygon = None
        self.selection_mode = SELECTION_MODE.NONE
        self._polygon_guide_line.hide()
Пример #9
0
class ChartView(QChartView):
    def __init__(self, *args, **kwargs):
        super(ChartView, self).__init__(*args, **kwargs)
        self.resize(800, 600)
        self.setRenderHint(QPainter.Antialiasing)  # 抗锯齿
        self.initChart()

        # 提示widget
        self.toolTipWidget = GraphicsProxyWidget(self._chart)

        # line
        self.lineItem = QGraphicsLineItem(self._chart)
        pen = QPen(Qt.gray)
        pen.setWidth(1)
        self.lineItem.setPen(pen)
        self.lineItem.setZValue(998)
        self.lineItem.hide()

    '''
        # 一些固定计算,减少mouseMoveEvent中的计算量
        # 获取x和y轴的最小最大值
        axisX, axisY = self._chart.axisX(), self._chart.axisY()
        self.min_x, self.max_x = axisX.min(), axisX.max()
        self.min_y, self.max_y = axisY.min(), axisY.max()
        # 坐标系中左上角顶点
        self.point_top = self._chart.mapToPosition(
            QPointF(self.min_x, self.max_y))
        # 坐标原点坐标
        self.point_bottom = self._chart.mapToPosition(
            QPointF(self.min_x, self.min_y))
        self.step_x = (self.max_x - self.min_x) / (axisX.tickCount() - 1)

    def mouseMoveEvent(self, event):
        super(ChartView, self).mouseMoveEvent(event)
        pos = event.pos()
        # 把鼠标位置所在点转换为对应的xy值
        x = self._chart.mapToValue(pos).x()
        y = self._chart.mapToValue(pos).y()
        index = round((x - self.min_x) / self.step_x)
        # 得到在坐标系中的所有series的类型和点
        points = [(serie, serie.at(index))
                  for serie in self._chart.series() if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y]
        if points:
            pos_x = self._chart.mapToPosition(
                QPointF(index * self.step_x + self.min_x, self.min_y))
            self.lineItem.setLine(pos_x.x(), self.point_top.y(),
                                  pos_x.x(), self.point_bottom.y())
            self.lineItem.show()
            try:
                title = self.category[index]
            except:
                title = ""
            t_width = self.toolTipWidget.width()
            t_height = self.toolTipWidget.height()
            # 如果鼠标位置离右侧的距离小于tip宽度
            x = pos.x() - t_width if self.width() - \
                pos.x() - 20 < t_width else pos.x()
            # 如果鼠标位置离底部的高度小于tip高度
            y = pos.y() - t_height if self.height() - \
                pos.y() - 20 < t_height else pos.y()
            self.toolTipWidget.show(
                title, points, QPoint(x, y))
        else:
            self.toolTipWidget.hide()
            self.lineItem.hide()
    '''

    def handleMarkerClicked(self):
        marker = self.sender()  # 信号发送者
        if not marker:
            return
        visible = not marker.series().isVisible()
        #         # 隐藏或显示series
        marker.series().setVisible(visible)
        marker.setVisible(True)  # 要保证marker一直显示
        # 透明度
        alpha = 1.0 if visible else 0.4
        # 设置label的透明度
        brush = marker.labelBrush()
        color = brush.color()
        color.setAlphaF(alpha)
        brush.setColor(color)
        marker.setLabelBrush(brush)
        # 设置marker的透明度
        brush = marker.brush()
        color = brush.color()
        color.setAlphaF(alpha)
        brush.setColor(color)
        marker.setBrush(brush)
        # 设置画笔透明度
        pen = marker.pen()
        color = pen.color()
        color.setAlphaF(alpha)
        pen.setColor(color)
        marker.setPen(pen)

    def handleMarkerHovered(self, status):
        # 设置series的画笔宽度
        marker = self.sender()  # 信号发送者
        if not marker:
            return
        series = marker.series()
        if not series:
            return
        pen = series.pen()
        if not pen:
            return
        pen.setWidth(pen.width() + (1 if status else -1))
        series.setPen(pen)

    def handleSeriesHoverd(self, status, index, barset):
        return print(status, index, barset)


#         # 设置series的画笔宽度
#         series = self.sender()  # 信号发送者
#         pen = series.pen()
#         if not pen:
#             return
#         pen.setWidth(pen.width() + (1 if state else -1))
#         series.setPen(pen)

    def initChart(self):
        self._chart = QChart(title="柱状图堆叠")
        self._chart.setAcceptHoverEvents(True)
        # Series动画
        self._chart.setAnimationOptions(QChart.SeriesAnimations)
        categories = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
        names = ["邮件营销", "联盟广告", "视频广告", "直接访问", "搜索引擎"]
        series = QBarSeries(self._chart)
        for name in names:
            bar = QBarSet(name)
            #随机数据
            for _ in range(7):
                bar.append(randint(0, 10))
            series.append(bar)
            #             series.hovered.connect(self.handleSeriesHoverd)  # 鼠标悬停
            self._chart.addSeries(series)
        self._chart.createDefaultAxes()  # 创建默认的轴
        # x轴
        axis_x = QBarCategoryAxis(self._chart)
        axis_x.append(categories)
        self._chart.setAxisX(axis_x, series)
        # chart的图例
        legend = self._chart.legend()
        # 设置图例由Series来决定样式
        legend.setMarkerShape(QLegend.MarkerShapeFromSeries)
        # 遍历图例上的标记并绑定信号
        for marker in legend.markers():
            # 点击事件
            marker.clicked.connect(self.handleMarkerClicked)
            # 鼠标悬停事件
            marker.hovered.connect(self.handleMarkerHovered)
        self.setChart(self._chart)
Пример #10
0
class barChartView(QChartView):
    def __init__(self, xAxis=[], *args, **kwargs):
        super(barChartView, self).__init__(*args, **kwargs)
        self.initChart(xAxis)

        # line 宽度需要调整
        self.lineItem = QGraphicsLineItem(self._chart)
        pen = QPen(Qt.gray)
        self.lineItem.setPen(pen)
        self.lineItem.setZValue(998)
        self.lineItem.hide()
        self.cal()

    # 一些固定计算,减少mouseMoveEvent中的计算量
    def cal(self):
        # 提示widget
        self.toolTipWidget = GraphicsProxyWidget(self._chart)
        # 获取x和y轴的最小最大值
        axisX, axisY = self._chart.axisX(), self._chart.axisY()
        self.category_len = len(axisX.categories())
        self.min_x, self.max_x = -0.5, self.category_len - 0.5
        self.min_y, self.max_y = axisY.min(), axisY.max()
        # 坐标系中左上角顶点
        self.point_top = self._chart.mapToPosition(
            QPointF(self.min_x, self.max_y))

    def setCat(self, data):
        self.categories = data

    #初始化
    def initChart(self, xAxis):
        self._chart = QChart()
        # 调整边距
        self._chart.layout().setContentsMargins(0, 0, 0, 0)  # 外界
        self._chart.setMargins(QMargins(3, 0, 3, 0))  # 内界
        self._chart.setBackgroundRoundness(0)
        self._chart.setBackgroundVisible(False)
        # 设置主题
        self._chart.setTheme(QChart.ChartThemeBlueIcy)
        # 抗锯齿
        self.setRenderHint(QPainter.Antialiasing)
        # 开启动画效果
        self._chart.setAnimationOptions(QChart.SeriesAnimations)
        self.categories = xAxis
        self._series = QBarSeries(self._chart)
        self._chart.addSeries(self._series)
        self._chart.createDefaultAxes()  # 创建默认的轴
        self._axis_x = QBarCategoryAxis(self._chart)
        self._axis_x.append(self.categories)
        self._axis_y = QValueAxis(self._chart)
        self._axis_y.setTitleText("任务数")
        self._axis_y.setRange(0, 10)
        self._chart.setAxisX(self._axis_x, self._series)
        self._chart.setAxisY(self._axis_y, self._series)
        # chart的图例
        legend = self._chart.legend()
        legend.setVisible(True)

        self.setChart(self._chart)

    def mouseMoveEvent(self, event):
        super(barChartView, self).mouseMoveEvent(event)
        pos = event.pos()
        # 把鼠标位置所在点转换为对应的xy值
        x = self._chart.mapToValue(pos).x()
        y = self._chart.mapToValue(pos).y()
        index = round(x)
        # 得到在坐标系中的所有bar的类型和点
        serie = self._chart.series()[0]
        bars = [
            (bar, bar.at(index)) for bar in serie.barSets()
            if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y
        ]
        # print(bars)
        if bars:
            right_top = self._chart.mapToPosition(
                QPointF(self.max_x, self.max_y))
            # 等分距离比例
            step_x = round(
                (right_top.x() - self.point_top.x()) / self.category_len)
            posx = self._chart.mapToPosition(QPointF(x, self.min_y))
            self.lineItem.setLine(posx.x(), self.point_top.y(), posx.x(),
                                  posx.y())
            self.lineItem.show()
            try:
                title = self.categories[index]
            except:
                title = ""
            t_width = self.toolTipWidget.width()
            t_height = self.toolTipWidget.height()
            # 如果鼠标位置离右侧的距离小于tip宽度
            x = pos.x() - t_width if self.width() - \
                pos.x() - 20 < t_width else pos.x()
            # 如果鼠标位置离底部的高度小于tip高度
            y = pos.y() - t_height if self.height() - \
                pos.y() - 20 < t_height else pos.y()
            self.toolTipWidget.show(title, bars, QPoint(x, y))
        else:
            self.toolTipWidget.hide()
            self.lineItem.hide()
Пример #11
0
class AbstractGridTool(AbstractTool):
    _RADIUS = styles.GRID_HELIX_RADIUS
    _CENTER_OF_HELIX = QPointF(_RADIUS, _RADIUS)
    FILTER_NAME = 'virtual_helix'
    # _CENTER_OF_HELIX = QPointF(0. 0.)
    """Abstract base class to be subclassed by all other pathview tools."""
    def __init__(self, manager: AbstractToolManager):
        """Summary

        Args:
            manager (TYPE): Description
        """
        # Setting parent to viewroot to prevent orphan _line_item from occurring
        super(AbstractGridTool, self).__init__(parent=manager.viewroot)
        self.slice_graphics_view = manager.window.grid_graphics_view
        self.manager = manager
        self._active = False
        self._last_location = None
        self._line_item = QGraphicsLineItem(self)
        self._line_item.hide()
        self._vhi = None

        self.hide()
        self.is_started = False
        self.angles = [math.radians(x) for x in range(0, 360, 30)]
        self.vectors = self.setVectors()
        self.part_item = None

        self.vhi_hint_item = QGraphicsEllipseItem(_DEFAULT_RECT, self)
        self.vhi_hint_item.setPen(_MOD_PEN)
        self.vhi_hint_item.setZValue(styles.ZPARTITEM)

    # end def

    ######################## Drawing #######################################
    def setVectors(self):
        """Summary

        Returns:
            TYPE: Description
        """
        rad = self._RADIUS
        return [
            QLineF(rad, rad, rad * (1. + 2. * math.cos(x)),
                   rad * (1. + 2. * math.sin(x))) for x in self.angles
        ]

    # end def

    def setVirtualHelixItem(self, virtual_helix_item):
        """Summary

        Args:
            virtual_helix_item (cadnano.views.gridview.virtualhelixitem.VirtualHelixItem): Description

        Returns:
            TYPE: Description
        """
        rad = self._RADIUS
        self._vhi = virtual_helix_item
        li = self._line_item
        li.setParentItem(virtual_helix_item)
        li.setLine(rad, rad, rad, rad)
        # li.setLine(0., 0., 0., 0.)

    # end def

    def setSelectionFilter(self, filter_name_list):
        if 'virtual_helix' in filter_name_list:
            self.vhi_hint_item.setPen(_MOD_PEN)
        else:
            self.vhi_hint_item.setPen(_INACTIVE_PEN)

    # end def

    def resetTool(self):
        """Summary

        Returns:
            TYPE: Description
        """
        self._line_item.setParentItem(self)

    def idNum(self):
        """Summary

        Returns:
            TYPE: Description
        """
        if self._vhi is not None:
            return self._vhi.idNum()

    def setPartItem(self, part_item):
        """Summary

        Args:
            part_item (TYPE): Description

        Returns:
            TYPE: Description
        """
        self.vhi_hint_item.setParentItem(part_item)
        self.part_item = part_item

    # end def

    def boundingRect(self):
        """Required to prevent NotImplementedError()
        """
        return QRectF()

    def eventToPosition(self, part_item, event):
        """take an event and return a position as a QPointF
        update widget as well

        Args:
            part_item (TYPE): Description
            event (TYPE): Description
        """
        if self.is_started:
            pos = self.findNearestPoint(part_item, event.scenePos())
        else:
            pos = event.pos()
        self.vhi_hint_item.setPos(pos -
                                  QPointF(_RADIUS - DELTA, _RADIUS - DELTA))
        return pos

    # end def

    def setHintPos(self, pos):
        self.vhi_hint_item.setPos(pos -
                                  QPointF(_RADIUS - DELTA, _RADIUS - DELTA))

    # end def

    def findNearestPoint(self, part_item: QAbstractPartItem,
                         target_scenepos: QPointF) -> QPointF:
        """
        Args:
            part_item: Description
            target_scenepos: position in the Scene
        """
        li = self._line_item
        pos = li.mapFromScene(target_scenepos)

        line = li.line()
        mouse_point_vec = QLineF(self._CENTER_OF_HELIX, pos)

        # Check if the click happened on the origin VH
        if mouse_point_vec.length() < self._RADIUS:
            # return part_item.mapFromScene(target_scenepos)
            return None

        angle_min = 9999
        direction_min = None
        for vector in self.vectors:
            angle_new = mouse_point_vec.angleTo(vector)
            if angle_new < angle_min:
                direction_min = vector
                angle_min = angle_new
        if direction_min is not None:
            li.setLine(direction_min)
            return part_item.mapFromItem(li, direction_min.p2())
        else:
            print("default point")
            line.setP2(pos)
            li.setLine(line)
            return part_item.mapFromItem(li, pos)

    # end def

    def findNextPoint(self, part_item: QAbstractPartItem,
                      target_part_pos: QPointF) -> QPointF:
        """
        Args:
            part_item: Description
            target_part_pos: Position in the Part
        """
        li = self._line_item
        pos = li.mapFromItem(part_item, target_part_pos)
        for i, vector in enumerate(self.vectors):
            if vector.p2() == pos:
                return part_item.mapFromItem(li, self.vectors[i - 1].p2())
        # origin VirtualHelixItem is overlapping destination VirtualHelixItem
        return part_item.mapFromItem(li, self.vectors[0].p2())

    # end def

    def hideLineItem(self):
        """ Hide the ``_line_item`` and the ``vhi_hint_item``
        set ``is_started`` to :bool:`False`
        """
        # print("hideLineItem")
        self.vhi_hint_item.hide()
        li = self._line_item
        li.hide()
        li.setParentItem(self)
        line = li.line()
        line.setP2(self._CENTER_OF_HELIX)
        li.setLine(line)
        self.is_started = False

    # end def

    # def hoverEnterEvent(self, event):
    #     self.vhi_hint_item.show()
    #     #print("Grid VHI hoverEnterEvent")

    # # def hoverMoveEvent(self, event):
    #     # print("Grid VHI hoverMoveEvent")

    # def hoverLeaveEvent(self, event):
    #     # self.vhi_hint_item.hide()
    #     #print("Grid VHI hoverLeaveEvent")

    def hoverMoveEvent(self, part_item, event):
        """Summary

        Args:
            part_item (TYPE): Description
            event (TYPE): Description

        Returns:
            TYPE: Description
        """
        # self.vhi_hint_item.setPos(  event.pos()-
        #                             QPointF(_RADIUS - DELTA, _RADIUS - DELTA))
        pos = self.eventToPosition(part_item, event)
        return pos

    # end def

    def setActive(self, will_be_active, old_tool=None):
        """
        Called by GridToolManager.setActiveTool when the tool becomes
        active. Used, for example, to show/hide tool-specific ui elements.

        Args:
            will_be_active (TYPE): Description
            old_tool (None, optional): Description
        """
        if self._active and not will_be_active:
            self.deactivate()
        self._active = will_be_active
        self.slice_graphics_view = self.manager.window.grid_graphics_view
        if hasattr(self, 'getCustomContextMenu'):
            # print("connecting ccm")
            try:  # Hack to prevent multiple connections
                self.slice_graphics_view.customContextMenuRequested.disconnect(
                )
            except (AttributeError, TypeError):
                pass
            self.slice_graphics_view.customContextMenuRequested.connect(
                self.getCustomContextMenu)

    # end def

    def deactivate(self):
        """Summary

        Returns:
            TYPE: Description
        """
        if hasattr(self, 'getCustomContextMenu'):
            # print("disconnecting ccm")
            self.slice_graphics_view.customContextMenuRequested.disconnect(
                self.getCustomContextMenu)
        self.slice_graphics_view = None
        self.is_started = False
        self.hideLineItem()
        self._vhi = None
        self.part_item = None
        self.hide()
        self._active = False

    # end def

    def isActive(self) -> bool:
        """Returns isActive
        """
        return self._active
Пример #12
0
class KVWidget(QWidget):
    """ 主页面 """
    def __init__(self):
        super(KVWidget, self).__init__()
        self.setMouseTracking(True)

        # 获取数据
        self.stocks = read_tick_data()

        self.k_view = KLineChartView(self.stocks[:100])
        self.v_view = VLineChartView(self.stocks[:100])
        self.k_view.candles_hovered.connect(self.on_series_hovered)
        self.v_view.bar_hovered.connect(self.on_series_hovered)

        btn_widget = QWidget()
        h_layout = QHBoxLayout()
        btn_clear = QPushButton('清除')
        btn_add = QPushButton('添加')
        h_layout.addStretch()
        h_layout.addWidget(btn_clear)
        h_layout.addWidget(btn_add)
        btn_widget.setLayout(h_layout)

        btn_clear.clicked.connect(self.on_clear_clicked)
        btn_add.clicked.connect(self.on_add_clicked)

        self.v_splitter = QSplitter(Qt.Vertical, self)
        self.v_splitter.addWidget(self.k_view)
        self.v_splitter.addWidget(self.v_view)
        self.v_splitter.addWidget(btn_widget)
        self.v_splitter.setStretchFactor(0, 3)
        self.v_splitter.setStretchFactor(1, 2)
        self.v_splitter.setStretchFactor(2, 1)

        layout = QVBoxLayout()
        # layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.v_splitter)
        self.setLayout(layout)
        # self.showMaximized()

        # 鼠标跟踪的十字线
        pen = QPen()
        pen.setStyle(Qt.DotLine)
        pen.setColor(QColor(0x40, 0x99, 0xF5))
        pen.setWidth(1)
        self.k_line_h = QGraphicsLineItem(self.k_view.chart())
        self.k_line_v = QGraphicsLineItem(self.k_view.chart())
        self.v_line_h = QGraphicsLineItem(self.v_view.chart())
        self.v_line_v = QGraphicsLineItem(self.v_view.chart())
        self.k_line_h.setPen(pen)
        self.k_line_v.setPen(pen)
        self.v_line_h.setPen(pen)
        self.v_line_v.setPen(pen)
        self.k_line_h.setZValue(100)
        self.k_line_v.setZValue(100)
        self.v_line_h.setZValue(100)
        self.v_line_v.setZValue(100)
        self.k_line_h.hide()
        self.k_line_v.hide()
        self.v_line_h.hide()
        self.v_line_v.hide()

        # 鼠标在图表的位置, 初始化在左上角
        self.tool_tip_widget = GraphicsProxyWidget(self.k_view.chart())
        self.k_zero_point = self.k_view.min_point()
        self.k_max_point = self.k_view.max_point()
        self.v_zero_point = self.v_view.min_point()
        self.v_max_point = self.v_view.max_point()
        self._hovered_pos_left = QPointF(self.k_zero_point.x(),
                                         self.k_max_point.y())
        self._hovered_pos_right = QPointF(
            self.k_max_point.x() - self.tool_tip_widget.width(),
            self.k_max_point.y())
        self._hovered_pos = self._hovered_pos_left
        # 鼠标在中心左边
        self._is_left = True

        # 事件过滤
        QApplication.instance().installEventFilter(self)

        # TODO: 向左右拖动图表能显示之前或之后的图表,且坐标跟着变化
        # TODO: 能标准成本线,能计算指定两个点的涨幅度

    def eventFilter(self, obj: QObject, event: QEvent):
        if event.type() == QEvent.MouseMove:
            pos = QMouseEvent(event).globalPos()
            k_chart_pos = self.k_view.chart().mapFromParent(
                self.k_view.mapFromGlobal(pos))
            v_chart_pos = self.v_view.chart().mapFromParent(
                self.v_view.mapFromGlobal(pos))
            self.k_zero_point = self.k_view.min_point()
            self.k_max_point = self.k_view.max_point()
            self.v_zero_point = self.v_view.min_point()
            self.v_max_point = self.v_view.max_point()
            self.update_lines(k_chart_pos, v_chart_pos)
        elif event.type() == QEvent.Resize and obj == self:
            self.update_margins()
            self.k_zero_point = self.k_view.min_point()
            self.k_max_point = self.k_view.max_point()

        return super(KVWidget, self).eventFilter(obj, event)

    def update_lines(self, k_chart_pos, v_chart_pos):
        """ 更新跟踪的十字线 """
        if (self.k_zero_point.y() >= k_chart_pos.y() >= self.k_max_point.y()) \
                and (self.k_zero_point.x() <= k_chart_pos.x() <= self.k_max_point.x()):
            self.k_line_h.setLine(self.k_zero_point.x(), k_chart_pos.y(),
                                  self.k_max_point.x(), k_chart_pos.y())
            self.k_line_v.setLine(k_chart_pos.x(), self.k_max_point.y(),
                                  k_chart_pos.x(), self.k_zero_point.y())
            self.v_line_v.setLine(v_chart_pos.x(), self.v_max_point.y(),
                                  v_chart_pos.x(), self.v_zero_point.y())
            self.k_line_h.show()
            self.k_line_v.show()
            self.v_line_v.show()
            self.v_line_h.hide()
            self._is_left = k_chart_pos.x() < (self.k_max_point.x() +
                                               self.k_zero_point.x()) / 2
        elif (self.v_zero_point.y() >= v_chart_pos.y() >= self.v_max_point.y()) \
                and (self.v_zero_point.x() <= v_chart_pos.x() <= self.v_max_point.x()):
            self.k_line_v.setLine(k_chart_pos.x(), self.k_max_point.y(),
                                  k_chart_pos.x(), self.k_zero_point.y())
            self.v_line_h.setLine(self.v_zero_point.x(), v_chart_pos.y(),
                                  self.k_max_point.x(), v_chart_pos.y())
            self.v_line_v.setLine(v_chart_pos.x(), self.v_max_point.y(),
                                  v_chart_pos.x(), self.v_zero_point.y())
            self.k_line_h.hide()
            self.k_line_v.show()
            self.v_line_v.show()
            self.v_line_h.show()
            self._is_left = v_chart_pos.x() < (self.v_max_point.x() +
                                               self.v_zero_point.x()) / 2
        else:
            self.k_line_h.hide()
            self.k_line_v.hide()
            self.v_line_v.hide()
            self.v_line_h.hide()

    def update_margins(self):
        margin_k = self.k_view.chart().margins()
        margin_v = self.v_view.chart().margins()
        width_k = self.k_view.chart().plotArea().width()
        width_v = self.v_view.chart().plotArea().width()
        sub = width_k - width_v
        if sub > 0:
            self.k_view.chart().setMargins(
                QMargins(margin_k.left() + sub, margin_k.top(),
                         margin_k.right(), margin_k.bottom()))
        else:
            self.v_view.chart().setMargins(
                QMargins(margin_v.left() - sub, margin_v.top(),
                         margin_v.right(), margin_v.bottom()))
        self.update()

    def on_clear_clicked(self):
        """ 清空图表 """
        self.k_view.clear_series_values()
        self.v_view.clear_series_value()
        self.k_view.set_name('')

    def on_add_clicked(self):
        """ 添加数据 """
        self.k_view.add_series_values(self.stocks)
        self.v_view.add_series_values(self.stocks)
        self.k_view.set_name(self.stocks['name'].iloc[0])
        self.update_margins()

    def on_series_hovered(self, status: bool, index_date: str):
        """ QCandlestickSeries的hovered的信号响应槽 """
        if status:
            self._hovered_pos_left = QPointF(self.k_zero_point.x(),
                                             self.k_max_point.y())
            self._hovered_pos_right = QPointF(
                self.k_max_point.x() - self.tool_tip_widget.width(),
                self.k_max_point.y())
            self._hovered_pos = self._hovered_pos_right if self._is_left else self._hovered_pos_left
            tip_value_df = self.stocks[self.stocks['trade_date'] == index_date]
            if tip_value_df.empty:
                return
            self.tool_tip_widget.show(index_date,
                                      str(tip_value_df['open'].iloc[0]),
                                      str(tip_value_df['close'].iloc[0]),
                                      str(tip_value_df['high'].iloc[0]),
                                      str(tip_value_df['low'].iloc[0]),
                                      str(tip_value_df['vol'].iloc[0]),
                                      self._hovered_pos)
        else:
            self.tool_tip_widget.hide()
Пример #13
0
class DetailChartView(QChartView):
    def __init__(self, *args, **kwargs):
        super(DetailChartView, self).__init__(*args, **kwargs)
        self.setRenderHint(QPainter.Antialiasing)
        self.setMaximumHeight(320)
        self.c_chart = None
        self.moved = False

    def linesInstallHoverEvent(self):
        for series in self.chart().series():
            series.hovered.connect(self.lines_hovered)  # 鼠标悬停信号连接
        if self.c_chart:
            # 线条对象
            self.line_item = QGraphicsLineItem(self.c_chart)
            # # 提示块
            self.tips_tool = GraphicsProxyWidget(self.c_chart)

            axis_X = self.c_chart.axisX()
            axis_Y = self.c_chart.axisY()
            self.min_x, self.max_x = axis_X.min(), axis_X.max()
            self.min_y, self.max_y = axis_Y.min(), axis_Y.max()
            self.moved = True  # 开启鼠标移动事件

    def lines_hovered(self, point, state):
        # 鼠标悬停信号槽函数:state表示鼠标是否在线上(布尔值)
        series = self.sender()  # 获取获得鼠标信号的那条线
        pen = series.pen()
        if not pen:
            return
        pen.setWidth(pen.width() + (1 if state else -1))
        series.setPen(pen)

    def barsInstallHoverEvent(self):
        self.moved = False  # 关闭鼠标移动事件
        for series in self.chart().series():
            # print(series, type(series))
            for bar in series.barSets():
                bar.hovered.connect(self.bars_hovered)  # 鼠标悬停信号连接

    def bars_hovered(self, status, index):
        # status 是否在柱子上
        # index 第几组柱形图
        bar = self.sender()
        pen = bar.pen()
        if not pen:
            return
        pen.setColor(bar.color())
        pen.setWidth(pen.width() + (1 if status else -1))
        bar.setPen(pen)

    def setChart(self, chart):
        super(DetailChartView, self).setChart(chart)
        self.c_chart = chart

    def mouseMoveEvent(self, event):
        super(DetailChartView, self).mouseMoveEvent(event)  # 原先的hover事件
        if self.c_chart and self.moved:
            pos = event.pos()
            # 鼠标位置转为坐标点
            x, y = self.c_chart.mapToValue(pos).x(), self.c_chart.mapToValue(
                pos).y()
            index = round((x - self.min_x) / self.step_x)
            points = [(series, series.at(index))
                      for series in self.c_chart.series()
                      if self.min_x <= x <= self.max_x
                      and self.min_y <= y <= self.max_y]
            if points:
                pos_x = self.c_chart.mapToPosition(
                    QPointF(index * self.step_x + self.min_x,
                            self.min_y))  # 算出当前鼠标所在的x位置
                # 自定义指示线
                self.line_item.setLine(pos_x.x(), self.point_top.y(),
                                       pos_x.x(), self.point_bottom.y())
                self.line_item.show()
                try:
                    title = self.c_chart.x_labels[index]
                except Exception:
                    title = ''
                tips_width = self.tips_tool.width()
                tips_height = self.tips_tool.height()
                # 如果鼠标位置离右侧的距离小于tip宽度
                x = pos.x() - tips_width if self.width() - pos.x(
                ) - 20 < tips_width else pos.x()
                # 如果鼠标位置离底部的高度小于tip高度
                y = pos.y() - tips_height if self.height() - pos.y(
                ) - 20 < tips_height else pos.y()
                # print(title, points, QPoint(x, y))
                self.tips_tool.show(title, points, QPoint(x, y))
            else:
                self.tips_tool.hide()
                self.line_item.hide()

        # pos = event.pos()
        # # 鼠标位置转为坐标点
        # x, y = self.c_chart.mapToValue(pos).x(), self.c_chart.mapToValue(pos).y()
        # if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y:
        #     points = []
        #     for series in self.c_chart.series():
        #         for point in series.pointsVector():
        #             if QDateTime.fromMSecsSinceEpoch(point.x()).date() == QDateTime.fromMSecsSinceEpoch(x).date():
        #                 points.append((series, point))
        #                 break
        #     if points:
        #         pos_x = self.c_chart.mapToPosition(QPointF(x, self.min_y))  # 算出当前鼠标所在的x位置
        #         # 自定义指示线
        #         self.line_item.setLine(pos_x.x(), self.point_top.y(),
        #                                pos_x.x(), self.point_bottom.y())
        #         self.line_item.show()
        #         try:
        #             title = QDateTime.fromMSecsSinceEpoch(x).toString("yyyy-MM-dd")
        #         except Exception:
        #             title = ''
        #         tips_width = self.tips_tool.width()
        #         tips_height = self.tips_tool.height()
        #         # 如果鼠标位置离右侧的距离小于tip宽度
        #         x = pos.x() - tips_width if self.width() - \
        #                                     pos.x() - 20 < tips_width else pos.x()
        #         # 如果鼠标位置离底部的高度小于tip高度
        #         y = pos.y() - tips_height if self.height() - \
        #                                      pos.y() - 20 < tips_height else pos.y()
        #         # print(title, points, QPoint(x, y))
        #         self.tips_tool.show(
        #             title, points, QPoint(x, y))
        # else:
        #     self.tips_tool.hide()
        #     self.line_item.hide()

    def resizeEvent(self, event):
        super(DetailChartView, self).resizeEvent(event)
        if self.c_chart and self.moved:
            # 当窗口大小改变时需要重新计算
            # 坐标系中左上角顶点
            self.point_top = self.c_chart.mapToPosition(
                QPointF(self.min_x, self.max_y))
            # 坐标原点坐标
            self.point_bottom = self.c_chart.mapToPosition(
                QPointF(self.min_x, self.min_y))
            self.step_x = 1  # 步长取1 每个数据都能显示
Пример #14
0
class KLineChartView(QChartView):
    def __init__(self):
        super(KLineChartView, self).__init__()
        self.setRenderHint(QPainter.Antialiasing)  # 抗锯齿
        self._chart = QChart(title='蜡烛图悬浮提示')
        self.stocks = read_tick_data()
        self.category = [
            trade_date[4:] for trade_date in self.stocks['trade_date']
        ]
        self._count = len(self.category)
        self.resize(800, 300)

        self.init_chart()

        self.toolTipWidget = GraphicsProxyWidget(self._chart)

        # 鼠标跟踪的十字线
        self.lineItem_h = QGraphicsLineItem(self._chart)
        self.lineItem_v = QGraphicsLineItem(self._chart)
        pen = QPen()
        pen.setStyle(Qt.DotLine)
        pen.setColor(QColor(Qt.gray))
        pen.setWidth(2)
        self.lineItem_h.setPen(pen)
        self.lineItem_v.setPen(pen)
        self.lineItem_h.setZValue(100)
        self.lineItem_v.setZValue(100)
        self.lineItem_h.hide()
        self.lineItem_v.hide()

        # 坐标轴上最大最小的值
        # x 轴是
        self.min_x, self.max_x = 0, len(self._chart.axisX().categories())
        self.min_y, self.max_y = self._chart.axisY().min(), self._chart.axisY(
        ).max()
        # y 轴最高点坐标
        self.point_y_max = self._chart.mapToPosition(
            QPointF(self.min_x, self.max_y))
        # x 轴最高点坐标
        self.point_x_max = self._chart.mapToPosition(
            QPointF(self.max_x, self.min_y))
        # self.point_x_min = self._chart.mapToPosition(QPointF(self.min_x, self.min_y))

        # 计算x轴单个cate的宽度,用来处理横线不能画到边界
        self.x_width = (self.point_x_max.x() - self.point_y_max.x()) / len(
            self.category)
        self.x_x_min = self.point_y_max.x() - self.x_width / 2
        self.x_x_max = self.point_x_max.x() - self.x_width / 2

        # 中间位置,用来判断TipWidget放在哪里
        mid_date = self.stocks['trade_date'].iloc[len(
            self.stocks['trade_date']) // 2]
        self.mid_x = float(
            time.mktime(
                datetime.datetime.strptime(str(mid_date),
                                           '%Y%m%d').timetuple()))
        self.left_pos = self.point_y_max
        self.right_pos = self._chart.mapToPosition(
            QPointF(self.max_x, self.max_y))

    def mouseMoveEvent(self, event):
        super(KLineChartView, self).mouseMoveEvent(event)
        pos = event.pos()
        if self.x_x_min < pos.x() < self.x_x_max \
                and self.point_x_max.y() > pos.y() > self.point_y_max.y():
            self.lineItem_h.setLine(self.x_x_min, pos.y(), self.x_x_max,
                                    pos.y())
            self.lineItem_v.setLine(pos.x(), self.point_y_max.y(), pos.x(),
                                    self.point_x_max.y())
            self.lineItem_h.show()
            self.lineItem_v.show()
        else:
            self.lineItem_h.hide()
            self.lineItem_v.hide()

    def resizeEvent(self, event):
        super(KLineChartView, self).resizeEvent(event)
        # y 轴最高点坐标
        self.point_y_max = self._chart.mapToPosition(
            QPointF(self.min_x, self.max_y))
        # x 轴最高点坐标
        self.point_x_max = self._chart.mapToPosition(
            QPointF(self.max_x, self.min_y))
        # 计算x轴单个cate的宽度,用来处理横线不能画到边界
        self.x_width = (self.point_x_max.x() - self.point_y_max.x()) / len(
            self.category)
        self.x_x_min = self.point_y_max.x() - self.x_width / 2
        self.x_x_max = self.point_x_max.x() - self.x_width / 2
        self.left_pos = self.point_y_max
        self.right_pos = self._chart.mapToPosition(
            QPointF(self.max_x, self.max_y))

    def init_chart(self):
        self._chart.setAnimationOptions(QChart.SeriesAnimations)
        series = QCandlestickSeries()
        series.setIncreasingColor(QColor(Qt.red))
        series.setDecreasingColor(QColor(Qt.green))
        series.setName(self.stocks['name'].iloc[0])
        for _, stock in self.stocks.iterrows():
            time_p = datetime.datetime.strptime(stock['trade_date'], '%Y%m%d')
            time_p = float(time.mktime(time_p.timetuple()))
            _set = QCandlestickSet(float(stock['open']), float(stock['high']),
                                   float(stock['low']), float(stock['close']),
                                   time_p, series)
            _set.hovered.connect(self.handleBarHoverd)  # 鼠标悬停
            series.append(_set)
        self._chart.addSeries(series)

        self._chart.createDefaultAxes()
        self._chart.setLocalizeNumbers(True)
        axis_x = self._chart.axisX()
        axis_y = self._chart.axisY()
        axis_x.setGridLineVisible(False)
        axis_y.setGridLineVisible(False)
        axis_x.setCategories(self.category)
        max_p = self.stocks[['high', 'low']].stack().max() + 10
        min_p = self.stocks[['high', 'low']].stack().min() - 10
        axis_y.setRange(min_p, max_p)

        # chart的图例
        legend = self._chart.legend()
        # 设置图例由Series来决定样式
        legend.setMarkerShape(QLegend.MarkerShapeFromSeries)

        self.setChart(self._chart)
        # 设置外边界全部为0
        self._chart.layout().setContentsMargins(0, 0, 0, 0)
        # 设置内边界都为0
        # self._chart.setMargins(QMargins(0, 0, 0, 0))
        # 设置背景区域无圆角
        self._chart.setBackgroundRoundness(0)

    def handleBarHoverd(self, status):
        """ 改变画笔的风格 """
        bar = self.sender()  # 信号发送者
        pen = bar.pen()
        if not pen:
            return
        pen.setStyle(Qt.DotLine if status else Qt.SolidLine)
        bar.setPen(pen)
        if status:
            # 通过 bar 可以获取横轴坐标(timestamp)和纵轴坐标(high)
            # 然后将坐标值转换为位置,显示 TipWidget 的位置
            right_pos = QPointF(
                self.right_pos.x() - self.toolTipWidget.width() - self.x_width,
                self.right_pos.y())
            pos = self.left_pos if bar.timestamp() > self.mid_x else right_pos
            trade_date = time.strftime('%Y%m%d',
                                       time.localtime(bar.timestamp()))
            self.toolTipWidget.show(str(trade_date), str(bar.open()),
                                    str(bar.close()), str(bar.high()),
                                    str(bar.low()), pos)
        else:
            self.toolTipWidget.hide()
Пример #15
0
class LineChartView(QChartView):
    def __init__(self):
        super(LineChartView, self).__init__()
        self.resize(800, 600)
        self.setRenderHint(QPainter.Antialiasing)  # 抗锯齿
        # 自定义x轴label

    def CreatePlot(self, NumSection, Alltime, PlotData, type, name='列车调度'):
        if type == 1:
            self.initChart(NumSection, Alltime, PlotData, name)
        else:
            self.initProgressChart(NumSection, Alltime, PlotData, name)

        # 提示widget
        self.toolTipWidget = GraphicsProxyWidget(self._chart)

        # line
        self.lineItem = QGraphicsLineItem(self._chart)
        pen = QPen(Qt.gray)
        pen.setWidth(1)
        self.lineItem.setPen(pen)
        self.lineItem.setZValue(998)
        #self.lineItem.hide()
        self.lineItem.show()
        # 一些固定计算,减少mouseMoveEvent中的计算量
        # 获取x和y轴的最小最大值
        axisX, axisY = self._chart.axisX(), self._chart.axisY()
        self.min_x, self.max_x = axisX.min(), axisX.max()
        self.min_y, self.max_y = axisY.min(), axisY.max()

    def resizeEvent(self, event):
        super(LineChartView, self).resizeEvent(event)
        # 当窗口大小改变时需要重新计算
        # 坐标系中左上角顶点
        self.point_top = self._chart.mapToPosition(
            QPointF(self.min_x, self.max_y))
        # 坐标原点坐标
        self.point_bottom = self._chart.mapToPosition(
            QPointF(self.min_x, self.min_y))
        self.step_x = (self.max_x -
                       self.min_x) / (self._chart.axisX().tickCount() - 1)

    def mouseMoveEvent(self, event):
        super(LineChartView, self).mouseMoveEvent(event)
        pos = event.pos()
        # 把鼠标位置所在点转换为对应的xy值
        x = self._chart.mapToValue(pos).x()
        y = self._chart.mapToValue(pos).y()
        index = round(int(x / self.step_x) * self.step_x) + round(
            x % self.step_x) - 1
        #index = round((x - self.min_x) / self.step_x)
        # 得到在坐标系中的所有正常显示的series的类型和点
        points = [
            (serie, serie.at(index)) for serie in self._chart.series()
            if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y
        ]
        if points:
            pos_x = self._chart.mapToPosition(
                QPointF(index * self.step_x + self.min_x, self.min_y))
            self.lineItem.setLine(pos_x.x(), self.point_top.y(), pos_x.x(),
                                  self.point_bottom.y())
            self.lineItem.show()
            try:
                title = ""
            except:
                title = ""
            t_width = self.toolTipWidget.width()
            t_height = self.toolTipWidget.height()
            # 如果鼠标位置离右侧的距离小于tip宽度
            x = pos.x() - t_width if self.width() - \
                                     pos.x() - 20 < t_width else pos.x()
            # 如果鼠标位置离底部的高度小于tip高度
            y = pos.y() - t_height if self.height() - \
                                      pos.y() - 20 < t_height else pos.y()
            self.toolTipWidget.show(title, points, QPoint(x, y))
        else:
            self.toolTipWidget.hide()
            self.lineItem.hide()

    def handleMarkerClicked(self):
        marker = self.sender()  # 信号发送者
        if not marker:
            return
        visible = not marker.series().isVisible()
        #         # 隐藏或显示series
        marker.series().setVisible(visible)
        marker.setVisible(True)  # 要保证marker一直显示
        # 透明度
        alpha = 1.0 if visible else 0.4
        # 设置label的透明度
        brush = marker.labelBrush()
        color = brush.color()
        color.setAlphaF(alpha)
        brush.setColor(color)
        marker.setLabelBrush(brush)
        # 设置marker的透明度
        brush = marker.brush()
        color = brush.color()
        color.setAlphaF(alpha)
        brush.setColor(color)
        marker.setBrush(brush)
        # 设置画笔透明度
        pen = marker.pen()
        color = pen.color()
        color.setAlphaF(alpha)
        pen.setColor(color)
        marker.setPen(pen)

    def handleMarkerHovered(self, status):
        # 设置series的画笔宽度
        marker = self.sender()  # 信号发送者
        if not marker:
            return
        series = marker.series()
        if not series:
            return
        pen = series.pen()
        if not pen:
            return
        pen.setWidth(pen.width() + (1 if status else -1))
        series.setPen(pen)

    def handleSeriesHoverd(self, point, state):
        # 设置series的画笔宽度
        series = self.sender()  # 信号发送者
        pen = series.pen()
        if not pen:
            return
        pen.setWidth(pen.width() + (1 if state else -1))
        series.setPen(pen)

    def initProgressChart(self, MaxTime, Allgen, PlotData, name):
        MinTime = 100000
        for k in PlotData:
            if min(k[1]) < MinTime:
                MinTime = min(k[1])
        self._chart = QChart(title=name)
        self._chart.setAcceptHoverEvents(True)
        # Series动画

        self._chart.setAnimationOptions(QChart.SeriesAnimations)
        # dataTable = [
        #     ["邮件营销", [120, 132, 101, 134, 90, 230]],
        #     ["联盟广告", [220, 182, 191, 234, 290, 330, 310]],
        #     ["视频广告", [150, 232, 201, 154, 190, 330, 410]],
        #     ["直接访问", [320, 332, 301, 334, 390, 330, 320]],
        #     ["搜索引擎", [820, 932, 901, 934, 1290, 1330, 1320]]
        # ]
        for series_name, data_list, label_list in PlotData:
            series = QLineSeries(self._chart)
            for j, v in zip(label_list, data_list):
                series.append(j, v)
            series.setName(series_name)
            series.setPointsVisible(True)  # 显示圆点
            series.hovered.connect(self.handleSeriesHoverd)  # 鼠标悬停
            self._chart.addSeries(series)
        self._chart.createDefaultAxes()  # 创建默认的轴
        axisX = self._chart.axisX()  # x轴
        axisX.setTickCount(11)  # x轴设置7个刻度
        axisX.setGridLineVisible(False)  # 隐藏从x轴往上的线条
        axisY = self._chart.axisY()
        axisY.setTickCount(10)  # y轴设置7个刻度
        #axisY.setRange(0, MaxTime)  # 设置y轴范围
        # 自定义x轴
        # axis_x = QCategoryAxis(
        #     self._chart, labelsPosition=QCategoryAxis.AxisLabelsPositionOnValue)
        # axis_x.setTickCount(Allgen)
        # axis_x.setGridLineVisible(False)
        # min_x = axisX.min()
        # for i in range(0, Allgen + 1):
        #     axis_x.append(str(i), min_x + i)
        # self._chart.setAxisX(axis_x, self._chart.series()[-1])

        # 自定义y轴
        # axis_y = QCategoryAxis(
        #     self._chart, labelsPosition=QCategoryAxis.AxisLabelsPositionOnValue)
        # axis_y.setTickCount(round(MaxTime)-max(round(MinTime)-10,0))
        # axis_y.setRange(max(round(MinTime)-10,0), round(MaxTime))
        # for i in range(max(round(MinTime)-10,0), round(MaxTime) + 1):
        #     axis_y.append('%i' % i, i)
        # self._chart.setAxisY(axis_y, self._chart.series()[-1])
        # chart的图例
        legend = self._chart.legend()
        # 设置图例由Series来决定样式
        legend.setMarkerShape(QLegend.MarkerShapeFromSeries)
        # 遍历图例上的标记并绑定信号
        for marker in legend.markers():
            # 点击事件
            marker.clicked.connect(self.handleMarkerClicked)
            # 鼠标悬停事件
            marker.hovered.connect(self.handleMarkerHovered)
        self.setChart(self._chart)

    def initChart(self, NumSection, Alltime, PlotData, name):
        self._chart = QChart(title=name)
        self._chart.setAcceptHoverEvents(True)
        # Series动画
        self._chart.setAnimationOptions(QChart.SeriesAnimations)
        # dataTable = [
        #     ["邮件营销", [120, 132, 101, 134, 90, 230]],
        #     ["联盟广告", [220, 182, 191, 234, 290, 330, 310]],
        #     ["视频广告", [150, 232, 201, 154, 190, 330, 410]],
        #     ["直接访问", [320, 332, 301, 334, 390, 330, 320]],
        #     ["搜索引擎", [820, 932, 901, 934, 1290, 1330, 1320]]
        # ]
        for series_name, data_list, label_list in PlotData:
            series = QLineSeries(self._chart)
            for j, v in zip(label_list, data_list):
                series.append(j, v)
            series.setName(series_name)
            series.setPointsVisible(True)  # 显示圆点
            series.hovered.connect(self.handleSeriesHoverd)  # 鼠标悬停
            self._chart.addSeries(series)
        self._chart.createDefaultAxes()  # 创建默认的轴
        axisX = self._chart.axisX()  # x轴
        axisX.setTickCount(Alltime)  # x轴设置7个刻度
        axisX.setGridLineVisible(False)  # 隐藏从x轴往上的线条
        axisY = self._chart.axisY()
        axisY.setTickCount(NumSection)  # y轴设置7个刻度
        axisY.setRange(0, NumSection)  # 设置y轴范围
        # 自定义x轴
        axis_x = QCategoryAxis(
            self._chart,
            labelsPosition=QCategoryAxis.AxisLabelsPositionOnValue)
        axis_x.setTickCount(Alltime + 1)
        axis_x.setGridLineVisible(False)
        min_x = axisX.min()
        for i in range(0, Alltime + 1):
            axis_x.append(str(i), min_x + i)
        self._chart.setAxisX(axis_x, self._chart.series()[-1])

        # 自定义y轴
        axis_y = QCategoryAxis(
            self._chart, labelsPosition=QCategoryAxis.AxisLabelsPositionCenter)
        axis_y.setTickCount(NumSection)
        axis_y.setRange(0, NumSection)
        for i in range(0, NumSection + 1):
            axis_y.append('section%i' % (NumSection - i + 1), i)
        self._chart.setAxisY(axis_y, self._chart.series()[-1])
        # chart的图例
        legend = self._chart.legend()
        # 设置图例由Series来决定样式
        legend.setMarkerShape(QLegend.MarkerShapeFromSeries)
        # 遍历图例上的标记并绑定信号
        for marker in legend.markers():
            # 点击事件
            marker.clicked.connect(self.handleMarkerClicked)
            # 鼠标悬停事件
            marker.hovered.connect(self.handleMarkerHovered)
        self.setChart(self._chart)