I am having some trouble keeping the size even across all elements in my gridLayout.
Ocasionally the inner widgets just completely change size after I update the plot data (on both width and height), and especially after maximizing/minimizing or changing the window size:
What I was hoping to achieve was for the size to be kept evenly distributed regardless of the dimensions of the window:
This is a summary of what I have written to populate the grid, and how I update the CSS to make the borders change color. I notice that if I do not change the CSS then this issue is not apparent.
class ResultsViewer(QtGui.QWidget):
plots = {} #the currently displayed plot widgets
curves = {} #the currently displayed data that store the points
def __init__(self):
super(ResultsViewer, self).__init__()
self.win = QtGui.QMainWindow()
self.win.setCentralWidget(self)
self.win.resize(800, 250)
self.win.setWindowTitle("Cavity Results Viewer")
self.grid = QtGui.QGridLayout(self)
self.win.setWindowFlags(self.win.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
def reset_indicators(self):
for plot in self.plots.keys():
self.plots[plot].setStyleSheet("""
border-top: 5px solid rgba(0,0,0,0);
border-radius: 12px;
""")
def set_indicator_border_color(self, cavnum, result):
color = "lime" if result['decision'] else "red"
self.plots[cavnum].setStyleSheet("""
border-top: 5px solid %s;
border-radius: 12px;
""" % color)
def create_indicators(self, params):
for c, cavnum in enumerate(params.keys()):
r = (4 % (c + 1)) / 4 #every fourth column jump to next row
box = QtGui.QHBoxLayout()
plt = pg.PlotWidget()
plt.setStyleSheet("""
border-top: 5px solid yellow;
border-radius: 12px;
""")
curve_blue = plt.plotItem.plot(pen=None, symbol='o', symbolPen=None, symbolSize=8, symbolBrush=(100, 100, 255, 80)) #points for showing history data
curve_green = plt.plotItem.plot(pen=None, symbol='o', symbolPen=None, symbolSize=8,symbolBrush=(100, 255, 100, 80))
curve_blue_last = plt.plotItem.plot(pen=None, symbol='x', symbolPen=None, symbolSize=18, symbolBrush=(50, 50, 255, 255)) #points for showing the newest data
curve_green_last = plt.plotItem.plot(pen=None, symbol='x', symbolPen=None, symbolSize=18,symbolBrush=(50, 255, 50, 255))
plt.plotItem.setLabel('left', "Amplitude", units='A')
plt.plotItem.setLabel('bottom', "Frequency", units='Hz')
self.plots[cavnum] = plt
self.curves[cavnum] = {"blue": curve_blue,
"blue_last": curve_blue_last,
"green": curve_green,
"green_last": curve_green_last}
box.addWidget(plt)
self.grid.addLayout(box, r, c % 4)
def update(self, cavnum, results):
#update the plots and render all points
...
...
self.set_indicator_border_color(cavnum, result)
[UPDATE]
And this is a fully functional example:
import sys, time
import random
import numpy as np
import pyqtgraph as pg
from PyQt4 import QtCore, QtGui
class ResultsViewer(QtGui.QWidget):
plots = {} #the currently displayed plot widgets
curves = {} #the currently displayed data that store the points
def __init__(self):
super(ResultsViewer, self).__init__()
self.win = QtGui.QMainWindow()
self.win.setCentralWidget(self)
self.win.resize(800, 250)
self.win.setWindowTitle("Cavity Results Viewer")
self.grid = QtGui.QGridLayout(self)
self.win.setWindowFlags(self.win.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
self.win.show()
def reset_indicators(self):
#return #Uncomment this to skip modifying the CSS (resizing problem seems to go away!!!)
for plot in self.plots.keys():
self.plots[plot].setStyleSheet("""
border-top: 5px solid rgba(0,0,0,0);
border-radius: 12px;
""")
def set_indicator_border_color(self, cavnum, result):
#return #Uncomment this to skip modifying the CSS (resizing problem seems to go away!!!)
color = "lime" if result['decision'] else "red"
self.plots[cavnum].setStyleSheet("""
border-top: 5px solid %s;
border-radius: 12px;
""" % color)
def create_indicators(self, params):
for c, cavnum in enumerate(params.keys()):
r = (4 % (c + 1)) / 4 #every fourth column jump to next row
box = QtGui.QHBoxLayout()
plt = pg.PlotWidget()
plt.setStyleSheet("""
border-top: 5px solid yellow;
border-radius: 12px;
""")
curve_blue = plt.plotItem.plot(pen=None, symbol='o', symbolPen=None, symbolSize=8, symbolBrush=(100, 100, 255, 80)) #points for showing history data
curve_green = plt.plotItem.plot(pen=None, symbol='o', symbolPen=None, symbolSize=8,symbolBrush=(100, 255, 100, 80))
plt.plotItem.setLabel('left', "Amplitude", units='A')
plt.plotItem.setLabel('bottom', "Frequency", units='Hz')
self.plots[cavnum] = plt
self.curves[cavnum] = {"blue": curve_blue,
"green": curve_green}
box.addWidget(plt)
self.grid.addLayout(box, r, c % 4)
def update(self, cavnum, results):
#update the plots and render all points
max_history = 1000 #max points per plot to store
result = results[cavnum]
if result.has_key('peaks'):
peaks = result['peaks']
if peaks.has_key('amps'):
amps = np.round(peaks['amps'], 2)
freqs = np.round(peaks['freqs'], 2)
x_blue, y_blue = self.curves[cavnum]['blue'].getData()
x_blue = np.append(freqs, x_blue)[:max_history]
y_blue = np.append(amps, y_blue)[:max_history]
x_blue = x_blue[x_blue != np.array(None)] #remove any none from the initial getData
y_blue = y_blue[y_blue != np.array(None)] #remove any none from the initial getData
self.curves[cavnum]['blue'].setData(x_blue, y_blue)
self.set_indicator_border_color(cavnum, result)
class MyThread(QtCore.QThread):
update = QtCore.pyqtSignal(int, object)
def __init__(self, resutls, parent=None):
super(MyThread, self).__init__(parent)
self.results = resutls #number of plots
def run(self):
while True:
for cavnum in range(len(self.results.keys())):
time.sleep(1)
peaks = {'amps': np.random.rand(3,1), 'freqs': np.random.rand(3,1)}
self.results[cavnum]['decision'] = bool(random.getrandbits(1))
self.results[cavnum]['peaks'] = peaks
self.update.emit(cavnum, self.results)
# create the dialog for zoom to point
class MainApp(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainApp, self).__init__(parent)
# Set up the user interface from Designer.
n = 8
results = {}
for x in range(n):
results[x] = {}
self.thread = MyThread(results)
self.thread.update.connect(self.update)
self.viewer = ResultsViewer()
self.viewer.create_indicators(results)
self.thread.start()
def update(self, cavnum, data):
self.viewer.update(cavnum, data)
if __name__ == "__main__":
app = QtGui.QApplication([])
widget = MainApp()
widget.move(300, 300)
widget.show()
sys.exit(app.exec_())
Considering an even more simplified example made me find the solution.
It seems that setting the styleSheet to the PlotWidget
lets the widget resize itself and thereby negotiate for more or less space with the QGridLayout
. It does not matter that the style wouldn't change the size of the Widget, even setting something completely unrelated like font or even invalid styles reproduces the problem. Using a normal QWidget
instead of a PlotWidget
does not produce this problem.
In any case the solution therefore needs to be to tell QGridLayout
not to change the space for its columns and rows.
This can be done using
QGridLayout.setColumnStretch (self, int column, int stretch)
and
QGridLayout.setRowStretch (self, int row, int stretch)
where stretch
needs to be the same for all rows/columns and larger than zero. See below for the minimal example. It should be rather straight forward to adapt the real code accordingly.
import sys, time
import numpy as np
import pyqtgraph as pg
from PyQt4 import QtCore, QtGui
class MainApp(QtGui.QMainWindow):
def __init__(self):
super(MainApp, self).__init__()
self.win = QtGui.QWidget()
self.setCentralWidget(self.win)
self.resize(800, 250)
self.grid = QtGui.QGridLayout()
self.win.setLayout(self.grid)
self.colors = ["yellow", "green", "red", "blue"]
self.n = 8
self.create_boxes()
self.thread = MyThread()
self.thread.update.connect(self.setBoxColor)
self.thread.start()
self.show()
def create_boxes(self):
self.boxes = []
for i in range(self.n):
r = (4 % (i + 1)) / 4
box = pg.PlotWidget()
#box = QtGui.QWidget() # problem does not appear when using QWidget
self.boxes.append(box)
self.setBoxColor(i,0)
#########
# The following two lines solve the problem!!!
# comment them out to see old unwanted behaviour
self.grid.setColumnStretch(i % 4, 1)
self.grid.setRowStretch(r, 1)
#########
self.grid.addWidget(box, r, i % 4)
def setBoxColor(self, boxnumber, color):
stylesheet = """
border-top: 5px solid %s;
border-radius: 12px;
""" % self.colors[color]
self.boxes[boxnumber].setStyleSheet(stylesheet)
class MyThread(QtCore.QThread):
update = QtCore.pyqtSignal(int, int)
def __init__(self, parent=None):
super(MyThread, self).__init__(parent)
def run(self):
time.sleep(1)
while True:
boxnumber = np.random.randint(0,8)
color = np.random.randint(0,4)
self.update.emit(boxnumber, color)
time.sleep(0.34)
if __name__ == "__main__":
app = QtGui.QApplication([])
widget = MainApp()
widget.move(300, 300)
widget.show()
sys.exit(app.exec_())