ear1
ear1

Reputation: 45

How to draw and fill a rectangle in a PyQtGraph plot at position where mouse is clicked

I am using PyQt5 and PyQtGraph. I have simplified the example code below. Then I want to draw in the plot view a small red rectangle each time the mouse is clicked at the position where the mouse is clicked, thus accumulating several red rectangles in the plot view. The code below has a #??? comment where I need some help with code that will draw the red rectangle(s).

import sys
from PyQt5 import QtWidgets
import numpy as np
import pyqtgraph as pg
from pyqtgraph import PlotWidget, plot

# *********************************************************************************************
# *********************************************************************************************

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.setWindowTitle("My MainWindow")
        self.qPlotWidget = pg.PlotWidget(self)
        self.qPlotWidget.setLabel("bottom", "X-Axis")
        self.qPlotWidget.setLabel("left", "Y-Axis")
        self.qPlotWidget.scene().sigMouseClicked.connect(self.mouseClickedEvent)

        data1 = np.zeros((2, 2), float) # create the array to hold the data
        data1[0] = np.array((1.0, 10.0))
        data1[1] = np.array((2.0, 20.0))

        pen1 = pg.mkPen(color=(255,0,0), width=1) # red
        self.qPlotWidget.plot(data1, pen=pen1, name="data1")

    def mouseClickedEvent(self, event):
        print("mouseClickedEvent")
        pos = event.scenePos()
        if (self.qPlotWidget.sceneBoundingRect().contains(pos)):
            mousePoint = self.qPlotWidget.plotItem.vb.mapSceneToView(pos)
            print("mousePoint=", mousePoint)

            # draw and fill a 2-pixel by 2-pixel red rectangle where
            # the mouse was clicked at [mousePoint.x(), mousePoint.y()]
            # ??? add code here

    def resizeEvent(self, event):
        size = self.geometry()
        self.qPlotWidget.setGeometry(10, 10, size.width()-20, size.height()-20)

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)

    w = MainWindow()
    screen = QtWidgets.QDesktopWidget().screenGeometry()
    w.setGeometry(100, 100, screen.width()-200, screen.height()-200) # x, y, Width, Height
    w.show()

    sys.exit(app.exec_())

Upvotes: 0

Views: 2145

Answers (2)

Teun
Teun

Reputation: 579

I was looking for a similar thing myself, but I needed to be able to size the rectangle myself and extract the coordinates of the rectangle. Eventually I came up with writing a class based on pyqtgraph PlotWidget, but with the right mouse click being used for drawing a rectangular ROI (instead of zooming). I've shared it here in case someone who stumbles onto this thread is looking for something similar

from PyQt6 import QtCore
from PyQt6.QtGui import QMouseEvent, QKeyEvent
from PyQt6.QtWidgets import QMainWindow
import pyqtgraph as pg


