Stack two widgets in a GUI

644 views Asked by At

From this answer I have code that adds a number of Widgets to a PyQt5 GUI:

enter image description here

I would like to add another widget just in this configuration:

enter image description here

I was trying to play around with QGridLayout(), for a simplified version of the problem (MWE below).

What I tried (MWE below). I get something like this (plots all squashed on one side):

enter image description here

and varying the grid coordinates has no effect whatsoever.

MWE:

import PyQt5
from PyQt5 import QtGui, QtCore
import pyqtgraph as pg
import sys
import numpy as np

width = 1000
height = 500

class layout():
    def setup(self, window):
        self.window = window
        self.window.resize(width, height)

        grid = PyQt5.QtWidgets.QGridLayout()

        self.dialogue = QtGui.QTextEdit()

        grid.addWidget(self.dialogue , 100, 0)

        self.plot = pg.GraphicsLayoutWidget(self.window)
  
        grid.addWidget(self.plot , 200, 200)

class Window(pg.Qt.QtGui.QMainWindow, layout):

    def __init__(self, shot = None):

        super(Window, self).__init__()
        self.setup(self)
        self.show()

if __name__ == '__main__':
    app = pg.Qt.QtGui.QApplication([])
    Window()
    sys.exit(app.exec_())
1

There are 1 answers

4
eyllanesc On BEST ANSWER

The errors in the code provided by the OP are:

  • The layout (QGridLayout) was never set in a widget. The Qt layouts are not visual elements but managers of the geometry of the widgets.

  • A centralWidget must be set if a QMainWindow is used.

import sys

import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore


width = 1000
height = 500


class layout:
    def setup(self, window):
        self.window = window
        self.window.resize(width, height)

        grid = QtGui.QGridLayout()

        self.dialogue = QtGui.QTextEdit()
        grid.addWidget(self.dialogue, 100, 0)

        self.plot = pg.GraphicsLayoutWidget()
        grid.addWidget(self.plot, 200, 200)

        central_widget = QtGui.QWidget()
        window.setCentralWidget(central_widget)
        central_widget.setLayout(grid)


class Window(pg.Qt.QtGui.QMainWindow, layout):
    def __init__(self, shot=None):
        super(Window, self).__init__()
        self.setup(self)


if __name__ == "__main__":
    app = QtGui.QApplication([])
    w = Window()
    w.show()
    sys.exit(app.exec_())

enter image description here

Anyway, that problem has nothing to do with the initial problem.


Probably (since the OP does not provide any attempt to the initial goal) the error is that it is thought that adding the same widget 2 times will create 2 copies but it is not, when you add a widget to a layout then it will be removed from its previous position. The solution is to create 2 widgets. For this, it is better to create a class that allows to implement this logic in a simple way.

import sys

import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui

import numpy as np


from glumpy import app as glumpy_app, gl, gloo, data, library
from glumpy.geometry import primitives
from glumpy.transforms import Trackball


width = 1000
height = 500

glumpy_app.use("qt5")


vertex = """
#include "misc/spatial-filters.frag"
uniform float height;
uniform sampler2D data;
uniform vec2 data_shape;
attribute vec3 position;
attribute vec2 texcoord;
varying vec3 v_position;
varying vec2 v_texcoord;
void main()
{
    float z = height*Bicubic(data, data_shape, texcoord).r;
    gl_Position = <transform>;
    v_texcoord = texcoord;
    v_position = vec3(position.xy, z);
}
"""

fragment = """
#include "misc/spatial-filters.frag"
uniform mat4 model;
uniform mat4 view;
uniform mat4 normal;
uniform sampler2D texture;
uniform float height;
uniform vec4 color;
uniform sampler2D data;
uniform vec2 data_shape;
uniform vec3 light_color[3];
uniform vec3 light_position[3];
varying vec3 v_position;
varying vec2 v_texcoord;
float lighting(vec3 v_normal, vec3 light_position)
{
    // Calculate normal in world coordinates
    vec3 n = normalize(normal * vec4(v_normal,1.0)).xyz;
    // Calculate the location of this fragment (pixel) in world coordinates
    vec3 position = vec3(view * model * vec4(v_position, 1));
    // Calculate the vector from this pixels surface to the light source
    vec3 surface_to_light = light_position - position;
    // Calculate the cosine of the angle of incidence (brightness)
    float brightness = dot(n, surface_to_light) /
                      (length(surface_to_light) * length(n));
    brightness = max(min(brightness,1.0),0.0);
    return brightness;
}
void main()
{
    mat4 model = <transform.trackball_model>;
    // Extract data value
    float value = Bicubic(data, data_shape, v_texcoord).r;
    // Compute surface normal using neighbour values
    float hx0 = height*Bicubic(data, data_shape, v_texcoord+vec2(+1,0)/data_shape).r;
    float hx1 = height*Bicubic(data, data_shape, v_texcoord+vec2(-1,0)/data_shape).r;
    float hy0 = height*Bicubic(data, data_shape, v_texcoord+vec2(0,+1)/data_shape).r;
    float hy1 = height*Bicubic(data, data_shape, v_texcoord+vec2(0,-1)/data_shape).r;
    vec3 dx = vec3(2.0/data_shape.x,0.0,hx0-hx1);
    vec3 dy = vec3(0.0,2.0/data_shape.y,hy0-hy1);
    vec3 v_normal = normalize(cross(dx,dy));
    // Map value to rgb color
    float c = 0.6 + 0.4*texture2D(texture, v_texcoord).r;
    vec4 l1 = vec4(light_color[0] * lighting(v_normal, light_position[0]), 1);
    vec4 l2 = vec4(light_color[1] * lighting(v_normal, light_position[1]), 1);
    vec4 l3 = vec4(light_color[2] * lighting(v_normal, light_position[2]), 1);
    gl_FragColor = color * vec4(c,c,c,1) * (0.5 + 0.5*(l1+l2+l3));
} """


