Issue
I implementing custom chart. But I stucked with mouse hitting detection with QPainterPath.
I tried with graphicsitem's shape(), boundingRect(). but that only checks rough shape of boundary.
I want to check mouse hit system with exact position on QPainterPath path instance. But seems to no api like that functionality.
My app's QGraphicsScene is set with same coordinate with QGraphicsView in view's resizeEvent().
scene: MyScene = self.scene()
scene.setSceneRect(self.rect().x(), self.rect().y(),
self.rect().width(), self.rect().height())
At the same time, my plot QGraphicsItem scales by 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)
And in the MyScene, add rect item mouse_rec
. So, I check mouse_rec
and plot
item's path with mouse_rec.collidesWithPath(path)
It just works only with original path.
Here is all code. Just copy and paste, you could run it.
Red plot is original path and yellow plot is scaled path. Mouse hit check is only works with red plot...
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_()
Any idea checking mouse point with path ?? I first tried custom math function calculating [point <-> line] distance, but that need much time and making lagging app..
I will make not only line plot but also bar, area, points, candlestick plot.. Is there any idea to solve this problem?
Solution
You have to convert the position of the path with respect to the item that is scaled to the position relative to the scene using 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))
Answered By - eyllanesc
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.