I have made this GUI with PyQt5:
How can I make the colour of the points in the bottom plot match the colour bar in the 2D plot above? And change accordingly when I change the bar max/min ?
MWE:
from PyQt5 import QtGui, QtCore
import pyqtgraph as pg
import sys
import numpy as np
width = 1000
height = 500
x = np.linspace(-10,10,100)
def func():
X, Y = np.meshgrid(x, x)
return np.exp(-X**2/10 - Y**2/2)
array = func()
sumxaxis = np.sum(array, axis=0)
sumyaxis = np.sum(array, axis=1)
class layout():
def setup(self, window):
self.window = window
self.window.resize(width, height)
self.centralwidget = QtGui.QWidget(self.window)
self.horizontallayout = QtGui.QHBoxLayout(self.centralwidget)
self.window.setCentralWidget(self.centralwidget)
self.plot = pg.GraphicsLayoutWidget(self.window)
self.horizontallayout.addWidget(self.plot)
self.view = self.plot.addPlot()
self.img = pg.ImageItem(border='k')
self.img.setImage(array, border = 'k')
self.view.addItem(self.img)
self.viewbox = self.view.getViewBox()
self.hist = pg.HistogramLUTItem()
self.hist.setImageItem(self.img)
self.hist.setLevels(0, 1)
self.hist.gradient.loadPreset('viridis')
self.plot.addItem(self.hist, colspan=1)
self.plot.nextRow()
self.plot2 = self.plot.addPlot(colspan=1)
self.plot2.setMaximumHeight(200)
self.plot2.plot(-x,sumyaxis,symbol='o',symbolSize=1, symbolBrush=('w'), pen=None, clear=True)
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_())
UPDATE
I managed to change the scatter plot to the same colourbar as the 2D image, but I have not yet managed to make it change its colour as I drag the colour bar cursor!
New MWE:
from PyQt5 import QtGui, QtCore
import pyqtgraph as pg
import sys
import numpy as np
width = 1000
height = 500
x = np.linspace(-10,10,100)
def func():
X, Y = np.meshgrid(x, x)
return np.exp(-X**2/10 - Y**2/2)
array = func()
sumyaxis = array[:, len(array)//2]
class layout():
def setup(self, window):
self.window = window
self.window.resize(width, height)
self.centralwidget = QtGui.QWidget(self.window)
self.horizontallayout = QtGui.QHBoxLayout(self.centralwidget)
self.window.setCentralWidget(self.centralwidget)
self.plot = pg.GraphicsLayoutWidget(self.window)
self.horizontallayout.addWidget(self.plot)
self.view = self.plot.addPlot()
self.img = pg.ImageItem(border='k')
self.img.setImage(array, border = 'k')
self.view.addItem(self.img)
self.viewbox = self.view.getViewBox()
self.hist = pg.HistogramLUTItem()
self.hist.setImageItem(self.img)
self.hist.setLevels(0, 1)
self.hist.gradient.loadPreset('viridis')
self.plot.addItem(self.hist, colspan=1)
self.colours = self.hist.getLookupTable(img=array)
self.cmap = pg.ColorMap(pos=np.linspace(self.hist.getLevels()[0], self.hist.getLevels()[1], len(self.colours)), color=self.colours)
self.plot.nextRow()
self.plot2 = self.plot.addPlot(colspan=1)
self.plot2.setMaximumHeight(200)
self.c = self.cmap.map(sumyaxis, 'qcolor')
self.plot2.plot(-x,sumyaxis,symbol='o',symbolSize=10, symbolBrush = self.c, pen=None, clear=True)
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([])
w = Window()
sys.exit(app.exec_())
You have to use the sigLookupTableChanged signal and implement the logic to update the symbolBrush of PlotDataItem:
# ...
self.plot.nextRow()
self.plot2 = self.plot.addPlot(colspan=1)
self.plot2.setMaximumHeight(200)
self.curve = self.plot2.plot(
-x,
sumyaxis,
symbol="o",
symbolSize=10,
pen=None,
clear=True,
)
self.hist.sigLookupTableChanged.connect(self.handle_sigLookupTableChanged)
self.handle_sigLevelsChanged()
def handle_sigLookupTableChanged(self):
colours = self.hist.getLookupTable(img=array)
cmap = pg.ColorMap(
pos=np.linspace(*self.hist.getLevels(), len(colours)),
color=colours,
)
c = cmap.map(sumyaxis, "qcolor")
self.curve.opts["symbolBrush"] = c
self.curve.updateItems()
Related
I have a window which holds two plots, one is a 2D plot and the other is a selection of a cut along the y-axis of that plot. I would like to be able to select what cut I want by moving a horizontal bar up to a position and having the 1D plot update. I am having trouble relating the position from the scene where the line item is, to the axes on the plot. The part where I would be figuring this out is in the main window in the function defined plot_position. I am also open to other ideas of how to go about this. Here is a screen-shot for reference:
import sys
from PyQt5.Qt import Qt, QObject, QPen, QPointF
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QSizePolicy
from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsLineItem, QGraphicsView, \
QGraphicsScene, QWidget, QHBoxLayout
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import xarray as xr
import numpy as np
class Signals(QObject):
bttnReleased = pyqtSignal(float, float)
class HLineItem(QGraphicsLineItem):
def __init__(self, signals):
super(HLineItem, self).__init__()
self.signals = signals
self.setPen(QPen(Qt.red, 3))
self.setFlag(QGraphicsLineItem.ItemIsMovable)
self.setCursor(Qt.OpenHandCursor)
self.setAcceptHoverEvents(True)
def mouseMoveEvent(self, event):
orig_cursor_position = event.lastScenePos()
updated_cursor_position = event.scenePos()
orig_position = self.scenePos()
updated_cursor_y = updated_cursor_position.y() - \
orig_cursor_position.y() + orig_position.y()
self.setPos(QPointF(orig_position.x(), updated_cursor_y))
def mouseReleaseEvent(self, event):
x_pos = event.scenePos().x()
y_pos = event.scenePos().y()
self.signals.bttnReleased.emit(x_pos, y_pos)
class PlotCanvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100):
self.fig = Figure(figsize=(width, height), dpi=dpi)
super(PlotCanvas, self).__init__(self.fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QSizePolicy.Expanding,
QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
self.data = xr.DataArray()
self.axes = None
def plot(self, data):
self.data = data
self.axes = self.fig.add_subplot(111)
self.data.plot(ax=self.axes)
self.axes.set_xlim(-.5, .5)
self.draw()
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.signals = Signals()
x = np.linspace(-1, 1, 51)
y = np.linspace(-1, 1, 51)
z = np.linspace(-1, 1, 51)
xyz = np.meshgrid(x, y, z, indexing='ij')
d = np.sin(np.pi * np.exp(-1 * (xyz[0] ** 2 + xyz[1] ** 2 + xyz[2] ** 2))) * np.cos(np.pi / 2 * xyz[1])
self.xar = xr.DataArray(d, coords={"slit": x, 'perp': y, "energy": z}, dims=["slit", "perp", "energy"])
self.cut = self.xar.sel({"perp": 0}, method='nearest')
self.edc = self.cut.sel({'slit': 0}, method='nearest')
self.canvas = PlotCanvas()
self.canvas_edc = PlotCanvas()
self.canvas.plot(self.cut)
self.canvas_edc.plot(self.edc)
self.view = QGraphicsView()
self.scene = QGraphicsScene()
self.line = HLineItem(self.signals)
self.line_pos = [0, 0]
self.layout1 = QHBoxLayout()
self.layout2 = QHBoxLayout()
self.connect_scene()
self.layout1.addWidget(self.view)
self.layout2.addWidget(self.canvas_edc)
self.central = QWidget()
self.main_layout = QHBoxLayout()
self.main_layout.addLayout(self.layout1)
self.main_layout.addLayout(self.layout2)
self.central.setLayout(self.main_layout)
self.setCentralWidget(self.central)
self.signals.bttnReleased.connect(self.plot_position)
def connect_scene(self):
s = self.canvas.figure.get_size_inches() * self.canvas.figure.dpi
self.view.setScene(self.scene)
self.scene.addWidget(self.canvas)
# self.scene.setSceneRect(0, 0, s[0], s[1])
# self.capture_scene_change()
self.line.setLine(0, 0, self.scene.sceneRect().width(), 0)
self.line.setPos(self.line_pos[0], self.line_pos[1])
self.scene.addItem(self.line)
def handle_plotting(self):
self.clearLayout(self.layout2)
self.refresh_edc()
def refresh_edc(self):
self.canvas_edc = PlotCanvas()
self.canvas_edc.plot(self.edc)
self.layout2.addWidget(self.canvas_edc)
def clearLayout(self, layout):
while layout.count():
child = layout.takeAt(0)
if child.widget():
child.widget().deleteLater()
def plot_position(self, x, y):
self.line_pos = [x, y]
plot_bbox = self.canvas.axes.get_position()
# something here to relate the hline position in the scene to the axes positions/plot axes
sel_val = min(self.cut.slit, key=lambda f: abs(f - y)) # this should not be y, but rather
# the corresponding value on the y-axis
self.edc = self.cut.sel({"slit": 0}, method='nearest') # this should not be 0, but rather
# the sel_val
self.handle_plotting()
class App(QApplication):
def __init__(self, sys_argv):
super(App, self).__init__(sys_argv)
self.setAttribute(Qt.AA_EnableHighDpiScaling)
self.mainWindow = MainWindow()
self.mainWindow.setWindowTitle("arpys")
self.mainWindow.show()
def main():
app = App(sys.argv)
sys.exit(app.exec_())
if __name__ == "__main__":
main()
This took me quite a while to figure out. The frustrating part was dealing with Matplotlib's positions of their objects and understanding what classes include what. For a while I was trying to use axes.get_position but this was returning a value for the height that was way too small. I am still not sure for this function what they define as y1 and y0 to give the height. Next I looked at axes.get_window_extent (had a similar problem) and axes.get_tightbbox. the tightbbox function returns the bounding box of the axes including their decorators (xlabel, title, etc) which was better but was actually giving me a height that was beyond the extent I wanted since it included these decorators. Finally, I found that what I really wanted with the length of the spine! In the end I used the spine.get_window_extent() function and this was exactly what I needed. I have attached the updated code.
It was helpful to look at this diagram of the inheritances to see what I was dealing with/looking for.
import sys
from PyQt5.Qt import Qt, QObject, QPen, QPointF
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QSizePolicy
from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsLineItem, QGraphicsView, \
QGraphicsScene, QWidget, QHBoxLayout
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import xarray as xr
import numpy as np
class Signals(QObject):
bttnReleased = pyqtSignal(float)
class HLineItem(QGraphicsLineItem):
def __init__(self, signals):
super(HLineItem, self).__init__()
self.signals = signals
self.setPen(QPen(Qt.red, 3))
self.setFlag(QGraphicsLineItem.ItemIsMovable)
self.setCursor(Qt.OpenHandCursor)
self.setAcceptHoverEvents(True)
def mouseMoveEvent(self, event):
orig_cursor_position = event.lastScenePos()
updated_cursor_position = event.scenePos()
orig_position = self.scenePos()
updated_cursor_y = updated_cursor_position.y() - \
orig_cursor_position.y() + orig_position.y()
self.setPos(QPointF(orig_position.x(), updated_cursor_y))
def mouseReleaseEvent(self, event):
y_pos = event.scenePos().y()
self.signals.bttnReleased.emit(y_pos)
class PlotCanvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100):
self.fig = Figure(figsize=(width, height), dpi=dpi)
super(PlotCanvas, self).__init__(self.fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QSizePolicy.Expanding,
QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
self.data = xr.DataArray()
self.axes = None
def plot(self, data):
self.data = data
self.axes = self.fig.add_subplot(111)
self.data.plot(ax=self.axes)
self.fig.subplots_adjust(left=0.2)
self.fig.subplots_adjust(bottom=0.2)
self.axes.set_xlim(-.5, .5)
self.draw()
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.signals = Signals()
x = np.linspace(-1, 1, 51)
y = np.linspace(-1, 1, 51)
z = np.linspace(-1, 1, 51)
xyz = np.meshgrid(x, y, z, indexing='ij')
d = np.sin(np.pi * np.exp(-1 * (xyz[0] ** 2 + xyz[1] ** 2 + xyz[2] ** 2))) * np.cos(np.pi / 2 * xyz[1])
self.xar = xr.DataArray(d, coords={"slit": x, 'perp': y, "energy": z}, dims=["slit", "perp", "energy"])
self.cut = self.xar.sel({"perp": 0}, method='nearest')
self.edc = self.cut.sel({'slit': 0}, method='nearest')
self.canvas = PlotCanvas()
self.canvas_edc = PlotCanvas()
self.canvas.plot(self.cut)
self.canvas_edc.plot(self.edc)
self.view = QGraphicsView()
self.scene = QGraphicsScene()
self.line = HLineItem(self.signals)
self.line_pos = [0, 0]
self.layout1 = QHBoxLayout()
self.layout2 = QHBoxLayout()
self.connect_scene()
self.layout1.addWidget(self.view)
self.layout2.addWidget(self.canvas_edc)
self.central = QWidget()
self.main_layout = QHBoxLayout()
self.main_layout.addLayout(self.layout1)
self.main_layout.addLayout(self.layout2)
self.central.setLayout(self.main_layout)
self.setCentralWidget(self.central)
self.signals.bttnReleased.connect(self.plot_position)
def connect_scene(self):
s = self.canvas.figure.get_size_inches() * self.canvas.figure.dpi
self.view.setScene(self.scene)
self.scene.addWidget(self.canvas)
self.scene.setSceneRect(0, 0, s[0], s[1])
# self.capture_scene_change()
self.line.setLine(0, 0, self.scene.sceneRect().width(), 0)
self.line.setPos(self.line_pos[0], self.line_pos[1])
self.scene.addItem(self.line)
def handle_plotting(self):
self.clearLayout(self.layout2)
self.refresh_edc()
def refresh_edc(self):
self.canvas_edc = PlotCanvas()
self.canvas_edc.plot(self.edc)
self.layout2.addWidget(self.canvas_edc)
def clearLayout(self, layout):
while layout.count():
child = layout.takeAt(0)
if child.widget():
child.widget().deleteLater()
def plot_position(self, y):
rel_pos = lambda x: abs(self.scene.sceneRect().height() - x)
bbox = self.canvas.axes.spines['left'].get_window_extent()
plot_bbox = [bbox.y0, bbox.y1]
if rel_pos(y) < plot_bbox[0]:
self.line.setPos(0, rel_pos(plot_bbox[0]))
elif rel_pos(y) > plot_bbox[1]:
self.line.setPos(0, rel_pos(plot_bbox[1]))
self.line_pos = self.line.pos().y()
size_range = len(self.cut.slit)
r = np.linspace(plot_bbox[0], plot_bbox[1], size_range).tolist()
corr = list(zip(r, self.cut.slit.values))
sel_val = min(r, key=lambda f: abs(f - rel_pos(self.line_pos)))
what_index = r.index(sel_val)
self.edc = self.cut.sel({"slit": corr[what_index][1]}, method='nearest')
self.handle_plotting()
class App(QApplication):
def __init__(self, sys_argv):
super(App, self).__init__(sys_argv)
self.setAttribute(Qt.AA_EnableHighDpiScaling)
self.mainWindow = MainWindow()
self.mainWindow.setWindowTitle("arpys")
self.mainWindow.show()
def main():
app = App(sys.argv)
sys.exit(app.exec_())
if __name__ == "__main__":
main()
I have an issue with a matplotlib figure that I've embedded in a pyqt5 QScrollArea.
My issue is when I'm scrolling down, it is like only the top of the matplotlib has been updated.
For instance, in the image below, the curves below the curve number 35 are not updated properly.
This only happens with mac OS but it run well on Windows 10.
I don't find the issue in the code.
Update
To me, it looks like the matplotlib figure doesn't match with the QSrollBar. I try to explain more. When I scroll down with the Qsrollbar, the Matplotlib figure is going down but not as fast as the scrollbar. I see that with the number of curve that is increasing when I scroll down. It is like the Qscrollbar go down too fast with respect to the matplotlib figure update.
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
import matplotlib
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import numpy as np
import time
class Viewer(QMainWindow):
def __init__(self, parent=None):
super(Viewer, self).__init__()
self.parent = parent
#######################################
self.centralWidget = QWidget()
self.setCentralWidget(self.centralWidget)
self.mainVBOX_param_scene = QVBoxLayout()
self.mascene = plot(self)
self.paramPlotV = QVBoxLayout()
self.horizontalSliders = QScrollBar(Qt.Horizontal)
self.horizontalSliders.setFocusPolicy(Qt.StrongFocus)
self.horizontalSliders.valueChanged.connect(self.update_plot)
self.horizontalSliders.setMinimum(0)
self.horizontalSliders.setMaximum(1)
self.paramPlot = QHBoxLayout()
l_gain = QLabel('Gain')
self.e_gain = QLineEdit('5')
l_win = QLabel('Window')
self.e_win = QLineEdit('10')
l_spacing = QLabel('vertical spacing')
self.e_spacing = QLineEdit('10')
l_linewidth = QLabel('linewidth')
self.e_linewidth = QLineEdit('1')
self.e_gain.returnPressed.connect(self.update_plot)
self.e_win.returnPressed.connect(self.udpate_plot_plus_slider)
self.e_spacing.returnPressed.connect(self.update_plot)
self.e_linewidth.returnPressed.connect(self.update_plot)
self.paramPlot.addWidget(l_gain)
self.paramPlot.addWidget(self.e_gain)
self.paramPlot.addWidget(l_win)
self.paramPlot.addWidget(self.e_win)
self.paramPlot.addWidget(l_spacing)
self.paramPlot.addWidget(self.e_spacing)
self.paramPlot.addWidget(l_linewidth)
self.paramPlot.addWidget(self.e_linewidth)
self.paramPlotV.addWidget(self.horizontalSliders)
self.paramPlotV.addLayout(self.paramPlot)
self.mainVBOX_param_scene.addWidget(self.mascene)
self.mainVBOX_param_scene.addLayout(self.paramPlotV)
self.centralWidget.setLayout(self.mainVBOX_param_scene)
self.Fs = 1024
self.Sigs_dict = np.random.rand(50,20*self.Fs)
self.t = np.arange(self.Sigs_dict.shape[1])/self.Fs
self.parent.processEvents()
self.update()
def updateslider(self):
self.horizontalSliders.setMinimum(0)
self.horizontalSliders.setMaximum(np.ceil(self.t[-1]/int(self.e_win.text()))-1)
self.horizontalSliders.setPageStep(1)
self.horizontalSliders.update()
def udpate_plot_plus_slider(self):
self.updateslider()
self.mascene.update()
def update_plot(self):
t0 = time.time()
self.mascene.update()
print('time old:', time.time()-t0)
def update(self):
self.updateslider()
self.mascene.modify_sigs()
self.mascene.update()
class plot(QGraphicsView):
def __init__(self, parent=None):
super(plot, self).__init__(parent)
self.parent = parent
self.scene = QGraphicsScene(self)
self.setScene(self.scene)
self.figure = plt.figure(facecolor='white')#Figure()
self.canvas = FigureCanvas(self.figure)
self.widget = QWidget()
self.widget.setLayout(QVBoxLayout())
self.widget.layout().setContentsMargins(0, 0, 0, 0)
self.widget.layout().setSpacing(0)
self.scroll = QScrollArea(self.widget)
self.scroll.setWidget(self.canvas)
layout = QVBoxLayout()
layout.addWidget(self.scroll)
self.setLayout(layout)
def modify_sigs(self):
self.Sigs_dict = self.parent.Sigs_dict
self.t = self.parent.t
self.Fs= self.parent.Fs
def update(self):
win_num = self.parent.horizontalSliders.value()
self.figure.clear()
plt.figure(self.figure.number)
plt.subplots_adjust(left=0.1, bottom=0.01, right=1, top=1, wspace=0.0 , hspace=0.0 )
self.axes = plt.subplot(1, 1, 1)
gain = float(self.parent.e_gain.text())
win= float(self.parent.e_win.text())
self.spacing = float(self.parent.e_spacing.text())
linewidth = float(self.parent.e_linewidth.text())
ts = int(win*(win_num) * self.Fs)
te = ts + int(win * self.Fs)
if te > len(self.t):
te=len(self.t)
for i in range(self.Sigs_dict.shape[0]):
line, = plt.plot(self.t[ts:te], gain*(self.Sigs_dict[i,ts:te]-np.mean(self.Sigs_dict[i,ts:te]))+i*self.spacing, linewidth=linewidth )
self.axes.autoscale(enable=True, axis='both', tight=True)
self.axes.set_ylim((-self.spacing,(self.Sigs_dict.shape[0]+1)*self.spacing))
self.axes.set_xlim((ts/ self.Fs, ts / self.Fs + win ))
self.axes.set_yticks(np.arange(self.Sigs_dict.shape[0]) * self.spacing)
self.axes.set_yticklabels([str(n) for n in np.arange(self.Sigs_dict.shape[0])])
self.canvas.setGeometry(0, 0, self.parent.width()-100, (self.parent.height()-100)*self.spacing)
self.canvas.draw()
def main():
app = QApplication(sys.argv)
app.setStyle('Windows')
ex = Viewer(app)
ex.showMaximized()
sys.exit(app.exec())
if __name__ == '__main__':
main()
I update the version of my matplotlib module from 3.1.1 to 3.2.0rc1 and it solves the issue.
I've created a small app and I'm trying to make it so that when the main window is resized (and the GraphicsView and scene are too) that the whole scene (pixmap and rectangles) scale vertically to fit completely inside the GraphicsView. I don't want a vertical scrollbar and I don't want it to scale horizontally.
I can't figure out how to scale the scene properly. I use a GraphicsScene to contain a graph and a couple vertical rectangle "markers". When I can scale the graph to fit by redrawing the pixmap and then reattach it, the z-order is wrong AND the rectangle widgets are not scaled with it.
I need to keep track of the rectangle widgets, so I can't just keep deleting and re-adding them as there's meta data along with each one.
I know about fitInView (from here: Issue with fitInView of QGraphicsView when ItemIgnoresTransformations is on) that applies to the containing GraphicsView, but I don't understand why it needs a parameter. I just want the scene to fit in the GraphicsView (vertically but not horizontally) so why doesn't GraphicsView just scale everything in the scene to fit inside it's current size? What should the parameter look like to get the scene to fit vertically?
In the resizeEvent I can redraw the pixmap and re-add, but then it covers the rectangles as the z-order is messed up. Also, it doesn't stay centered vertically in the scene and I would need to copy over the meta data.
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_()
My solution will fit the height of the smallest rectangle that encapsulates all the items (sceneRect) to the viewport of the QGraphicsView. So set the height of the items a value not so small so that the image quality is not lost. I have also scaled the items using the QTransforms. In addition, the QGraphicsView coordinate system was inverted since by default the vertical axis is top-bottom and I have inverted it so that the painting is more consistent with the data.
I have refactored the OP code to make it more scalable, there is a GraphItem that takes the data (x, y) and the image dimensions.
Considering the above, the solution is:
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()
I'm very new to PyQt and I'm finding ordering the widgets in the window pretty hard.
I have a plot that updates in real-time, a table that updates every time that the user clicks on the plot, and two buttons (start/stop).
The current layout is behaving this way:
I want it to look something like this:
Where "other widgets" correspond to future Widgets I'll add when this is working (such as a slider or a dropdown list). The table should also be as thin as possible, to maximize the size of the plot and don't have the table occupying unnecessary space. My current code is the following one (it's self-contained):
import sys
import numpy as np
from matplotlib.backends.qt_compat import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.figure import Figure
def onclick(event):
global clicks
clicks.append(event.xdata)
return
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super(ApplicationWindow, self).__init__()
self._title = 'Prueba real-time'
self.setWindowTitle(self._title)
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
layout = QtWidgets.QHBoxLayout(self._main)
dynamic_canvas = FigureCanvas(Figure(figsize=(10, 10)))
layout.addWidget(dynamic_canvas)
self._dynamic_ax = dynamic_canvas.figure.subplots()
dynamic_canvas.figure.canvas.mpl_connect('button_press_event', onclick)
self._dynamic_ax.grid()
self._timer = dynamic_canvas.new_timer(
100, [(self._update_window, (), {})])
self._timer.start()
button_stop = QtWidgets.QPushButton('Stop', self)
layout.addWidget(button_stop)
button_stop.clicked.connect(self.button_pressed)
button_start = QtWidgets.QPushButton('Start', self)
layout.addWidget(button_start)
button_start.clicked.connect(self.button_pressed)
self.table_clicks = QtWidgets.QTableWidget()
self.table_clicks.setRowCount(0)
self.table_clicks.setColumnCount(2)
layout.addWidget(self.table_clicks)
def button_pressed(self):
if self.sender().text() == 'Stop':
self._timer.stop()
if self.sender().text() == 'Start':
self._timer.start()
def _update_window(self):
self._dynamic_ax.clear()
global x, y1, y2, y3, N, count_iter, last_number_clicks
x.append(x[count_iter] + 0.01)
y1.append(np.random.random())
idx_inf = max([count_iter-N, 0])
if last_number_clicks < len(clicks):
for new_click in clicks[last_number_clicks:(len(clicks))]:
rowPosition = self.table_clicks.rowCount()
self.table_clicks.insertRow(rowPosition)
self.table_clicks.setItem(rowPosition,0, QtWidgets.QTableWidgetItem(str(new_click)))
self.table_clicks.setItem(rowPosition,1, QtWidgets.QTableWidgetItem("Descripcion"))
last_number_clicks = len(clicks)
self._dynamic_ax.plot(x[idx_inf:count_iter], y1[idx_inf:count_iter],'-o', color='b')
count_iter += 1
self._dynamic_ax.figure.canvas.draw()
#%%
if __name__ == "__main__":
pressed_key = {}
clicks = []
last_number_clicks = len(clicks)
N = 25
y1 = [np.random.random()]
x = [0]
count_iter = 0
qapp = QtWidgets.QApplication(sys.argv)
app = ApplicationWindow()
app.show()
qapp.exec_()
The structure that you want is a grid so you should use a QGridLayout.
QGridLayout has the addWidget() method where you can indicate the row and column where the widget will be placed, the same with addLayout()
void QGridLayout::addWidget(QWidget *widget, int row, int column, Qt::Alignment alignment = ...)
void QGridLayout::addLayout(QLayout *layout, int row, int column, Qt::Alignment alignment = ...)
In your case the structure should be the following:
QGridLayout
├── Plot (0, 0)
├── Table (0, 1)
├── Other Widgets (1, 0)
└── QVBoxLayout (1, 1)
├── button_start
└── button_stop
Then you can indicate the weight of each column, that is, say that the column width will have a proportional width with respect to column 2 using setColumnStretch():
layout.setColumnStretch(0, 2)
layout.setColumnStretch(1, 1)
Considering the above we have the following:
import sys
import numpy as np
from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.figure import Figure
def onclick(event):
global clicks
clicks.append(event.xdata)
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super(ApplicationWindow, self).__init__()
self._title = 'Prueba real-time'
self.setWindowTitle(self._title)
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
dynamic_canvas = FigureCanvas(Figure(figsize=(10, 10)))
self._dynamic_ax = dynamic_canvas.figure.subplots()
dynamic_canvas.figure.canvas.mpl_connect('button_press_event', onclick)
self._dynamic_ax.grid()
self._timer = dynamic_canvas.new_timer(
100, [(self._update_window, (), {})])
self._timer.start()
button_stop = QtWidgets.QPushButton('Stop', self)
button_stop.clicked.connect(self._timer.stop)
button_start = QtWidgets.QPushButton('Start', self)
button_start.clicked.connect(self._timer.start)
self.table_clicks = QtWidgets.QTableWidget(0, 2)
self.table_clicks.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
other_widget = QtWidgets.QLabel("Other widgets",
font=QtGui.QFont("Times", 60, QtGui.QFont.Bold),
alignment=QtCore.Qt.AlignCenter)
# layouts
layout = QtWidgets.QGridLayout(self._main)
layout.addWidget(dynamic_canvas, 0, 0)
layout.addWidget(self.table_clicks, 0, 1)
layout.addWidget(other_widget, 1, 0)
button_layout = QtWidgets.QVBoxLayout()
button_layout.addWidget(button_stop)
button_layout.addWidget(button_start)
layout.addLayout(button_layout, 1, 1)
layout.setColumnStretch(0, 2)
layout.setColumnStretch(1, 1)
def _update_window(self):
self._dynamic_ax.clear()
global x, y1, y2, y3, N, count_iter, last_number_clicks
x.append(x[count_iter] + 0.01)
y1.append(np.random.random())
idx_inf = max([count_iter-N, 0])
if last_number_clicks < len(clicks):
for new_click in clicks[last_number_clicks:(len(clicks))]:
rowPosition = self.table_clicks.rowCount()
self.table_clicks.insertRow(rowPosition)
self.table_clicks.setItem(rowPosition,0, QtWidgets.QTableWidgetItem(str(new_click)))
self.table_clicks.setItem(rowPosition,1, QtWidgets.QTableWidgetItem("Descripcion"))
last_number_clicks = len(clicks)
self._dynamic_ax.plot(x[idx_inf:count_iter], y1[idx_inf:count_iter],'-o', color='b')
count_iter += 1
self._dynamic_ax.figure.canvas.draw()
#%%
if __name__ == "__main__":
pressed_key = {}
clicks = []
last_number_clicks = len(clicks)
N = 25
y1 = [np.random.random()]
x = [0]
count_iter = 0
qapp = QtWidgets.QApplication(sys.argv)
app = ApplicationWindow()
app.show()
sys.exit(qapp.exec_())
I would personally separate the different parts of the program in their own class and use several vertical and horizontal layouts to position the widgets. This especially helps if you want to add features. Especially for large applications this helps to keep the MainWindow, which always tends to become a mess, to be more simple and easier to understand
import sys
import numpy as np
from matplotlib.backends.qt_compat import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.figure import Figure
def onclick(event):
global clicks
clicks.append(event.xdata)
return
class TableWidget(QtWidgets.QWidget):
def __init__(self,parent,*args,**kwargs):
super().__init__(*args,**kwargs)
self.parent = parent
layout = QtWidgets.QVBoxLayout(self)
self.table_clicks = QtWidgets.QTableWidget()
self.table_clicks.setRowCount(0)
self.table_clicks.setColumnCount(2)
layout.addWidget(self.table_clicks)
button_widget = QtWidgets.QWidget(self)
layout.addWidget(button_widget)
button_layout = QtWidgets.QHBoxLayout(button_widget)
button_stop = QtWidgets.QPushButton('Stop', self)
button_layout.addWidget(button_stop)
button_stop.clicked.connect(self.parent.plot_widget.button_pressed)
button_start = QtWidgets.QPushButton('Start', self)
button_layout.addWidget(button_start)
button_start.clicked.connect(self.parent.plot_widget.button_pressed)
class PlotWidget(QtWidgets.QWidget):
def __init__(self,parent,*args,**kwargs):
super().__init__(parent,*args,**kwargs)
self.parent = parent
layout = QtWidgets.QVBoxLayout(self)
dynamic_canvas = FigureCanvas(Figure(figsize=(10, 10)))
layout.addWidget(dynamic_canvas)
self._dynamic_ax = dynamic_canvas.figure.subplots()
dynamic_canvas.figure.canvas.mpl_connect('button_press_event', onclick)
self._dynamic_ax.grid()
self._timer = dynamic_canvas.new_timer(
100, [(self._update_window, (), {})])
self._timer.start()
def button_pressed(self):
if self.sender().text() == 'Stop':
self._timer.stop()
if self.sender().text() == 'Start':
self._timer.start()
def _update_window(self):
self._dynamic_ax.clear()
global x, y1, y2, y3, N, count_iter, last_number_clicks
x.append(x[count_iter] + 0.01)
y1.append(np.random.random())
idx_inf = max([count_iter-N, 0])
if last_number_clicks < len(clicks):
for new_click in clicks[last_number_clicks:(len(clicks))]:
rowPosition = self.parent.table_widget.table_clicks.rowCount()
self.parent.table_widget.table_clicks.insertRow(rowPosition)
self.parent.table_widget.table_clicks.setItem(rowPosition,0, QtWidgets.QTableWidgetItem(str(new_click)))
self.parent.table_widget.table_clicks.setItem(rowPosition,1, QtWidgets.QTableWidgetItem("Descripcion"))
last_number_clicks = len(clicks)
self._dynamic_ax.plot(x[idx_inf:count_iter], y1[idx_inf:count_iter],'-o', color='b')
count_iter += 1
self._dynamic_ax.figure.canvas.draw()
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super(ApplicationWindow, self).__init__()
self._title = 'Prueba real-time'
self.setWindowTitle(self._title)
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
main_layout = QtWidgets.QHBoxLayout(self._main)
self.plot_widget = PlotWidget(self)
main_layout.addWidget(self.plot_widget)
self.table_widget = TableWidget(self)
main_layout.addWidget(self.table_widget)
#%%
if __name__ == "__main__":
pressed_key = {}
clicks = []
last_number_clicks = len(clicks)
N = 25
y1 = [np.random.random()]
x = [0]
count_iter = 0
qapp = QtWidgets.QApplication(sys.argv)
app = ApplicationWindow()
app.show()
qapp.exec_()
Trying to draw arc and problem is I want to cal draw function form another py file and no luck so far (if draw function in main py file it is ok). I imported another py file but nothing happens. here is the code:
main.py
from PyQt4 import QtGui, Qt, QtCore
import sys
from src.cPrg import cPrg
from PyQt4.Qt import QPen
class mainWindow(QtGui.QWidget):
def __init__(self):
super(mainWindow, self).__init__()
self.otherFile = cPrg()
self.initUI()
def initUI(self):
#self.exitBtn = QtGui.QPushButton('Exit', self)
#self.exitBtn.setGeometry(100,100,60,40)
#self.exitBtn.clicked.connect(self.close_app)
self.label = QtGui.QLabel(self)
self.label.setText(self.otherFile.textas)
self.label.setGeometry(100,140, 60, 40)
self.otherFile.setGeometry(20,20, 20,20)
self.otherFile.startA = 270
self.otherFile.endA = -270
#self.showFullScreen()
self.setGeometry(100, 100, 800, 480)
self.setWindowTitle('Window Title')
self.show()
def close_app(self):
sys.exit()
def main():
app = QtGui.QApplication(sys.argv)
gui = mainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
and anotherfile.py
from PyQt4 import QtGui, QtCore, Qt
from PyQt4.Qt import QPen
class cPrg(QtGui.QWidget):
def __init__(self):
super(cPrg, self).__init__()
self.startA = 0
self.endA = 0
self.textas = 'bandom'
def paintEvent(self, e):
painter = QtGui.QPainter(self)
painter.setRenderHint(painter.Antialiasing)
rect = e.rect
r = QtCore.QRect(200,200,20,20) #<-- create rectangle
size = r.size() #<-- get rectangle size
r.setSize(size*10) #<-- set size
startAngle = self.startA*16 #<-- set start angle to draw arc
endAngle = self.endA*16 #<-- set end arc angle
painter.setPen(QPen(QtGui.QColor('#000000'))) #<-- arc color
#painter.setBrush(QtCore.Qt.HorPattern)
painter.drawArc(r, startAngle, endAngle) #<-- draw arc
painter.end()
super(cPrg,self).paintEvent(e)
What I doing wrong and how can I change line width?
Thank you
EDIT: all painting I made in main py file, here is the code:
from PyQt4 import QtGui, Qt, QtCore
import sys
from src.cprg import cPrg
from src.cprogress import GaugeWidget
from PyQt4.Qt import QPen
class mainWindow(QtGui.QWidget):
def __init__(self):
self.otherFile = cPrg()
self.gauge = GaugeWidget()
self.i = 0
self.lineWidth = 3
self._value = 0
self.completed = 0
super(mainWindow, self).__init__()
self.initUI()
def initUI(self):
self.setValue(.5)
#self.showFullScreen()
self.setGeometry(100, 100, 800, 480)
self.setWindowTitle('Window Title')
self.show()
def close_app(self):
sys.exit()
def setValue(self, val):
val = float(min(max(val, 0), 1))
self._value = -270 * val
self.update()
def setLineWidth(self, lineWidth):
self.lineWidth = lineWidth
def paintEvent(self, e):
painter = QtGui.QPainter(self)
painter.setRenderHint(painter.Antialiasing)
rect = e.rect
outerRadius = min(self.width(),self.height())
#arc line
r = QtCore.QRect(20,20,outerRadius-10,outerRadius-10) #<-- create rectangle
size = r.size() #<-- get rectangle size
r.setSize(size*.4) #<-- set size
startAngle = 270*16 #<-- set start angle to draw arc
endAngle = -270*16 #<-- set end arc angle
painter.setPen(QPen(QtGui.QColor('#000000'), self.lineWidth)) #<-- arc color
#painter.setBrush(QtCore.Qt.HorPattern)
painter.drawArc(r, startAngle, endAngle) #<-- draw arc
#arc prg
painter.save()
painter.setPen(QPen(QtGui.QColor('#ffffff'), 20))
painter.drawArc(r, startAngle, self._value*16)
painter.restore()
painter.end()
super(mainWindow,self).paintEvent(e)
def main():
app = QtGui.QApplication(sys.argv)
gui = mainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
this is my simple circular progress bar, now question is how can I place setValue, setlineWidth and paintEvent functions into separate py file and then just call by importing this file and class with these functions? I tried this:
from PyQt4 import QtGui, Qt, QtCore
import sys
from src.cprg import cPrg #<import progressbar
from src.cprogress import GaugeWidget
from PyQt4.Qt import QPen
class mainWindow(QtGui.QWidget):
def __init__(self):
self.otherFile = cPrg() #< imported progress bar
self.gauge = GaugeWidget()
self.i = 0
self.lineWidth = 3
self._value = 0
self.completed = 0
super(mainWindow, self).__init__()
self.initUI()
def initUI(self):
self.otherFile.setGeometry(10,10,100,100) #<<<< progress bar size
self.otherFile.setValue(0.5) #< progress bar value
and this is not working.
Change line width with QPen(color, line_width), update() redraw with paintEvent.
try with this:
from PyQt4 import QtGui, QtCore
class cPrg:
def __init__(self):
self.linewidth = 0
def setLineWidth(self, linewidth):
self.linewidth = linewidth
def drawArc(self, painter):
painter.setRenderHint(painter.Antialiasing)
r = QtCore.QRect(200,200,20,20) #<-- create rectangle
size = r.size() #<-- get rectangle size
r.setSize(size*10) #<-- set size
startAngle = self.startA*16 #<-- set start angle to draw arc
endAngle = self.endA*16 #<-- set end arc angle
painter.setPen(QtGui.QPen(QtGui.QColor('#000000'), self.linewidth)) #<-- arc color
painter.drawArc(r, startAngle, endAngle) #<-- draw arc
class mainWindow(QtGui.QWidget):
def __init__(self):
super(mainWindow, self).__init__()
self.otherFile = cPrg()
self.initUI()
self.i = 0
def initUI(self):
self.label = QtGui.QLabel(self)
self.label.setText(self.otherFile.textas)
self.label.setGeometry(100,140, 60, 40)
self.otherFile.startA = 270
self.otherFile.endA = -270
self.setGeometry(100, 100, 800, 480)
self.setWindowTitle('Window Title')
timer = QtCore.QTimer(self)
timer.timeout.connect(self.changeLineWidth)
timer.start(1000)
def changeLineWidth(self):
self.otherFile.setLineWidth(self.i)
self.i += 1
self.i %= 60
self.update()
def paintEvent(self, e):
painter = QtGui.QPainter(self)
self.otherFile.drawArc(painter)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
w = mainWindow()
w.show()
sys.exit(app.exec_())
If you want a circular progressbar, your must be override QProgressbar:
from math import ceil
from PyQt4 import QtGui, Qt, QtCore
import sys
class cPrg(QtGui.QProgressBar):
def __init__(self, parent=None):
super(cPrg, self).__init__(parent)
self.linewidth = 1
def factor(self, value):
a = 360 / (self.maximum() - self.minimum())
b = -a / (self.maximum() - self.minimum())
return a*value + b
def setLineWidth(self, linewidth):
self.linewidth = linewidth
self.update()
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.setRenderHint(painter.Antialiasing)
r = self.rect()
val = ceil(self.factor(self.value()))
nr = QtCore.QRect(r.topLeft() + QtCore.QPoint(self.linewidth, self.linewidth),
QtCore.QSize(r.width()-2*self.linewidth, r.height()-2*self.linewidth))
painter.setPen(QtGui.QPen(QtGui.QColor('#000000'), self.linewidth))
painter.drawArc(nr, 0*16, val*16)
class mainWindow(QtGui.QWidget):
def __init__(self):
super(mainWindow, self).__init__()
self.otherFile = cPrg(self)
self.otherFile.setMinimum(0)
self.otherFile.setMaximum(360)
self.otherFile.setValue(90)
self.initUI()
timerLW = QtCore.QTimer(self)
timerLW.timeout.connect(self.changeLW)
timerLW.start(100)
timerVal = QtCore.QTimer(self)
timerVal.timeout.connect(self.updateValue)
timerVal.start(100)
def initUI(self):
self.label = QtGui.QLabel(self)
self.label.setText("test")
self.label.setGeometry(200, 200, 60, 40)
self.otherFile.setGeometry(0, 0, 200, 200)
self.setGeometry(0, 0, 800, 480)
self.setWindowTitle('Window Title')
def changeLW(self):
lw = (self.otherFile.linewidth + 1) % 20
self.otherFile.setLineWidth(lw)
def updateValue(self):
self.otherFile.setValue(self.otherFile.value() + 1)
def main():
app = QtGui.QApplication(sys.argv)
gui = mainWindow()
gui.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()