PyQt mousePressEvent - 获取被点击的对象?

问题描述 投票:0回答:3

我正在使用 PyQt 和 PyQtGraph 构建一个相对简单的绘图 UI。作为其中的一部分,我有一个图形视图(pyqtgraph 的 graphicslayoutwidget),它有用户动态添加的 PlotItems。

我想要实现的是允许用户通过双击来选择一个 PlotItem。

如果用户双击了小部件窗口中的某处,这很简单,但我似乎无法弄清楚如何返回被点击的内容。

我的大部分搜索结果都试图为某些按钮重新实现 mousePressEvent。我读过一些关于事件过滤器的内容,但我不确定这是否是必要的解决方案。

我不确定还有哪些其他信息可能有助于回答这个问题,所以如果不清楚我在问什么,请告诉我,以便我澄清。

编辑:

这个的副本:

pyqtgraph:当我点击一个 PlotItem 时,我怎么知道哪个项目被点击了

python pyqt pyqtgraph qmouseevent
3个回答
7
投票

一种策略是连接到

GraphicsScene.sigMouseClicked
,然后询问场景哪些项目在鼠标光标下。

这应该让你走到那里:

import pyqtgraph as pg

w = pg.GraphicsWindow()
for i in range(4):
    w.addPlot(0, i)

def onClick(event):
    items = w.scene().items(event.scenePos())
    print "Plots:", [x for x in items if isinstance(x, pg.PlotItem)]

w.scene().sigMouseClicked.connect(onClick)

1
投票

在解决这个问题的很多麻烦之后,我发现

items
的方法
QGraphicsView
不能被粗心地使用,正如 stackoverflow 的许多答案中所报告的那样。

直接解决方案

我想出了一个 custom 解决方案来获取最近的数据点到点击点。这是函数

class DSP:
    @staticmethod
    def map_to_nearest(x: np.ndarray, x0: Union[np.ndarray, float]) -> np.ndarray:
        """This methods takes an array of time values and map it to nearest value in nidaq time array
        It returns the indices"""

        if type(x0) == float:
            x0 = np.array([x0])

        # A bit of magic
        x_tiled = np.tile(x, (x0.size, 1))
        x0_tiled = np.tile(x0, (x.size, 1)).T

        diff = np.abs(x_tiled - x0_tiled)
        idx = np.argmin(diff, axis=1)
        return idx

class MyPlotWidget(pg.PlotWidget):
        def nearest_data_index_to_mouse_click(self, click_scene_pos: QPointF):
        """
        :param click_scene_pos: The position of the mouse click in Scene coordinate
        :return:
            - int - Nearest point data index (or None)
            - view_rect (A rectangle in data coordinates of pixel_dist (equivalent scene coordinates) side
        """
        
        # QPoint to numpy array
        qpoint2np = lambda x: np.array([x.x(), x.y()])
        
        # Filter out all not data-driven items in the list. Must be customized and improved!
        get_data_items_only = lambda items: [item for item in items if
                                            any([isinstance(item, x) for x in [pg.PlotDataItem, pg.PlotCurveItem, pg.ScatterPlotItem]])]
        
        # Half side of the rectangular ROI around the click point
        pixel_dist = 5
        
        # Numpy click point
        p_click = qpoint2np(self.plot_item.mapToView(click_scene_pos))
        
        # Rectangle ROI in scene (pixel) coordinates
        scene_rect = QRectF(click_scene_pos.x() - pixel_dist, click_scene_pos.y() - pixel_dist, 2*pixel_dist, 2*pixel_dist)
                    
        # Rectangle ROI in data coordinates - NB: transforming from scene_rect to view_rect allows for the different x-y scaling!
        view_rect: QRectF = self.getPlotItem().mapRectToView(scene_rect)
        
        # Get all items canonically intercepted thourgh the methods already discussed by other answers
        items = get_data_items_only(self.scene().items(scene_rect))
        
        if len(items) == 0:
            return None, None, view_rect
            
        # Make your own decisional criterion
        item = items[0]


        # p0: bottom-left     p1: upper-right  view_rect items (DO NOT USE bottomLeft() and topRight()! The scene coordinates are different!
        # Y axis is upside-down
        p0 = np.array([view_rect.x(), view_rect.y() - view_rect.height()])
        p1 = np.array([view_rect.x() + view_rect.width(), view_rect.y()])

        # Limit the analysis to the same x-interval as the ROI
        _x_limits = np.array([p0[0], p1[0]])
        _item_data_x, _item_data_y = item.getData()
        
        xi = DSP.map_to_nearest(_item_data_x, _x_limits)
        # If the point is out of the interval
        if xi.size == 0:
            return None, None, view_rect
        xi = np.arange(xi[0], xi[1]+1).astype(np.int_)
        
        x, y = _item_data_x[xi], _item_data_y[xi]
        
        # (2,1) limited item data array
        _item_data = np.array([x,y])
        subitem = pg.PlotCurveItem(x=x, y=y)
        
        # Now intersects is used again, but this time the path is limited to a few points near the click! Some error might remains, but it may works well in most cases
        if subitem.getPath().intersects(view_rect):
            # Find nearest point
            delta = _item_data - p_click.reshape(2,1)
            min_dist_arg = np.argmin(np.linalg.norm(delta, axis=0))
            return item, xi[min_dist_arg], view_rect
        
        # View_rect is returned just to allow me to plot the ROI for debug reason
        return None, None, view_rect

