【问题标题】:How to properly scale GraphicsScene如何正确缩放 QGraphicsScene
【发布时间】:2020-01-21 00:10:29
【问题描述】:

我已经创建了一个小应用程序,我正在尝试制作它,以便在调整主窗口的大小(以及 GraphicsView 和场景)时,整个场景(像素图和矩形)垂直缩放以完全适合里面图形视图。我不想要垂直滚动条,也不希望它水平缩放。

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

我需要跟踪矩形小部件,所以我不能一直删除和重新添加它们,因为每个小部件都有元数据。

我知道适用于包含 GraphicsView 的 fitInView(来自这里:Issue with fitInView of QGraphicsView when ItemIgnoresTransformations is on),但我不明白为什么它需要一个参数。我只希望场景适合 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_()

【问题讨论】:

  • 这些矩形有特殊要求吗?我怀疑您希望这些矩形占据 QGraphicsView 视口的高度,并且不能垂直移动,只能水平移动,对吗?
  • 没错。矩形需要与像素图具有相同的高度。像素图比视口短一点,因为水平滚动条使像素图无法完全拟合。
  • 矩形应该能够垂直移动还是只能水平移动?
  • 不,矩形只能水平移动。将它们视为图表中突出显示的部分。

标签: python pyqt pyqt5 qgraphicsview qgraphicsscene


【解决方案1】:

我的解决方案将使封装所有项目(sceneRect)的最小矩形的高度适合 QGraphicsView 的视口。因此,将项目的高度设置为一个不太小的值,以免丢失图像质量。我还使用 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()

【讨论】:

  • 谢谢你,但它对我不起作用。图形和矩形不是缩放的,而是在缩小时被剪裁,在窗口放大时可见更多。
  • 太棒了,你是摇滚明星!完美运行!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多