class RectROIPlotWidget(pg.PlotWidget):
    """PlotWidget with a rectangular ROI that can be drawn by right-clicking and dragging the mouse"""
    def __init__(self, *args, **kwargs):
        """Initialize method of the RectROIPlotWidget"""
        super().__init__(*args, **kwargs)
        
        # disable autorange by default to avoid unexpected behaviour when drawing ROI
        self.plotItem.vb.autoRange(False)

        # add rectangular ROI
        self.roi_rect = None
        self.reset_roi_rect()

    def reset_roi_rect(self):
        """Reset the rectangular ROI"""
        # remove ROI from plot if it exists
        if self.roi_rect in self.items():
            self.removeItem(self.roi_rect)

        # define start point
        self.start_point = [0, 0]

        # add rectangular ROI
        self.roi_rect = pg.RectROI(
            pos = [-1, -1],
            size = [0, 0],
            rotatable = False,
            invertible = True,
            pen = pg.mkPen([200, 200, 200], width=2, style=QtCore.Qt.PenStyle.DotLine),
            hoverPen = pg.mkPen([255, 255, 255], width=2, style=QtCore.Qt.PenStyle.DashLine)
        )
        self.addItem(self.roi_rect)
        self.roi_rect.setZValue(1000)

        # set flag for drawing ROI
        self.draw_flag = False

    def mousePressEvent(self, event: QMouseEvent):
        """Mouse press event handling the start of drawing the ROI
        
        Arguments
        ---------
            - event (QMouseEvent): mouse press event
        """
        if event.button().name == 'RightButton':
            self.reset_roi_rect()
            self.draw_flag = True
            mouse_point = self.plotItem.vb.mapSceneToView(event.position())
            self.start_point = [mouse_point.x(), mouse_point.y()]
        else:
            super().mousePressEvent(event)

    def mouseMoveEvent(self, event: QMouseEvent):
        """Mouse move event handling the drawing of the ROI
        
        Arguments
        ---------
            - event (QMouseEvent): mouse move event
        """
        if self.draw_flag:            
            mouse_point = self.plotItem.vb.mapSceneToView(event.position())
            bottom_left = [min(self.start_point[0], mouse_point.x()), min(self.start_point[1], mouse_point.y())]
            top_right = [max(self.start_point[0], mouse_point.x()), max(self.start_point[1], mouse_point.y())]
            self.roi_rect.setPos(bottom_left)
            self.roi_rect.setSize([top_right[0] - bottom_left[0], top_right[1] - bottom_left[1]])

        super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event: QMouseEvent):
        """Mouse release event handling the end of drawing the ROI
        
        Arguments
        ---------
            - event (QMouseEvent): mouse release event
        """
        if event.button().name == 'RightButton':
            self.draw_flag = False

        super().mouseReleaseEvent(event)

    def keyPressEvent(self, event: QKeyEvent):
        """Key press event handling the removal of the ROI when pressing escape
        
        Arguments
        ---------
            - event (QKeyEvent): key press event
        """
        # remove ROI on pressing escape
        if event.key() == QtCore.Qt.Key.Key_Escape:
            self.roi_rect.setPos([-1, -1])
            self.roi_rect.setSize([0, 0])

        super().keyPressEvent(event)

    def get_roi_rect_coords(self) -> tuple:
        """Get the coordinates of the rectangular ROI
        
        Returns
        -------
            - tuple: tuple with two lists:
                - list: bottom left coordinates of the ROI
                - list: top right coordinates of the ROI
        """
        # get bottom left and size of ROI
        bottom_left = list(self.roi_rect.pos())
        size = list(self.roi_rect.size())

        # determine top right of ROI
        top_right = [bottom_left[0] + size[0], bottom_left[1] + size[1]]

        return bottom_left, top_right


app = pg.mkQApp("Plotting Example")
win = QMainWindow()
p = RectROIPlotWidget()
win.setCentralWidget(p)
win.show()


if __name__ == '__main__':
    app.exec()

enter image description here

Upvotes: 0

Heike
Heike

Reputation: 24420

What you could do is to create an empty scatter plot item and add it to self.qPlotWidget. Then in mousrClickedEvent you could add the mouse position to the list of points of this scatter plot item, i.e.

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        .... as before ....

        # add empty scatter plot item with a red brush and a square as the symbol to plot widget
        brush = pg.mkBrush(color=(255,0,0))
        self.scatterItem = pg.ScatterPlotItem(pen=None, size=10, brush=brush, symbol='s')
        self.qPlotWidget.addItem(self.scatterItem)


    def mouseClickedEvent(self, event):
        pos = event.scenePos()
        if (self.qPlotWidget.sceneBoundingRect().contains(pos)):
            mousePoint = self.qPlotWidget.plotItem.vb.mapSceneToView(pos)

            # add point to scatter item
            self.scatterItem.addPoints([mousePoint.x()], [mousePoint.y()])

Upvotes: 2

Related Questions