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

Posted

技术标签:

【中文标题】在 QGraphicsItem 中使用 QPainterPath 检测鼠标命中【英文标题】:Mouse hit detection with QPainterPath in QGraphicsItem 【发布时间】:2018-12-10 11:54:41 【问题描述】:

我实现了自定义图表。但我坚持使用 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 中,添加 rect item 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] 距离的自定义数学函数,但这需要很多时间并且制作滞后的应用程序..

我不仅会制作线图,还会制作条形图、面积图、点图、烛台图。有什么想法可以解决这个问题吗?

【问题讨论】:

你为什么不用QgraphicsPathItem? 你想做什么?在我看来,你正在努力前进 哦..我会尽快尝试 QGraphicsPathItem 和反馈!!非常感谢! 【参考方案1】:

您必须使用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))

【讨论】:

太棒了!只需self.scene().itemAt(self.mapToScene(event.pos()), QTransform()) 检测路径!!你救了我! 我正在尝试使用 QGraphicsPathItem,但是当数据超过 30k 点时,它开始变慢......但只是使用手动绘制的 QGraphicsItem,超过 300k 就可以了。我要走正确的路吗?拜托,我想得到建议。

以上是关于在 QGraphicsItem 中使用 QPainterPath 检测鼠标命中的主要内容,如果未能解决你的问题,请参考以下文章

如何在qml中使用qgraphicsitem

QGraphicsItem 鼠标中按事件

QGraphicsItem - 项目转换失败

Qt 获取QGraphicsItem在屏幕上的位置,在QGraphicsItem中获取全局位置,转换为screenPos

从 QGraphicsItem 派生类绘制 QGraphicsScene

在调用 QGraphicsItem::paint 之前