Как правильно масштабировать GraphicsScene - PullRequest
1 голос
/ 20 сентября 2019

Я создал небольшое приложение, и я пытаюсь сделать так, чтобы при изменении размера главного окна (а также GraphicsView и сцены) вся сцена (растровое изображение и прямоугольники) масштабировалась вертикально, чтобы полностью поместиться внутриGraphicsView.Я не хочу вертикальную полосу прокрутки и не хочу, чтобы она масштабировалась по горизонтали.

Я не могу понять, как правильно масштабировать сцену.Я использую GraphicsScene, чтобы содержать граф и пару вертикальных прямоугольников «маркеров».Когда я могу масштабировать график, чтобы подогнать его, перерисовав растровое изображение, а затем снова присоединить его, z-порядок неправильный, а прямоугольные виджеты не масштабируются с ним.

Мне нужно отслеживать прямоугольные виджеты, поэтомуЯ не могу просто продолжать удалять и повторно добавлять их, поскольку есть метаданные вместе с каждым из них.

Я знаю о fitInView (отсюда: Проблема с fitInView QGraphicsView, когда ItemIgnoresTransformations находится на ) это относится к содержащему 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_()

1 Ответ

2 голосов
/ 20 сентября 2019

Мое решение будет соответствовать высоте наименьшего прямоугольника, который инкапсулирует все элементы (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()
...