实现自定义数据提示

TextItem
,这是结果:

注意:

  1. 点显然可以不在ROI的中心,取决于线的点密度
  2. 这是我的一个解决方案,我相信可以改进。这只是我得到的第一个。
  3. 不确定它是否适用于对数刻度。它应该,因为
    scene_rect
    是自动转换的,但到目前为止我不知道
    pyqtgraph
    如何处理 PlotCurveItem
  4. 的基础数据
  5. 我经历过,即使一个人使用
    PlotDataItem
    ,它也会变成一对(
    ScatterPlotItem
    CurvePlotItem
    )来处理情节的线条和符号(标记)

说明

首先,虽然没有记录,但

QGraphicsView::items()
似乎很可能实现或使用方法
QPainterPath::intersects()
。检查此方法的文档(以 QRectF 作为参数):

如果组成矩形的任何一条线穿过路径的一部分,或者如果矩形的任何部分与路径包围的任何区域重叠,则存在交叉点。

通过运行一些测试脚本,

QPainterPath

 似乎正在考虑 
always 一条封闭路径,可能是通过将最后一点与第一个点连接起来。事实上,下面的脚本:

from PySide2.QtCore import QPoint, QRectF from PySide2.QtGui import QPainterPath, QPicture, QPainter from PySide2.QtWidgets import QApplication, QMainWindow import pyqtgraph as pg import numpy as np app = QApplication([]) # Path1 made of two lines x = [0, 5, 0] y = [0, 5, 6] path1 = pg.PlotCurveItem(x, y, name='Path1') rect = QRectF(1,4,1,1) # RectItem (here omitted) is taken from https://stackoverflow.com/questions/60012070/drawing-a-rectangle-in-pyqtgraph rect_item = RectItem(rect) pw = pg.PlotWidget() pw.addItem(path1) pw.addItem(rect_item) text = f'path1.getPath().intersects(rect): {path1.getPath().intersects(rect)}' pw.addItem(pg.TextItem(text)) # Need to replicate the item rect_item = RectItem(rect) path2 =pg.PlotCurveItem(x=[0,5,4], y=[0,5,6]) pw2 = pg.PlotWidget() pw2.addItem(path2) pw2.addItem(rect_item) text = f'path2.getPath().intersects(rect): {path2.getPath().intersects(rect)}' pw2.addItem(pg.TextItem(text)) pw.show() pw2.show() app.exec_()
获取此图和结果作为输出:


0
投票
一个非常直接的替代方法是使用 lambda 函数:

q_label = QLabel("MyLabel") q_label.mousePressEvent = lambda e: on_name_clicked(q_label) def on_name_clicked(self, q_label): print(q_label.text())
    
© www.soinside.com 2019 - 2024. All rights reserved.