在 QGraphicsItem 中使用 QPainterPath 进行鼠标命中检测

Mouse hit detection with QPainterPath in QGraphicsItem

我正在实施自定义图表。但我坚持使用 QPainterPath 进行鼠标命中检测。

我尝试使用 graphicsitem 的 shape()、boundingRect()。但这只会检查边界的粗略形状。

我想检查鼠标命中系统在 QPainterPath 路径实例上的确切位置。但似乎没有 api 喜欢那个功能。

我的应用程序的 QGraphicsScene 在视图的 resizeEvent() 中设置为与 QGraphicsView 相同的坐标。

scene: MyScene = self.scene()
scene.setSceneRect(self.rect().x(), self.rect().y(),
                   self.rect().width(), self.rect().height())

同时,我的绘图 QGraphicsItem 通过 QTransform 缩放。

plot: QGraphicsItem = scene.plot
trans = QTransform()
data = plot.df['data']
data = data - data.min()
data_max = data.max()
data_min = data.min()
trans.scale(self.width() / len(data),
            self.height() / (data_max - data_min))
plot.trans = trans
plot.setTransform(trans)

然后在 MyScene 中,添加矩形项 mouse_rec。所以,我用 mouse_rec.collidesWithPath(path)

检查 mouse_recplot 项目的路径

它只适用于原始路径。

这是所有代码。只需复制粘贴即可运行。

红色图为原始路径,黄色图为缩放路径。鼠标命中检查仅适用于红色图...

import numpy
import pandas

from PyQt5 import QtGui
from PyQt5.QtCore import Qt, QRectF, QRect
from PyQt5.QtGui import QRadialGradient, QGradient, QPen, QPainterPath, QTransform, QPainter, QColor
from PyQt5.QtWidgets import QApplication, QGraphicsScene, QGraphicsView, QGraphicsSceneMouseEvent, QGraphicsItem, \
    QStyleOptionGraphicsItem, QWidget, QGraphicsRectItem


class MyItem(QGraphicsItem):
    def __init__(self, df, parent=None):
        QGraphicsItem.__init__(self, parent)
        self.num = 1
        self.df = df
        self.path = QPainterPath()
        self.trans = QTransform()
        self.cached = False
        self.printed = False
        self.setZValue(0)

    def paint(self, painter: QtGui.QPainter, option: 'QStyleOptionGraphicsItem', widget: QWidget = ...):
        data = self.df['data']
        data = data - data.min()
        data_max = data.max()
        data_min = data.min()

        if not self.cached:
            for i in range(data.size - 1):
                self.path.moveTo(i, data[i])
                self.path.lineTo(i+1, data[i+1])

            self.cached = True

        pen = QPen(Qt.white)
        pen.setCosmetic(True)
        painter.setPen(pen)
        painter.drawRect(0, 0, data.size, data_max - data_min)

        pen.setColor(Qt.yellow)
        painter.setPen(pen)
        painter.drawPath(self.path)

        if not self.printed:
            rec_item = self.scene().addPath(self.path, QPen(Qt.red))
            rec_item.setZValue(-10)
            self.printed = True

    def boundingRect(self):
        data = self.df['data']
        data_max = data.max()
        data_min = data.min()

        return QRectF(0, 0, data.size, data_max - data_min)


class MyScene(QGraphicsScene):
    def __init__(self, data, parent=None):
        QGraphicsScene.__init__(self, parent)
        self.data = data
        self.mouse_rect = QGraphicsRectItem()
        self.plot: MyItem(data) = None
        self.bounding_rect = QGraphicsRectItem()
        self.setBackgroundBrush(QColor('#14161f'))

        self.addItem(self.bounding_rect)
        self.printed = False

    def mouseMoveEvent(self, event: 'QGraphicsSceneMouseEvent'):
        print()

        print("rec rect : ", self.mouse_rect.rect())
        print("Scene rect : ", self.sceneRect())
        print("ItemBounding rect : ", self.itemsBoundingRect())
        print("transform : ", self.plot.transform().m11(), ", ", self.plot.transform().m22())
        item = self.itemAt(event.scenePos(), self.plot.transform())

        if item and isinstance(item, MyItem):
            print()
            print('collides path : ', self.mouse_rect.collidesWithPath(item.path))
            print('collides item : ', self.mouse_rect.collidesWithItem(item))

        super().mouseMoveEvent(event)

    def print_bound(self, rect):
        self.bounding_rect.setPen(QPen(Qt.green))
        self.bounding_rect.setRect(rect.x() + 5, rect.y() + 5,
                                   rect.width() - 10, rect.height() - 10)


class MyView(QGraphicsView):
    def __init__(self, data, parent=None):
        QGraphicsView.__init__(self, parent)
        self.data = data
        self.setMouseTracking(True)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

    def wheelEvent(self, event: QtGui.QWheelEvent):
        print("pixel / Data : {}".format(self.width() / len(self.data)))

    def resizeEvent(self, event: QtGui.QResizeEvent):
        scene: MyScene = self.scene()
        scene.setSceneRect(self.rect().x(), self.rect().y(),
                           self.rect().width(), self.rect().height())

        scene.print_bound(self.rect())

        plot: QGraphicsItem = scene.plot
        trans = QTransform()
        data = plot.df['data']
        data = data - data.min()
        data_max = data.max()
        data_min = data.min()
        trans.scale(self.width() / len(data),
                    self.height() / (data_max - data_min))
        plot.trans = trans
        plot.setTransform(trans)

    def mouseMoveEvent(self, event: QtGui.QMouseEvent):
        mouse_rect: QGraphicsRectItem = self.scene().mouse_rect
        mouse_rect.setRect(event.pos().x() - 2, event.pos().y() - 2, 4, 4)

        super().mouseMoveEvent(event)


if __name__ == '__main__':
    df = pandas.DataFrame({'data': numpy.random.randint(0, 20, 50)})

    app = QApplication([])
    scene = MyScene(df)
    view = MyView(df)
    view.setScene(scene)

    rec = QGraphicsRectItem(-2, -2, 4, 4)
    rec.setPen(Qt.white)
    scene.mouse_rect = rec
    scene.addItem(rec)

    plot = MyItem(df)
    scene.addItem(plot)
    scene.plot = plot

    view.show()

    app.exec_()

知道用路径检查鼠标点吗??我首先尝试自定义数学函数计算 [point <-> line] 距离,但这需要很多时间并且制作滞后的应用程序..

我不仅会画线图,还会画条形图、面积图、点图、烛台图..有解决这个问题的想法吗?

您必须使用 mapToScene():

将路径相对于缩放的项目的位置转换为相对于场景的位置
if item and isinstance(item, MyItem):
    print('collides path : ', self.mouse_rect.collidesWithPath(item.mapToScene(item.path)))
    print('collides item : ', self.mouse_rect.collidesWithItem(item))