为了账号安全,请及时绑定邮箱和手机立即绑定

Python (PyQt5) 版本的 Qt Callout 示例

Python (PyQt5) 版本的 Qt Callout 示例

MMMHUHU 2022-07-26 20:44:49
标注示例来自 QtCharts 包:https ://doc.qt.io/qt-5/qtcharts-callout-example.html这个(优秀的)示例展示了如何在图表顶部绘制附加元素(标注)。问题是这个例子是否可以在 python 实现的某个地方找到?
查看完整描述

1 回答

?
蛊毒传说

TA贡献1895条经验 获得超3个赞

     我一直在寻找移植到 PyQt5 (python) 的示例,但没有找到。所以我自己写的,想和大家分享一下(只是为了节省你的时间)。它是为 Python 3.8 编写的,但也适用于旧版本,PyQt 版本是 5.14.1。


与原始的 C++ 实现只有一点点不同。QGraphicsScene必须显式设置 - 在View构造函数类中,并且每个新Callout对象都必须显式添加到场景中。坦率地说,我不知道为什么,但我不在乎。C++ 中完全相同的 Qt 库版本不需要。


import sys

from typing import List


from PyQt5.QtChart import QChart, QLineSeries, QSplineSeries

from PyQt5.QtCore import QPointF, QRect, QRectF, QSizeF, Qt

from PyQt5.QtGui import QColor, QFont, QFontMetrics, QMouseEvent, QPainter, QPainterPath, QResizeEvent

from PyQt5.QtWidgets import QApplication, QGraphicsItem, QGraphicsScene, QGraphicsSceneMouseEvent, \

    QGraphicsSimpleTextItem, QGraphicsView, QStyleOptionGraphicsItem, QWidget



class Callout(QGraphicsItem):


    def __init__(self, parent: QChart):

        super().__init__()

        self.m_chart: QChart = parent

        self.m_text: str = ''

        self.m_anchor: QPointF = QPointF()

        self.m_font: QFont = QFont()

        self.m_textRect: QRectF = QRectF()

        self.m_rect: QRectF = QRectF()


    def setText(self, text: str):

        self.m_text = text

        metrics = QFontMetrics(self.m_font)

        self.m_textRect = QRectF(metrics.boundingRect(QRect(0, 0, 150, 150), Qt.AlignLeft, self.m_text))

        self.m_textRect.translate(5, 5)

        self.prepareGeometryChange()

        self.m_rect = QRectF(self.m_textRect.adjusted(-5, -5, 5, 5))

        self.updateGeometry()


    def updateGeometry(self):

        self.prepareGeometryChange()

        self.setPos(self.m_chart.mapToPosition(self.m_anchor) + QPointF(10, -50))


    def boundingRect(self) -> QRectF:

        from_parent = self.mapFromParent(self.m_chart.mapToPosition(self.m_anchor))

        anchor = QPointF(from_parent)

        rect = QRectF()

        rect.setLeft(min(self.m_rect.left(), anchor.x()))

        rect.setRight(max(self.m_rect.right(), anchor.x()))

        rect.setTop(min(self.m_rect.top(), anchor.y()))

        rect.setBottom(max(self.m_rect.bottom(), anchor.y()))

        return rect


    def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem, widget: QWidget):

        path = QPainterPath()

        mr = self.m_rect

        path.addRoundedRect(mr, 5, 5)


        anchor = QPointF(self.mapFromParent(self.m_chart.mapToPosition(self.m_anchor)))

        if not mr.contains(anchor):

            point1 = QPointF()

            point2 = QPointF()


            # establish the position of the anchor point in relation to self.m_rect

            above = anchor.y() <= mr.top()

            above_center = mr.top() < anchor.y() <= mr.center().y()

            below_center = mr.center().y() < anchor.y() <= mr.bottom()

            below = anchor.y() > mr.bottom()


            on_left = anchor.x() <= mr.left()

            left_of_center = mr.left() < anchor.x() <= mr.center().x()

            right_of_center = mr.center().x() < anchor.x() <= mr.right()

            on_right = anchor.x() > mr.right()


            # get the nearest self.m_rect corner.

            x = (on_right + right_of_center) * mr.width()

            y = (below + below_center) * mr.height()

            corner_case = (above and on_left) or (above and on_right) or (below and on_left) or (below and on_right)

            vertical = abs(anchor.x() - x) > abs(anchor.y() - y)

            horizontal = bool(not vertical)


            x1 = x + left_of_center * 10 - right_of_center * 20 + corner_case * horizontal * (

                    on_left * 10 - on_right * 20)

            y1 = y + above_center * 10 - below_center * 20 + corner_case * vertical * (above * 10 - below * 20)

            point1.setX(x1)

            point1.setY(y1)


            x2 = x + left_of_center * 20 - right_of_center * 10 + corner_case * horizontal * (

                    on_left * 20 - on_right * 10)

            y2 = y + above_center * 20 - below_center * 10 + corner_case * vertical * (above * 20 - below * 10)

            point2.setX(x2)

            point2.setY(y2)


            path.moveTo(point1)

            path.lineTo(anchor)

            path.lineTo(point2)

            path = path.simplified()


        painter.setPen(QColor(30, 30, 30))

        painter.setBrush(QColor(255, 255, 255))

        painter.drawPath(path)

        painter.drawText(self.m_textRect, self.m_text)


    def mousePressEvent(self, event: QGraphicsSceneMouseEvent):

        event.setAccepted(True)


    def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent):

        if event.buttons() & Qt.LeftButton:

            self.setPos(self.mapToParent(event.pos() - event.buttonDownPos(Qt.LeftButton)))

            event.setAccepted(True)

        else:

            event.setAccepted(False)



