I present here a simple pyqt code, but I developed my whole design and algo upon this. The problem, is that PyQt is unable to manage a large number of objects when created. This is simple code for drawing many rectangles inside a big one. And it has Zoom and pan feature too.
import sys
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import *
from PyQt4.QtGui import *
item = None
class Rectangle(QGraphicsItem):
def __init__(self, parent, x, y, width, height, scene = None, fill=None):
self.parent = parent
super(Rectangle, self).__init__()
self.setFlags(QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable)
rect = QRectF(x,y,width,height)
self.dimen = [x,y,width,height]
self.rect = rect
scene.addItem(self)
self.show_its_ui()
#Function to get relative position
def get_relative_location(self,loc):
self.loc = QPointF(loc[0],loc[1])
global item
self.loc = self.mapFromItem(item, self.loc)
self.loc = (self.loc.x(),self.loc.y())
self.loc = list(self.loc)
self.dimen[0] = self.loc[0]
self.dimen[1] = self.loc[1]
#Showing its UI in the form of a rectangle
def show_its_ui(self):
#First gets its relative location and set it.
if(item is not None):
self.get_relative_location([self.dimen[0],self.dimen[1]])
#Make a Final rectangle
rect = QRectF(self.dimen[0],self.dimen[1],self.dimen[2],self.dimen[3])
self.rect = rect
def boundingRect(self):
return self.rect.adjusted(-2, -2, 2, 2)
def parentWidget(self):
return self.scene().views()[0]
def paint(self, painter, option, widget):
pen = QPen(Qt.SolidLine)
pen.setColor(Qt.black)
pen.setWidth(1)
painter.setBrush(QtGui.QColor(255, 50, 90, 200))
painter.drawRect(self.rect)
class GraphicsView(QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self.setDragMode(QGraphicsView.RubberBandDrag)
self.setRenderHint(QPainter.Antialiasing)
self.setRenderHint(QPainter.TextAntialiasing)
def wheelEvent(self, event):
factor = 1.41 ** (event.delta() / 240.0)
self.scale(factor, factor)
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self, parent)
self.Width = 800
self.Height = 500
self.view = GraphicsView()
self.scene = QGraphicsScene(self)
self.scene.setSceneRect(0, 0, self.Width, self.Height)
self.view.setScene(self.scene)
self.initUI()
hbox = QtGui.QHBoxLayout()
hbox.addWidget(self.view)
mainWidget = QtGui.QWidget()
mainWidget.setLayout(hbox)
self.setCentralWidget(mainWidget)
def initUI(self):
self.group = QGraphicsItemGroup()
self.group.setFlags(QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable)
global item
item = self.group
self.group.setFiltersChildEvents(True)
self.group.setHandlesChildEvents(False)
self._link1 = Rectangle(self, 10, 10, self.Width, self.Height,self.scene)
self._link1.setFiltersChildEvents(True)
self._link1.setHandlesChildEvents(False)
self.group.addToGroup(self._link1)
self.scene.addItem(self.group)
self.addObjects()
#Here I added objects in the big canvas
def addObjects(self):
xpos = 20
ypos = 20
#Change the below to 5 each
width = 50
height = 50
#Generate many rectangles
while(ypos < 450):
xpos = 20
while(xpos < 750):
t = Rectangle(self._link1,xpos,ypos,width,height,self.scene,True)
self.group.addToGroup(t)
xpos += (width+2)
ypos += (height+2)
app = QtGui.QApplication(sys.argv)
mainWindow = MainWindow()
method = mainWindow
mainWindow.show()
sys.exit(app.exec_())
Try changing, the width and height values present in addObjects function of the mainwindow class, say to 5 and 5 respectively from 50. Then the whole application run at very slow speed, when you pan/zoom. Please share your suggestions in rectifying this.
Related
I'm trying to rotate a line in a circle like clock's second arm. When rotating the line, I also want to draw small circles here and there inside the circle. The problem is that the small circles I drew is rotating along the line. I want these circles stay at their location and move with my code. Can someone help me?
import sys
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
from PySide2.QtOpenGL import *
from OpenGL import GL
class Helper:
def __init__(self):
gradient = QLinearGradient(QPointF(50, -20), QPointF(80, 20))
gradient.setColorAt(0.0, Qt.green)
gradient.setColorAt(1.0, Qt.blue)
self.background = QBrush(QColor(0, 0, 0))
self.circlePen = QPen(Qt.green)
self.circlePen.setWidth(2)
self.textPen = QPen(Qt.green,8,Qt.SolidLine)
self.textFont = QFont()
self.textFont.setPixelSize(100)
def paint(self, painter, event, elapsed):
painter.fillRect(event.rect(), self.background)
painter.setPen(self.circlePen)
painter.translate(400, 400)
painter.drawEllipse(-250,-250,500,500)
painter.rotate(elapsed * 0.35)
painter.setBrush(QColor(Qt.green))
dx = 100
dy = 100
for i in range(10):
painter.drawEllipse(dx,dy,8,8)
dx += 10
dy += 10
painter.save()
painter.drawLine(0,-250,0,0)
painter.restore()
class GLWidget(QGLWidget):
def __init__(self, helper, parent = None):
QGLWidget.__init__(self, QGLFormat(QGL.SampleBuffers), parent)
self.helper = helper
self.elapsed = 0
self.setFixedSize(800, 800)
def animate(self):
self.elapsed = (self.elapsed + self.sender().interval()) % 1000
self.repaint()
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
self.helper.paint(painter, event, self.elapsed)
painter.end()
class Window(QWidget):
def __init__(self, parent = None):
QWidget.__init__(self, parent)
helper = Helper()
openGL = GLWidget(helper, self)
openGLLabel = QLabel(self.tr("OpenGL"))
openGLLabel.setAlignment(Qt.AlignHCenter)
layout = QGridLayout()
layout.addWidget(openGL, 100, 100)
layout.addWidget(openGLLabel, 1, 0)
self.setLayout(layout)
timer = QTimer(self)
self.connect(timer, SIGNAL("timeout()"), openGL.animate)
timer.start(1)
self.setWindowTitle(self.tr("2D Painting on Native and OpenGL Widgets"))
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
small green circles are rotating with line
small green circles are rotating with line
1: The results of above code
The result I have
The result I want like this
I'm trying to rotate the background of my QGraphicsView object an I am getting in trouble. The red pen describes the problem what I'm getting.
What I want:
I want to keep the arrow centered in the middle of my screen.
I want the background image to rotate, taking the center of my screen as reference
I want to define a rectangle inside my QGraphicsView. that will be defined as the region where it will delimit what will be shown. Thus, every time I rotate my screen. The region "not covered" by the image will always be outside the boundary of the rectangle.
I want define the reference point of rotation, in the middle of my screen (x=VIEW_WIDTH/2, y=VIEW_HEIGHT/2)
And here is my code:
First, I'll show the arrow class:
from PyQt5 import QtCore, QtGui, QtWidgets # importation of some libraries
# Construnction of an arrow item, it'll be used in the QGraphicsView
class ArrowItem(QtWidgets.QGraphicsPathItem): # it inherit QgraphicsPathItem, which allows to handle it
# in the QgraphicsView
def __init__(self, parent=None): # The init method
super().__init__(parent)
self._length = -1
self._angle = 0
self._points = QtCore.QPointF(), QtCore.QPointF(), QtCore.QPointF() # with three points we
# construct a triangle.
self.length = 40.0 # the basic triangle length
self.rotate(180) # the triangle was built upside down, though I've just ran the function 'rotate'
#property
def angle(self):
"""
angle of the arrow
:return:
"""
return self._angle
#angle.setter
def angle(self, angle):
self._angle = angle
#property
def length(self):
return self._length
#length.setter
def length(self, l):
self._length = l
pos_top = QtCore.QPointF(0, l * 4 / 5)
pos_left = QtCore.QPointF(-l * 3 / 5, -l / 5)
pos_right = QtCore.QPointF(
l * 3 / 5,
-l / 5,
)
path = QtGui.QPainterPath()
path.moveTo(pos_top)
path.lineTo(pos_right)
path.lineTo(pos_left)
self.setPath(path)
self._points = pos_top, pos_left, pos_right
def paint(self, painter, option, widget):
pos_top, pos_left, pos_right = self._points
left_color = QtGui.QColor("#cc0000")
right_color = QtGui.QColor("#ff0000")
bottom_color = QtGui.QColor("#661900")
path_left = QtGui.QPainterPath()
path_left.lineTo(pos_top)
path_left.lineTo(pos_left)
path_right = QtGui.QPainterPath()
path_right.lineTo(pos_top)
path_right.lineTo(pos_right)
path_bottom = QtGui.QPainterPath()
path_bottom.lineTo(pos_left)
path_bottom.lineTo(pos_right)
painter.setPen(QtGui.QColor("black"))
painter.setBrush(left_color)
painter.drawPath(path_left)
painter.setBrush(right_color)
painter.drawPath(path_right)
painter.setBrush(bottom_color)
painter.drawPath(path_bottom)
def rotate(self, value):
"""
:param value:
"""
self._angle += value
transform = QtGui.QTransform()
transform.translate(20, 100)
transform.rotate(self._angle)
self.setTransform(transform)
# Just a function to translate the arrow, it'll not be used now!
def moveTo(self, next_position, duration=100):
self._animation = QtCore.QVariantAnimation()
self._animation.setStartValue(self.pos())
self._animation.setEndValue(next_position)
self._animation.setDuration(duration)
self._animation.start(QtCore.QAbstractAnimation.DeleteWhenStopped)
self._animation.valueChanged.connect(self.setPos)
Here follows the main window code:
# Just setting up the main Window
class WorkScreen(QtWidgets.QMainWindow):
# Some constant variable to keep things easy to change in the future
VIEW_HEIGHT = 600
VIEW_WIDTH = 900
FLAT_BUTTON = True
"""
Attempt to paint the work screen.
"""
def __init__(self, **kwargs):
# initializing the parent class, using a 2.7 pythonic style
super(WorkScreen, self).__init__(**kwargs)
# customizing the main window.
self.setStyleSheet("""
QMainWindow{
background: rgb(180, 180, 180)}
QPushButton{
background-color: rgb(0, 200, 0);
}
""")
# the attribute 'rotation' must be zero.
self.rotation = 0
# defining some attributes
self.pixmap = QtGui.QPixmap()
self.scene = QtWidgets.QGraphicsScene()
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
self.verticalLayout_3 = QtWidgets.QVBoxLayout()
self.pushButton_status = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_settings = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_analyse = QtWidgets.QPushButton(self.centralwidget)
self.verticalLayout_2 = QtWidgets.QVBoxLayout()
self.view = QtWidgets.QGraphicsView(self.centralwidget)
self.verticalLayout = QtWidgets.QVBoxLayout()
self.pushButton_field = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_guide = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_mapping = QtWidgets.QPushButton(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.arrowItem = ArrowItem()
self.set_up()
self.view.setScene(self.scene)
self.view.scale(-1, 1)
self.image = QtGui.QImage("ola.png")
self.image = self.image.scaled(self.VIEW_WIDTH+300, self.VIEW_HEIGHT+300)
self.pixmap.convertFromImage(self.image)
self.scene.addPixmap(self.pixmap)
self.add_arrow()
def set_up(self):
"""
build the main window.
"""
self.pushButton_guide.setFlat(self.FLAT_BUTTON)
self.pushButton_mapping.setFlat(self.FLAT_BUTTON)
self.pushButton_field.setFlat(self.FLAT_BUTTON)
self.pushButton_status.setFlat(self.FLAT_BUTTON)
self.pushButton_settings.setFlat(self.FLAT_BUTTON)
self.pushButton_analyse.setFlat(self.FLAT_BUTTON)
self.setObjectName("MainWindow")
self.setMaximumWidth(1024)
self.setMaximumHeight(600)
self.resize(1024, 600)
self.centralwidget.setObjectName("centralwidget")
self.horizontalLayout.setObjectName("horizontalLayout")
self.verticalLayout_3.setContentsMargins(-1, -1, 0, -1)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.pushButton_status.setObjectName("pushButton_status")
self.verticalLayout_3.addWidget(self.pushButton_status)
self.pushButton_settings.setObjectName("pushButton_settings")
self.verticalLayout_3.addWidget(self.pushButton_settings)
self.pushButton_analyse.setObjectName("pushButton_analyse")
self.verticalLayout_3.addWidget(self.pushButton_analyse)
self.horizontalLayout.addLayout(self.verticalLayout_3)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.view.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.view.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.view.setRenderHints(QtGui.QPainter.HighQualityAntialiasing | QtGui.QPainter.TextAntialiasing)
self.view.setCacheMode(QtWidgets.QGraphicsView.CacheBackground)
self.view.scale(-1, 1)
self.view.setFixedHeight(self.VIEW_HEIGHT)
self.view.setFixedWidth(self.VIEW_WIDTH)
self.view.setObjectName("view")
# self.view.setFixedWidth(self.VIEW_WIDTH)
# self.view.setFixedHeight(self.VIEW_HEIGHT)
self.verticalLayout_2.addWidget(self.view)
self.horizontalLayout.addLayout(self.verticalLayout_2)
self.verticalLayout.setContentsMargins(-1, -1, 0, -1)
self.verticalLayout.setObjectName("verticalLayout")
self.pushButton_field.setObjectName("pushButton_field")
self.verticalLayout.addWidget(self.pushButton_field)
self.pushButton_guide.setObjectName("pushButton_guide")
self.verticalLayout.addWidget(self.pushButton_guide)
self.pushButton_mapping.setObjectName("pushButton_mapping")
self.verticalLayout.addWidget(self.pushButton_mapping)
self.horizontalLayout.addLayout(self.verticalLayout)
self.setCentralWidget(self.centralwidget)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 26))
self.menubar.setObjectName("menubar")
self.setMenuBar(self.menubar)
self.statusbar.setObjectName("statusbar")
self.setStatusBar(self.statusbar)
self.retranslateUi()
QtCore.QMetaObject.connectSlotsByName(self)
# Just to test
self.pushButton_status.clicked.connect(self.rotate_arrow)
self.pushButton_guide.clicked.connect(self.rotate_pixmap)
# Created just to emit a signal
def rotate_arrow(self):
"""
It'll not be used after.
"""
self.arrowItem.rotate(value=30)
def rotate_pixmap(self):
pixmap = self.pixmap
self.rotation += 15
transform = QtGui.QTransform().rotate(self.rotation, axis=QtCore.Qt.ZAxis)
pixmap = pixmap.transformed(transform, QtCore.Qt.SmoothTransformation)
self.scene.addPixmap(pixmap)
self.arrowItem.setPos(self.arrowItem.pos())
self.arrowItem.rotate(self.rotation)
self.arrowItem.setZValue(self.arrowItem.zValue()+1)
def add_arrow(self):
"""
:cvar
"""
self.scene.addItem(self.arrowItem)
self.arrowItem.setPos(QtCore.QPointF(self.VIEW_WIDTH / 2 - 50, self.VIEW_HEIGHT / 2 - 100))
def retranslateUi(self):
_translate = QtCore.QCoreApplication.translate
self.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton_status.setText(_translate("MainWindow", "Situação"))
self.pushButton_settings.setText(_translate("MainWindow", "Configurações"))
self.pushButton_analyse.setText(_translate("MainWindow", "Analisar"))
self.pushButton_field.setText(_translate("MainWindow", "Campo"))
self.pushButton_guide.setText(_translate("MainWindow", "Guia"))
self.pushButton_mapping.setText(_translate("MainWindow", "Mapeamento"))
# calling the application
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = WorkScreen()
ui.show()
sys.exit(app.exec_())
Here is the image that I am using to display on the background:
If you want to rotate a QGraphicsItem it is not necessary to use QTransform, just use the setRotation method. On the other hand, the image must be scaled so that the rotated area intersected by the visible area is the same as the visible area and that minimum area is calculated as: sqrt(2) x max(width, height). Considering the above, the solution is:
from PyQt5 import QtCore, QtGui, QtWidgets
class ArrowItem(QtWidgets.QGraphicsPathItem):
def __init__(self, parent=None):
super().__init__(parent)
self._length = -1
self._points = (
QtCore.QPointF(),
QtCore.QPointF(),
QtCore.QPointF(),
)
self.length = 40.0
#property
def length(self):
return self._length
#length.setter
def length(self, l):
self._length = l
pos_top = QtCore.QPointF(0, l * 4 / 5)
pos_left = QtCore.QPointF(-l * 3 / 5, -l / 5)
pos_right = QtCore.QPointF(
l * 3 / 5,
-l / 5,
)
path = QtGui.QPainterPath()
path.moveTo(pos_top)
path.lineTo(pos_right)
path.lineTo(pos_left)
self.setPath(path)
self._points = pos_top, pos_left, pos_right
def paint(self, painter, option, widget):
pos_top, pos_left, pos_right = self._points
left_color = QtGui.QColor("#cc0000")
right_color = QtGui.QColor("#ff0000")
bottom_color = QtGui.QColor("#661900")
path_left = QtGui.QPainterPath()
path_left.lineTo(pos_top)
path_left.lineTo(pos_left)
path_right = QtGui.QPainterPath()
path_right.lineTo(pos_top)
path_right.lineTo(pos_right)
path_bottom = QtGui.QPainterPath()
path_bottom.lineTo(pos_left)
path_bottom.lineTo(pos_right)
painter.setPen(QtGui.QColor("black"))
painter.setBrush(left_color)
painter.drawPath(path_left)
painter.setBrush(right_color)
painter.drawPath(path_right)
painter.setBrush(bottom_color)
painter.drawPath(path_bottom)
def moveTo(self, next_position, duration=100):
self._animation = QtCore.QVariantAnimation()
self._animation.setStartValue(self.pos())
self._animation.setEndValue(next_position)
self._animation.setDuration(duration)
self._animation.start(QtCore.QAbstractAnimation.DeleteWhenStopped)
self._animation.valueChanged.connect(self.setPos)
class MainWindow(QtWidgets.QMainWindow):
VIEW_HEIGHT = 600
VIEW_WIDTH = 900
def __init__(self, parent=None):
super().__init__(parent)
self.scene = QtWidgets.QGraphicsScene(self)
self.view = QtWidgets.QGraphicsView(self.scene)
self.status_btn = QtWidgets.QPushButton("Status")
self.settings_btn = QtWidgets.QPushButton("Settings")
self.analyze_btn = QtWidgets.QPushButton("Analyze")
self.field_btn = QtWidgets.QPushButton("Field")
self.guide_btn = QtWidgets.QPushButton("Guide")
self.mapping_btn = QtWidgets.QPushButton("Mapping")
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
vlayl = QtWidgets.QVBoxLayout()
vlayl.addWidget(self.status_btn)
vlayl.addWidget(self.settings_btn)
vlayl.addWidget(self.analyze_btn)
vlayr = QtWidgets.QVBoxLayout()
vlayr.addWidget(self.field_btn)
vlayr.addWidget(self.guide_btn)
vlayr.addWidget(self.mapping_btn)
hlay = QtWidgets.QHBoxLayout(central_widget)
hlay.addLayout(vlayl)
hlay.addWidget(self.view)
hlay.addLayout(vlayr)
self.status_btn.clicked.connect(self.rotate_arrow)
self.guide_btn.clicked.connect(self.rotate_pixmap)
self.view.setFixedSize(self.VIEW_WIDTH, self.VIEW_HEIGHT)
r = self.view.mapToScene(self.view.viewport().rect()).boundingRect()
self.view.setSceneRect(r)
factor = 1.5 * max(self.VIEW_WIDTH, self.VIEW_HEIGHT)
pixmap = QtGui.QPixmap("ola.png").scaled(factor, factor)
self.pixmap_item = self.scene.addPixmap(pixmap)
center = self.pixmap_item.boundingRect().center()
self.pixmap_item.setPos(-center)
self.pixmap_item.setTransformOriginPoint(center)
self.arrow_item = ArrowItem()
self.scene.addItem(self.arrow_item)
def rotate_arrow(self):
delta = 30.0
self.arrow_item.setRotation(self.arrow_item.rotation() + delta)
def rotate_pixmap(self):
delta = 15.0
self.pixmap_item.setRotation(self.pixmap_item.rotation() + delta)
self.arrow_item.setRotation(self.arrow_item.rotation() + delta)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
I've created a small app and I'm trying to make it so that when the main window is resized (and the GraphicsView and scene are too) that the whole scene (pixmap and rectangles) scale vertically to fit completely inside the GraphicsView. I don't want a vertical scrollbar and I don't want it to scale horizontally.
I can't figure out how to scale the scene properly. I use a GraphicsScene to contain a graph and a couple vertical rectangle "markers". When I can scale the graph to fit by redrawing the pixmap and then reattach it, the z-order is wrong AND the rectangle widgets are not scaled with it.
I need to keep track of the rectangle widgets, so I can't just keep deleting and re-adding them as there's meta data along with each one.
I know about fitInView (from here: Issue with fitInView of QGraphicsView when ItemIgnoresTransformations is on) that applies to the containing GraphicsView, but I don't understand why it needs a parameter. I just want the scene to fit in the GraphicsView (vertically but not horizontally) so why doesn't GraphicsView just scale everything in the scene to fit inside it's current size? What should the parameter look like to get the scene to fit vertically?
In the resizeEvent I can redraw the pixmap and re-add, but then it covers the rectangles as the z-order is messed up. Also, it doesn't stay centered vertically in the scene and I would need to copy over the meta data.
import sys
import os
from PyQt5 import QtCore, QtGui, QtWidgets
import PyQt5 as qt
from PyQt5.QtGui import QColor
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout, QGroupBox, QDialog, QVBoxLayout
from PyQt5.QtWidgets import QVBoxLayout, QGridLayout, QStackedWidget, QTabWidget
import numpy as np
class GraphicsScene(QtWidgets.QGraphicsScene):
def __init__(self, parent=None):
super(GraphicsScene, self).__init__(parent)
def minimumSizeHint(self):
return QtCore.QSize(300, 200)
def dragMoveEvent(self, event):
print("dragMoveEvent", event)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
#super(MainWindow).__init__()
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
max_x, max_y = 2400, 700
max_x_view = 1200
self.max_x = max_x
self.max_y = max_y
self.first = True
self.setGeometry(200, 200, max_x_view, self.max_y)
self.gv = QtWidgets.QGraphicsView(self)
self.gv.setGeometry(0, 0, max_x_view, self.max_y)
self.gv2 = QtWidgets.QGraphicsView(self)
layout.addWidget(self.gv)
layout.addWidget(self.gv2)
scene = GraphicsScene()
self.scene = scene
self.gv.setScene(scene)
tab_widget = QTabWidget()
tab_widget.setTabPosition(QTabWidget.West)
widget = QWidget()
widget.setLayout(layout)
tab_widget.addTab(widget, "main")
self.setCentralWidget(tab_widget)
np.random.seed(777)
self.x_time = np.linspace(0, 12.56, 3000)
rand_data = np.random.uniform(0.0, 1.0, 3000)
self.data = .45*(np.sin(2*self.x_time) + rand_data) - .25*(np.sin(3*self.x_time))
self.first = True
pixmap_height = max_y//2 - 2*22 # 22 to take care of scrollbar height
pixmap = self.draw_graph()
pen = QtGui.QPen()
pen.setWidth(2)
pen.setColor(QtGui.QColor("red"))
self.gv1_pixmap = scene.addPixmap(pixmap)
rect = scene.sceneRect()
print("scene rect = {}".format(rect))
scene.setSceneRect(rect)
side, offset = 50, 200
for i in range(2):
r = QtCore.QRectF(QtCore.QPointF((i + 1)*offset + i * 2 * side, 2), QtCore.QSizeF(side, pixmap_height - 4))
rect_ref = scene.addRect(r, pen, QColor(255, 0, 0, 127))
rect_ref.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable)
all_items = scene.items()
print(all_items)
def draw_graph(self):
print("draw_graph: main Window size {}:".format(self.size()))
pixmap_height = self.height()//2 - 2*22 # 22 to take care of scrollbar height
x_final = self.x_time[-1]
data = self.data / np.max(np.abs(self.data))
data = [abs(int(k * pixmap_height)) for k in self.data]
x_pos = [int(self.x_time[i] * self.max_x / x_final) for i in range(len(data))]
pixmap = QtGui.QPixmap(self.max_x, pixmap_height)
painter = QtGui.QPainter(pixmap)
pen = QtGui.QPen()
pen.setWidth(2)
rect = pixmap.rect()
pen.setColor(QtGui.QColor("red"))
painter.drawRect(rect)
print("pixmap rect = {}".format(rect))
painter.fillRect(rect, QtGui.QColor('lightblue'))
pen.setWidth(2)
pen.setColor(QtGui.QColor("green"))
painter.setPen(pen)
for x, y in zip(x_pos, data):
painter.drawLine(x, pixmap_height, x, pixmap_height - y)
painter.end()
return pixmap
def resizeEvent(self, a0: QtGui.QResizeEvent):
#print("main Window resizeEvent")
print("main Window size {}:".format(a0.size()))
redraw = False
if redraw:
pixmap = self.draw_graph()
self.scene.removeItem(self.gv1_pixmap)
self.gv1_pixmap = self.scene.addPixmap(pixmap)
self.gv1_pixmap.moveBy(0, 30)
else:
#rect = QtCore.QRect(self.gv.startPos, self.gv.endPos)
#sceneRect = self.gv.mapToScene(rect).boundingRect()
#print 'Selected area: viewport coordinate:', rect,', scene coordinate:', sceneRect
#self.gv.fitInView(sceneRect)
pass
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
My solution will fit the height of the smallest rectangle that encapsulates all the items (sceneRect) to the viewport of the QGraphicsView. So set the height of the items a value not so small so that the image quality is not lost. I have also scaled the items using the QTransforms. In addition, the QGraphicsView coordinate system was inverted since by default the vertical axis is top-bottom and I have inverted it so that the painting is more consistent with the data.
I have refactored the OP code to make it more scalable, there is a GraphItem that takes the data (x, y) and the image dimensions.
Considering the above, the solution is:
import numpy as np
from PyQt5 import QtCore, QtGui, QtWidgets
class GraphItem(QtWidgets.QGraphicsPixmapItem):
def __init__(self, xdata, ydata, width, height, parent=None):
super(GraphItem, self).__init__(parent)
self._xdata = xdata
self._ydata = ydata
self._size = QtCore.QSize(width, height)
self.redraw()
def redraw(self):
x_final = self._xdata[-1]
pixmap = QtGui.QPixmap(self._size)
pixmap_height = pixmap.height()
pixmap.fill(QtGui.QColor("lightblue"))
painter = QtGui.QPainter(pixmap)
pen = QtGui.QPen(QtGui.QColor("green"))
pen.setWidth(2)
painter.setPen(pen)
for i, (x, y) in enumerate(
zip(self._xdata, self._ydata / np.max(np.abs(self._ydata)))
):
x_pos = int(x * self._size.width() / x_final)
y_pos = abs(int(y * pixmap_height))
painter.drawLine(x_pos, 0, x_pos, y_pos)
painter.end()
self.setPixmap(pixmap)
class HorizontalRectItem(QtWidgets.QGraphicsRectItem):
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self.scene():
newPos = self.pos()
newPos.setX(value.x())
return newPos
return super(HorizontalRectItem, self).itemChange(change, value)
class GraphicsView(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
scene = QtWidgets.QGraphicsScene(self)
self.setScene(scene)
self.scale(1, -1)
def resizeEvent(self, event):
h = self.mapToScene(self.viewport().rect()).boundingRect().height()
r = self.sceneRect()
r.setHeight(h)
self.setSceneRect(r)
height = self.viewport().height()
for item in self.items():
item_height = item.boundingRect().height()
tr = QtGui.QTransform()
tr.scale(1, height / item_height)
item.setTransform(tr)
super(GraphicsView, self).resizeEvent(event)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
tab_widget = QtWidgets.QTabWidget(tabPosition=QtWidgets.QTabWidget.West)
self.setCentralWidget(tab_widget)
self.graphics_view_top = GraphicsView()
self.graphics_view_bottom = QtWidgets.QGraphicsView()
container = QtWidgets.QWidget()
lay = QtWidgets.QVBoxLayout(container)
lay.addWidget(self.graphics_view_top)
lay.addWidget(self.graphics_view_bottom)
tab_widget.addTab(container, "main")
self.resize(640, 480)
side, offset, height = 50, 200, 400
np.random.seed(777)
x_time = np.linspace(0, 12.56, 3000)
rand_data = np.random.uniform(0.0, 1.0, 3000)
data = 0.45 * (np.sin(2 * x_time) + rand_data) - 0.25 * (np.sin(3 * x_time))
graph_item = GraphItem(x_time, data, 3000, height)
self.graphics_view_top.scene().addItem(graph_item)
for i in range(2):
r = QtCore.QRectF(
QtCore.QPointF((i + 1) * offset + i * 2 * side, 2),
QtCore.QSizeF(side, height),
)
it = HorizontalRectItem(r)
it.setPen(QtGui.QPen(QtGui.QColor("red"), 2))
it.setBrush(QtGui.QColor(255, 0, 0, 127))
self.graphics_view_top.scene().addItem(it)
it.setFlags(
it.flags()
| QtWidgets.QGraphicsItem.ItemIsMovable
| QtWidgets.QGraphicsItem.ItemSendsGeometryChanges
)
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
ret = app.exec_()
sys.exit(ret)
if __name__ == "__main__":
main()
According to http://doc.qt.io/qt-5/qsizepolicy.html#Policy-enum, setting the size policy of a widget has the following effect:
The sizeHint() is a sensible size, but the widget can be shrunk and
still be useful. The widget can make use of extra space, so it should
get as much space as possible (e.g. the horizontal direction of a
horizontal slider).
So, I expect the Yellow widget below to fill up the Green widget, but that does not happen. What did I do wrong?
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class Yellow(QWidget):
def __init__(self, *args):
super().__init__(*args)
# Set palette
bg = QPalette()
bg.setColor(QPalette.Window, Qt.yellow)
self.setAutoFillBackground(True)
self.setPalette(bg)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
class Green(QWidget):
def __init__(self, *args):
super().__init__(*args)
# Set palette
bg = QPalette()
bg.setColor(QPalette.Window, Qt.green)
self.setAutoFillBackground(True)
self.setPalette(bg)
self.yellow = Yellow(self)
class App(QMainWindow):
def __init__(self):
super().__init__()
self.title = 'PyQt5'
self.left = 10
self.top = 10
self.width = 200
self.height = 200
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.green = Green(self)
self.green.resize(184, 154)
self.green.move(10, 10)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
Use a Layout:
class Green(QWidget):
def __init__(self, *args):
super().__init__(*args)
# Set palette
bg = QPalette()
bg.setColor(QPalette.Window, Qt.green)
self.setAutoFillBackground(True)
self.setPalette(bg)
self.yellow = Yellow(self)
self.myLayout = QGridLayout()
self.myLayout.addWidget(self.yellow)
self.setLayout(self.myLayout)
Result:
If you add self.myLayout.setContentsMargins(0,0,0,0) the yellow widget completely covers the green one:
So I am building a node based interface using PyQt for a project I am working on and I am having some issues getting objects that belong to the base not to follow it in space. I would like when the user drags the base node, the child objects (inputs and output boxes) to follow it. I have a drag-able node that works but the child objects are not following properly. Any ideas?
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
This is the base py file for the GUI
"""
import sys
from PyQt4 import QtGui, QtCore
from array import *
"""
Base class for a node. Contains all the initialization, drawing, and containing inputs and outputs
"""
class node():
width = 100
height = 100
color = 1
x = 90
y = 60
inputs=[]
outputs=[]
def __init__(self, nWidth, nHeight):
self.width = nWidth
self.height = nHeight
self.iniNodeData()
"""
This is where inputs and outputs will be created
"""
def iniNodeData(self):
for j in range(5):
this = self
x = input(this,90, 0+(j*10))
self.inputs.append(x)
"""Draw the node then the input and output objects"""
def draw(self, drawObj):
item = drawObj.addRect(self.x, self.y, self.width, self.height)
item.setFlag(QtGui.QGraphicsItem.ItemIsMovable)
for curInput in self.inputs:
curInput.draw(drawObj)
print("(", self.x, ",", self.y, ")")
"""
Nodes will evaluate from the last node to the first node, therefore inputs are evaluted
"""
class input():
currentConnectedNode = None
currentConnectedOutput = None
parentNode = None
width = 10
height = 10
x = 1
y = 1
color = 1
def __init__(self, pnode, posX, posY):
self.parentNode = pnode
self.x = posX
self.y = posY
self.color = 1
def draw(self, drawObj):
item = drawObj.addRect(self.x+self.parentNode.x, self.y+self.parentNode.y, self.width, self.height)
class output():
parentNode = None
class MainWindow(QtGui.QGraphicsView):
nodes = []
def __init__(self):
super(MainWindow, self).__init__()
self.initUI()
def initUI(self):
for j in range(1):
x = node(100,100)
self.nodes.append(x)
self.setScene(QtGui.QGraphicsScene())
self.setWindowTitle('RIS RIB Generator')
self.setGeometry(800, 600, 800, 850)
self.initNodes()
self.show()
def initNodes(self):
for curNode in self.nodes:
curNode.draw(self.scene())
def main():
app = QtGui.QApplication(sys.argv)
mainwindow = MainWindow()
mainwindow.show()
app.exec_()
if __name__ == '__main__':
main()
Ok so after a week I figured it out. You need to do a few things.
ensure the flags are correct:
self.setFlag(QtGui.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
Once those are set you can use the built in event handelers but these over ride the original ones. so from inside your user defined one, you need to call the event handeler from the base class. Example:
def mousePressEvent(self, e):
print("Square got mouse press event")
print("Event came to us accepted: %s"%(e.isAccepted(),))
QtGui.QGraphicsRectItem.mousePressEvent(self, e)
Here is my working example of my progress.
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
This is the base py file for the GUI
Todo list
-----------------
- Pop up menu for adding new Nodes
- node connectivity
- create data structure for storing
"""
import sys
from PyQt4 import QtGui, QtCore
from array import *
"""
Base class for a node. Contains all the inilization, drawing, and containing inputs and outputs
"""
class node(QtGui.QGraphicsRectItem):
width = 100
height = 100
color = 1
x = 90
y = 60
inputs=[]
outputs=[]
viewObj = None
def __init__(self, n_x, n_y, n_width,n_height):
QtGui.QGraphicsRectItem.__init__(self, n_x, n_y, n_width, n_height)
self.width = n_width
self.height = n_height
self.x = n_x
self.y = n_y
self.setFlag(QtGui.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
self.iniNodeData()
def mousePressEvent(self, e):
print("Square got mouse press event")
print("Event came to us accepted: %s"%(e.isAccepted(),))
QtGui.QGraphicsRectItem.mousePressEvent(self, e)
def mouseReleaseEvent(self, e):
print("Square got mouse release event")
print("Event came to us accepted: %s"%(e.isAccepted(),))
QtGui.QGraphicsRectItem.mouseReleaseEvent(self, e)
"""
This is where inputs and outputs will be created based on node type
"""
def iniNodeData(self):
print('making node data')
for j in range(5):
this = self
x = input(this,0, 0+(j*10))
self.inputs.append(x)
for k in range(5):
this = self
x = output(this,self.x+self.width, self.y+(k*10))
self.outputs.append(x)
def mouseMoveEvent(self, event):
print('Dragging#')
QtGui.QGraphicsRectItem.mouseMoveEvent(self, event)
def mousePressEvent(self, event):
print('moving!')
"""
Nodes will evaluate from the last node to the first node, therefore inputs are evaluted
"""
class input(QtGui.QGraphicsRectItem):
currentConnectedNode = None
currentConnectedOutput = None
parentNode = None
width = 10
height = 10
x = 1
y = 1
color = 1
drawItem = None
def __init__(self, pnode, posX, posY):
self.parentNode = pnode
self.x = posX
self.y = posY
self.color = 1
QtGui.QGraphicsRectItem.__init__(self, self.x+self.parentNode.x, self.y+self.parentNode.y, self.width, self.height, self.parentNode)
'''
Output value from a node
'''
class output(node):
parentNode = None
width = 10
height = 10
x = 1
y = 1
def __init__(self, pnode, posX, posY):
self.parentNode = pnode
self.x = posX
self.y = posY
self.color = 1
QtGui.QGraphicsRectItem.__init__(self, self.x-self.width, self.y, self.width, self.height, self.parentNode)
'''
Check Click events on the scene Object
'''
class Scene(QtGui.QGraphicsScene):
nodes = []
def mousePressEvent(self, e):
print("Scene got mouse press event")
print("Event came to us accepted: %s"%(e.isAccepted(),))
QtGui.QGraphicsScene.mousePressEvent(self, e)
def mouseReleaseEvent(self, e):
print("Scene got mouse release event")
print("Event came to us accepted: %s"%(e.isAccepted(),))
QtGui.QGraphicsScene.mouseReleaseEvent(self, e)
def dragMoveEvent(self, e):
print('Scene got drag move event')
def addNode(self):
newNode = self.addItem(node(250,250,100,150))
self.nodes.append(newNode)
'''
Main Window Object
'''
class MainWindowUi(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setWindowTitle('RIS RIB Generator')
self.scene = Scene(0, 0, 800, 850, self)
self.view = QtGui.QGraphicsView()
self.setCentralWidget(self.view)
self.view.setScene(self.scene)
exitAction = QtGui.QAction(QtGui.QIcon('exit24.png'), 'Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
exitAction.triggered.connect(self.close)
newNodeAction = QtGui.QAction(QtGui.QIcon('exit24.png'), 'New Node', self)
newNodeAction.setStatusTip('Add a blank node')
newNodeAction.triggered.connect(self.scene.addNode)
self.statusBar()
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(newNodeAction)
fileMenu.addAction(exitAction)
'''
Start Point
'''
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
win = MainWindowUi()
win.show()
sys.exit(app.exec_())