def func3(x, y):
    return (1 - x / 2 + x ** 5 + y ** 3) * np.exp(-(x ** 2) - y ** 2)


class Viewer(QtGui.QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.glumpy_window = glumpy_app.Window(color=(1, 1, 1, 1))

        lay = QtGui.QVBoxLayout(self)
        lay.addWidget(self.glumpy_window._native_window)

        n = 64
        self.surface = gloo.Program(vertex, fragment)
        self.vertices, self.s_indices = primitives.plane(2.0, n=n)
        self.surface.bind(self.vertices)

        I = []
        for i in range(n):
            I.append(i)
        for i in range(1, n):
            I.append(n - 1 + i * n)
        for i in range(n - 1):
            I.append(n * n - 1 - i)
        for i in range(n - 1):
            I.append(n * (n - 1) - i * n)
        self.b_indices = np.array(I, dtype=np.uint32).view(gloo.IndexBuffer)

        x = np.linspace(-2.0, 2.0, 32).astype(np.float32)
        y = np.linspace(-2.0, 2.0, 32).astype(np.float32)
        X, Y = np.meshgrid(x, y)
        Z = func3(X, Y)

        self.surface["data"] = (Z - Z.min()) / (Z.max() - Z.min())
        self.surface["data"].interpolation = gl.GL_NEAREST
        self.surface["data_shape"] = Z.shape[1], Z.shape[0]
        self.surface["u_kernel"] = data.get("spatial-filters.npy")
        self.surface["u_kernel"].interpolation = gl.GL_LINEAR
        self.surface["texture"] = data.checkerboard(32, 24)

        self.transform = Trackball("vec4(position.xy, z, 1.0)")
        self.surface["transform"] = self.transform
        self.glumpy_window.attach(self.transform)

        T = (Z - Z.min()) / (Z.max() - Z.min())

        self.surface["height"] = 0.75
        self.surface["light_position[0]"] = 3, 0, 0 + 5
        self.surface["light_position[1]"] = 0, 3, 0 + 5
        self.surface["light_position[2]"] = -3, -3, +5
        self.surface["light_color[0]"] = 1, 0, 0
        self.surface["light_color[1]"] = 0, 1, 0
        self.surface["light_color[2]"] = 0, 0, 1
        phi, theta = -45, 0
        self.time = 0

        self.glumpy_window.set_handler("on_init", self.on_init)
        self.glumpy_window.set_handler("on_draw", self.on_draw)

    def on_init(self):
        gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
        gl.glPolygonOffset(1, 1)
        gl.glEnable(gl.GL_LINE_SMOOTH)
        gl.glLineWidth(2.5)

    def on_draw(self, dt):
        self.time += dt
        self.glumpy_window.clear()

        self.surface["data"]

        gl.glDisable(gl.GL_BLEND)
        gl.glEnable(gl.GL_DEPTH_TEST)
        gl.glEnable(gl.GL_POLYGON_OFFSET_FILL)
        self.surface["color"] = 1, 1, 1, 1

        self.surface.draw(gl.GL_TRIANGLES, self.s_indices)

        gl.glDisable(gl.GL_POLYGON_OFFSET_FILL)
        gl.glEnable(gl.GL_BLEND)
        gl.glDepthMask(gl.GL_FALSE)
        self.surface["color"] = 0, 0, 0, 1
        self.surface.draw(gl.GL_LINE_LOOP, self.b_indices)
        gl.glDepthMask(gl.GL_TRUE)

        model = self.surface["transform"]["model"].reshape(4, 4)
        view = self.surface["transform"]["view"].reshape(4, 4)
        self.surface["view"] = view
        self.surface["model"] = model
        self.surface["normal"] = np.array(np.matrix(np.dot(view, model)).I.T)
        self.surface["height"] = 0.75 * np.cos(self.time)

    def showEvent(self, event):
        super().showEvent(event)
        self.glumpy_window.dispatch_event("on_resize", *self.glumpy_window.get_size())


class layout:
    def setup(self, window):
        self.window = window
        self.window.resize(width, height)

        self.dialogue = QtGui.QTextEdit()

        self.plot = pg.GraphicsLayoutWidget(self.window)
        self.plot1 = self.plot.addPlot(colspan=1)

        self.centralwidget = QtGui.QWidget(self.window)
        self.horizontallayout = QtGui.QHBoxLayout(self.centralwidget)
        self.window.setCentralWidget(self.centralwidget)

        self.top_viewer = Viewer()
        self.bottom_viewer = Viewer()

        right_container = QtGui.QWidget()
        lay = QtGui.QVBoxLayout(right_container)
        lay.addWidget(self.top_viewer)
        lay.addWidget(self.bottom_viewer)

        self.horizontallayout.addWidget(self.dialogue, stretch=1)
        self.horizontallayout.addWidget(self.plot, stretch=1)
        self.horizontallayout.addWidget(right_container, stretch=1)


class Window(QtGui.QMainWindow, layout):
    def __init__(self, shot=None):
        super(Window, self).__init__()
        self.setup(self)

    def closeEvent(self, event):
        super().closeEvent(event)
        for viewer in (self.top_viewer, self.bottom_viewer):
            viewer.glumpy_window.close()


if __name__ == "__main__":
    app = pg.Qt.QtGui.QApplication([])
    w = Window()
    w.show()
    glumpy_app.run()

enter image description here