Home > OS >  how to draw by mouse an interactive fixed grid within a gui or figure? /python / matplotlib / pyqt5
how to draw by mouse an interactive fixed grid within a gui or figure? /python / matplotlib / pyqt5

Time:08-02

The below code allows me to draw an interactive rectangle and control its size and location by mouse.

In the same way, how do I draw a fixed interactive grid mXn (see the image at the link below) instead of a rectangle?

from matplotlib.widgets import RectangleSelector
import numpy as np
import matplotlib.pyplot as plt


def line_select_callback(eclick, erelease):
    'eclick and erelease are the press and release events'
    x1, y1 = eclick.xdata, eclick.ydata
    x2, y2 = erelease.xdata, erelease.ydata
    print("(%3.2f, %3.2f) --> (%3.2f, %3.2f)" % (x1, y1, x2, y2))
    print(" The button you used were: %s %s" % (eclick.button, erelease.button))


def toggle_selector(event):
    print(' Key pressed.')
    if event.key in ['Q', 'q'] and toggle_selector.RS.active:
        print(' RectangleSelector deactivated.')
        toggle_selector.RS.set_active(False)
    if event.key in ['A', 'a'] and not toggle_selector.RS.active:
        print(' RectangleSelector activated.')
        toggle_selector.RS.set_active(True)


fig, current_ax = plt.subplots()                 # make a new plotting range
N = 100000                                       # If N is large one can see
x = np.linspace(0.0, 10.0, N)                    # improvement by use blitting!

plt.plot(x,  np.sin(.2*np.pi*x), lw=3.5, c='b', alpha=.7)  # plot something
plt.plot(x,  np.cos(.2*np.pi*x), lw=3.5, c='r', alpha=.5)
plt.plot(x, -np.sin(.2*np.pi*x), lw=3.5, c='g', alpha=.3)

print("\n      click  -->  release")

# drawtype is 'box' or 'line' or 'none'
toggle_selector.RS = RectangleSelector(current_ax, line_select_callback,
                                       drawtype='box', useblit=True,
                                       button=[1, 3],  # don't use middle button
                                       minspanx=5, minspany=5,
                                       spancoords='pixels',
                                       interactive=True)
plt.connect('key_press_event', toggle_selector)
plt.show()

image of the mXn fixed grid

CodePudding user response:

If I understood it correctly, m and n are constants. If not, you can set them high enough. Matplotlib is designed for plotting functions, so PyQt5 is probably a much better choice (especially if you extend the functionality further).

from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys


class Window(QMainWindow):
    def __init__(self):
        super().__init__()

        title = "Grid"
        top = 400
        left = 400
        width = 800
        height = 600

        self.m = 8
        self.n = 6
        self.x1 = 0
        self.x2 = 0
        self.y1 = 0
        self.y2 = 0
        self.st_x = 0
        self.st_y = 0

        self.mode = "draw"

        self.setWindowTitle(title)
        self.setGeometry(top, left, width, height)

        self.image = QImage(self.size(), QImage.Format_RGB32) # creating canvas
        self.image.fill(Qt.white)
    
    def mousePressEvent(self, event):
        if self.x1 < event.x() < self.x2 and self.y1 < event.y() < self.y2:
            # mouse pressed in grid area
            self.mode = "move"
            self.st_x = event.x()
            self.st_y = event.y()

        else:
            self.mode = "draw"
            self.x1 = event.x()
            self.y1 = event.y()
        
        self.image.fill(Qt.white)
        self.update()
    
    def mouseReleaseEvent(self, event):
        if self.mode == "draw":
            self.x2 = event.x()
            self.y2 = event.y()
        
        else:
            x_delta = event.x() - self.st_x # 'delta' would be a x, y vector drawn by mouse press and release
            y_delta = event.y() - self.st_y

            self.x1  = x_delta
            self.y1  = y_delta
            self.x2  = x_delta
            self.y2  = y_delta

        self.draw_grid((self.x1, self.y1), (self.x2, self.y2), 50, 50)

    def paintEvent(self, event):
        canvasPainter = QPainter(self)
        canvasPainter.drawImage(
            self.rect(), self.image, self.image.rect()
        )
         
    def draw_grid(self, start, end, rect_width, rect_height):
        painter = QPainter(self.image)
        painter.setPen(
            QPen(Qt.black, 5, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
        )
        
        for y in range(self.n):
            for x in range(self.m):
                if start[0]   rect_width*x < end[0] and start[1]   rect_height*y < end[1]:
                    painter.drawRect(
                        QRect(start[0]   rect_width*x, start[1]   rect_height*y, rect_width, rect_height)
                    )

        self.update()
 

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()

    sys.exit(app.exec()) # may want to remove sys.exit

Update: In your example, a QGraphicsScene is used, unlike here. I don't know if this object provides any advantage but if you want to see the grid moving, while you are holding the mouse button, you can easily override the mouseMoveEvent, like in the example. QMainWindow inherits this event from QWidget.

  • Related