python - 如何正确缩放 QGraphicsScene

标签 python pyqt pyqt5 qgraphicsview qgraphicsscene

我创建了一个小应用程序,我试图使其在调整主窗口大小(GraphicsView 和场景也是如此)时整个场景(像素图和矩形)垂直缩放以完全适合内部图形 View 。我不想要垂直滚动条,也不希望它水平缩放。

我不知道如何正确缩放场景。我使用 GraphicsScene 来包含一个图形和几个垂直矩形“标记”。当我可以通过重新绘制像素图来缩放图形以适应然后重新附加它时,z 顺序是错误的,并且矩形小部件不会随之缩放。

我需要跟踪矩形小部件,因此我不能只是不断删除并重新添加它们,因为每个小部件都包含元数据。

我知道 fitInView (来自这里: Issue with fitInView of QGraphicsView when ItemIgnoresTransformations is on )适用于包含的 GraphicsView,但我不明白为什么它需要一个参数。我只想让场景适合 GraphicsView(垂直而不是水平),那么为什么 GraphicsView 不缩放场景中的所有内容以适合其当前大小呢?参数应该是什么样子才能使场景垂直适合?

在 resizeEvent 中,我可以重新绘制像素图并重新添加,但由于 z 顺序困惑,它会覆盖矩形。另外,它不会在场景中垂直居中,我需要复制元数据。

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_()

最佳答案

我的解决方案将使封装所有项目(sceneRect)的最小矩形的高度适合QGraphicsView的视口(viewport)。因此,将项目的高度设置得不要太小,以免损失图像质量。我还使用 QTransforms 缩放了项目。另外,QGraphicsView坐标系被颠倒了,因为默认情况下垂直轴是上下的,我已经颠倒了它,以便绘画与数据更加一致。

我重构了 OP 代码以使其更具可扩展性,有一个 GraphItem 获取数据 (x, y) 和图像尺寸。

考虑到上述情况,解决方案是:

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()

关于python - 如何正确缩放 QGraphicsScene,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58019808/

相关文章:

python - Qt Designer 如何为每个文件创建 1 个以上对话框?

python - 如何在工具栏中添加操作菜单?

python - PyQt 在处理事件时避免陷入无限循环

python - 使用 `statusBar.addPermanentWidget()` 时如何删除小部件之间的分隔线?

python - 值错误 : invalid literal for int() with base 10: '' turning entry into integer

Python:使用 vars() 将字符串分配给变量

python - Python 中的相对导入问题

python - 删除所有非字母字符,保留字符串中的空格

python - 使用 PyQt PlotWidget 进行实时绘图 - 错误消息 PlotWidget 对象不可调用

python - 将数据库中的所有纬度和经度标记放置到来自 sql 结果的 Openstreet/Leaflet map 上