class View(QGraphicsView):


    def __init__(self, parent=None):

        super().__init__(parent)

        self.m_callouts: List[Callout] = []

        self.setDragMode(QGraphicsView.NoDrag)

        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)


        # chart

        self.m_chart = QChart(parent)

        self.m_chart.setMinimumSize(640, 480)

        self.m_chart.setTitle("Hover the line to show callout. Click the line to make it stay")

        self.m_chart.legend().hide()

        series = QLineSeries()

        series.append(1, 3)

        series.append(4, 5)

        series.append(5, 4.5)

        series.append(7, 1)

        series.append(11, 2)

        self.m_chart.addSeries(series)


        series2 = QSplineSeries()

        series2.append(1.6, 1.4)

        series2.append(2.4, 3.5)

        series2.append(3.7, 2.5)

        series2.append(7, 4)

        series2.append(10, 2)

        self.m_chart.addSeries(series2)


        self.m_chart.createDefaultAxes()

        self.m_chart.setAcceptHoverEvents(True)


        self.setRenderHint(QPainter.Antialiasing)


        self.setScene(QGraphicsScene())

        self.scene().addItem(self.m_chart)


        self.m_coordX = QGraphicsSimpleTextItem(self.m_chart)

        self.m_coordX.setPos(self.m_chart.size().width() / 2 - 50, self.m_chart.size().height() - 20)

        self.m_coordX.setText("X: ")

        self.m_coordY = QGraphicsSimpleTextItem(self.m_chart)

        self.m_coordY.setPos(self.m_chart.size().width() / 2 + 50, self.m_chart.size().height() - 20)

        self.m_coordY.setText("Y: ")


        self.m_tooltip = Callout(self.m_chart)

        self.scene().addItem(self.m_tooltip)


        series.clicked.connect(self.keep_callout)

        series.hovered.connect(self.tooltip)

        series2.clicked.connect(self.keep_callout)

        series2.hovered.connect(self.tooltip)


        self.setMouseTracking(True)


    def resizeEvent(self, event: QResizeEvent):

        if scene := self.scene():

            scene.setSceneRect(QRectF(QPointF(0, 0), QSizeF(event.size())))

            self.m_chart.resize(QSizeF(event.size()))

            self.m_coordX.setPos(self.m_chart.size().width() / 2 - 50, self.m_chart.size().height() - 20)

            self.m_coordY.setPos(self.m_chart.size().width() / 2 + 50, self.m_chart.size().height() - 20)


            for callout in self.m_callouts:

                callout.updateGeometry()


        super().resizeEvent(event)


    def mouseMoveEvent(self, event: QMouseEvent):

        from_chart = self.m_chart.mapToValue(event.pos())

        self.m_coordX.setText(f"X: {from_chart.x()}")

        self.m_coordX.setText(f"Y: {from_chart.y()}")

        super().mouseMoveEvent(event)


    def keep_callout(self):

        self.m_callouts.append(self.m_tooltip)

        self.m_tooltip = Callout(self.m_chart)

        self.scene().addItem(self.m_tooltip)


    def tooltip(self, point: QPointF, state: bool):

        if not self.m_tooltip:

            self.m_tooltip = Callout(self.m_chart)


        if state:

            self.m_tooltip.setText(f"X: {point.x()} \nY: {point.x()} ")

            self.m_tooltip.m_anchor = point

            self.m_tooltip.setZValue(11)

            self.m_tooltip.updateGeometry()

            self.m_tooltip.show()

        else:

            self.m_tooltip.hide()



if __name__ == '__main__':

    app = QApplication(sys.argv)

    window = View()

    window.resize(640, 480)

    window.show()

    sys.exit(app.exec_())


查看完整回答
反对 回复 2022-07-26
  • 1 回答
  • 0 关注
  • 164 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号