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_()
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 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()
Start of the application
Plot the Graph
Full Screen
I have an application with 4 Box on the main window, and I want to had a full screen button in a windows which plot some graph, like on the Pictures on the top.
I first try a method to creating a fullScreen function in my code, linked to the button, but it is no work.
Here is my try :
class mainApplication(QWidget):
def __init__(self, parent=None):
super(mainApplication, self).__init__(parent)
self.layoutMap = {}
self.buttonMap = {}
# Figure Bottom Right
self.figure = plt.figure(figsize=(15,5))
self.figure.set_facecolor('0.915')
self.canvas = FigureCanvas(self.figure)
# Main Figure
self.setGeometry(600, 300, 1000, 600)
self.topLeft()
self.topRight()
self.bottomLeft()
self.bottomRight()
mainLayout = QGridLayout()
mainLayout.addWidget(self.topLeftBox, 1, 0)
mainLayout.addWidget(self.topRightBox, 1, 1)
mainLayout.addWidget(self.bottomLeftBox, 2, 0)
mainLayout.addWidget(self.bottomRightBox, 2, 1)
mainLayout.setRowStretch(1, 1)
mainLayout.setRowStretch(2, 1)
mainLayout.setColumnStretch(0, 1)
mainLayout.setColumnStretch(1, 1)
self.saveLayout(mainLayout, "main")
self.setLayout(mainLayout)
self.setWindowTitle("Title")
QApplication.setStyle("Fusion")
self.show()
def bottomRight(self):
self.bottomRightBox = QGroupBox("Bottom Right")
# Create Select Button
chooseButton = QPushButton("Select")
chooseButton.setMaximumWidth(100)
chooseButton.setMaximumHeight(20)
self.saveButton(chooseButton)
chooseButton.clicked.connect(self.selectFunction)
# Create Full Screen Button
fullScreenButton = QPushButton("Full")
fullScreenButton.setMaximumWidth(100)
fullScreenButton.setMaximumHeight(20)
self.saveButton(fullScreenButton)
fullScreenButton.clicked.connect(self.swichFullScreen)
# Create Layout
layout = QVBoxLayout()
layout.addWidget(self.canvas)
layout.addWidget(chooseButton)
layout.addWidget(fullScreenButton)
layout.addStretch(1)
self.saveLayout(layout, "full")
# Add Layout to GroupBox
self.bottomRightBox.setLayout(layout)
def selectFunction(self):
# Select Data
filePath, _ = QtWidgets.QFileDialog.getOpenFileName(self, 'Open file', '/Data/')
df = pd.read_csv(str(filePath))
x = df.x.tolist()
y = df.y.tolist()
# Create Figure
self.figure.clf()
ax = self.figure.add_subplot(111)
ax.plot(x, y)
ax.set_facecolor('0.915')
ax.set_title('Graphique')
# Draw Graph
self.canvas.draw()
def saveLayout(self,obj, text):
self.layoutMap[text] = obj
def findLayout(self,text):
return self.layoutMap[text]
def saveButton(self,obj):
self.buttonMap[obj.text()] = obj
def findButton(self,text):
return self.buttonMap[text]
def swichFullScreen(self):
self.setLayout(self.findLayout("full"))
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWindow = mainApplication()
sys.exit(app.exec_())
Have you an idea? because for example, if in my initialization I don't do :
self.setLayout(mainLayout)
but :
swichFullScreen()
I have the result that I want, so why call this fonction after the creation of my main layout don't work?
Moreover, I have try an other thing adapter from this : PyQt: Change GUI Layout after button is clicked
But it still not worked because when I clic on the button "full", it switch very well, but the normalWindow object has been delete so the button select stop to work.
If you have a solution for my first idea, I prefer because it avoid the creation of other class, but if it is not possible and that you find a solution for the second solution to avoid the destruction of the object, I take it too.
Here the code for my second solution :
class fullScreenApplication(QWidget):
def __init__(self, parent=None):
super(fullScreenApplication, self).__init__(parent)
self.setGeometry(600, 300, 1000, 600)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setGeometry(600, 300, 1000, 600)
self.normalWindows()
def normalWindows(self):
self.normalBox = mainApplication(self)
self.setCentralWidget(self.normalBox)
self.normalBox.findButton("Full").clicked.connect(self.fullScreenWindow)
self.show()
def fullScreenWindow(self):
self.FullBox = fullScreenApplication(self)
self.FullBox.setLayout(self.normalBox.findLayout("full"))
self.normalBox.findButton("Full").clicked.connect(self.normalWindows)
self.normalBox.findButton("Select").clicked.connect(self.normalBox.selectFunction)
self.setCentralWidget(self.FullBox)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWindow = MainWindow()
sys.exit(app.exec_())
Thank you
Try it:
import sys
import pandas as pd
import matplotlib.pyplot as plt
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
class mainApplication(QWidget):
def __init__(self, parent=None):
super(mainApplication, self).__init__(parent)
self.layoutMap = {}
self.buttonMap = {}
# Figure Bottom Right
self.figure = plt.figure(figsize=(15,5))
self.figure.set_facecolor('0.915')
self.canvas = FigureCanvas(self.figure)
# Main Figure
# self.setGeometry(600, 300, 1000, 600)
self.topLeftBox = self.topLeft()
self.topRightBox = self.topRight()
self.bottomLeftBox = self.bottomLeft()
self.bottomRight()
self.mainLayout = QGridLayout()
self.mainLayout.addWidget(self.topLeftBox, 1, 0)
self.mainLayout.addWidget(self.topRightBox, 1, 1)
self.mainLayout.addWidget(self.bottomLeftBox, 2, 0)
self.mainLayout.addWidget(self.bottomRightBox, 2, 1)
self.mainLayout.setRowStretch(1, 1)
self.mainLayout.setRowStretch(2, 1)
self.mainLayout.setColumnStretch(0, 1)
self.mainLayout.setColumnStretch(1, 1)
self.saveLayout(self.mainLayout, "main")
self.setLayout(self.mainLayout)
self.setWindowTitle("Title")
QApplication.setStyle("Fusion")
# self.show()
def bottomRight(self):
self.bottomRightBox = QGroupBox("Bottom Right")
# Create Select Button
chooseButton = QPushButton("Select")
chooseButton.setMaximumWidth(100)
chooseButton.setMaximumHeight(20)
self.saveButton(chooseButton)
chooseButton.clicked.connect(self.selectFunction)
# Create Full Screen Button
self.fullScreenButton = QPushButton("Full")
self.fullScreenButton.setMaximumWidth(100)
self.fullScreenButton.setMaximumHeight(20)
self.saveButton(self.fullScreenButton)
self.fullScreenButton.clicked.connect(self.swichFullScreen)
# Create Layout
layout = QVBoxLayout()
layout.addWidget(self.canvas)
layout.addWidget(chooseButton)
layout.addWidget(self.fullScreenButton)
layout.addStretch(1)
self.saveLayout(layout, "full")
# Add Layout to GroupBox
self.bottomRightBox.setLayout(layout)
def selectFunction(self):
# Select Data
filePath, _ = QFileDialog.getOpenFileName(self, 'Open file', '/Data/')
df = pd.read_csv(str(filePath))
x = df.x.tolist()
y = df.y.tolist()
# Create Figure
self.figure.clf()
ax = self.figure.add_subplot(111)
ax.plot(x, y)
ax.set_facecolor('0.915')
ax.set_title('Graphique')
# Draw Graph
self.canvas.draw()
def saveLayout(self,obj, text):
self.layoutMap[text] = obj
def findLayout(self,text):
return self.layoutMap[text]
def saveButton(self,obj):
self.buttonMap[obj.text()] = obj
def findButton(self,text):
return self.buttonMap[text]
def swichFullScreen(self):
# self.setLayout(self.findLayout("full")) # ---
# self.show() # ---
# +++ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
if self.sender().text()== "Full":
self.topLeftBox.hide()
self.topRightBox.hide()
self.bottomLeftBox.hide()
self.bottomRightBox.hide()
self.mainLayout.addWidget(self.bottomRightBox, 0, 0, 1, 2)
self.bottomRightBox.show()
self.fullScreenButton.setText("NoFull")
else:
self.bottomRightBox.hide()
self.topLeftBox.show()
self.topRightBox.show()
self.bottomLeftBox.show()
self.mainLayout.addWidget(self.bottomRightBox, 2, 1)
self.bottomRightBox.show()
self.fullScreenButton.setText("Full")
def topLeft(self):
textEdit = QTextEdit()
return textEdit
def topRight(self):
textEdit = QTextEdit()
return textEdit
def bottomLeft(self):
textEdit = QTextEdit()
return textEdit
# +++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWindow = mainApplication()
mainWindow.setGeometry(200, 100, 1000, 600)
mainWindow.show()
sys.exit(app.exec_())
I used this question and tried to use it to do fast live plotting of many subplots.
Unfortunately it is very difficult for me to understand the code and thus I have problems changing it for my needs.
I wanted to create a subplot of 2x2 matrices with 10x10 pixels. Right now I am getting the following:
The code looks like that:
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
import numpy as np
import time
import sys
class App(QtGui.QMainWindow):
def __init__(self, parent=None):
super(App, self).__init__(parent)
#### Create Gui Elements ###########
self.mainbox = QtGui.QWidget()
self.setCentralWidget(self.mainbox)
self.mainbox.setLayout(QtGui.QVBoxLayout())
self.canvas = pg.GraphicsLayoutWidget()
self.mainbox.layout().addWidget(self.canvas)
self.label = QtGui.QLabel()
self.mainbox.layout().addWidget(self.label)
self.view = self.canvas.addViewBox()
self.view.setAspectLocked(True)
self.view.setRange(QtCore.QRectF(0, 0, 50, 50))
self.img = []
for i in range(4):
self.img.append(pg.ImageItem(None, border="w"))
self.canvas.nextRow()
self.view.addItem(self.img[i])
self._update()
def _update(self):
for i in range(4):
self.data = np.random.rand(10,10)
self.img[i].setImage(self.data)
QtCore.QTimer.singleShot(1, self._update)
def sensor_data(n_sensors, x_res, y_res):
return np.random.rand(n_sensors, x_res, y_res)
if __name__ == '__main__':
while True:
# Get sensor data
data = sensor_data(4, 10, 10)
# Pass data to live plot function?
app = QtGui.QApplication(sys.argv)
thisapp = App()
thisapp.show()
sys.exit(app.exec_())
Can someone please show me what I'm doing wrong?
You have only built a single ViewBox and in it you are adding the items and that causes the problem, what you must do is create several ViewBox and add an item as I show you next:
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
import numpy as np
class App(QtGui.QMainWindow):
def __init__(self, parent=None):
super(App, self).__init__(parent)
self.mainbox = QtGui.QWidget()
self.setCentralWidget(self.mainbox)
self.canvas = pg.GraphicsLayoutWidget()
self.label = QtGui.QLabel()
lay = QtGui.QVBoxLayout(self.mainbox)
lay.addWidget(self.canvas)
lay.addWidget(self.label)
self.img_items = []
for i in range(4):
view = self.canvas.addViewBox()
view.setAspectLocked(True)
view.setRange(QtCore.QRectF(0, 0, 10, 10))
it = pg.ImageItem(None, border="w")
view.addItem(it)
self.img_items.append(it)
self.canvas.nextRow()
timer = QtCore.QTimer(self, interval=1)
timer.timeout.connect(self._update)
timer.start()
def _update(self):
for item in self.img_items:
data = np.random.rand(10, 10)
item.setImage(data)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
thisapp = App()
thisapp.show()
sys.exit(app.exec_())
Update:
NxN
n = 2
for i in range(n):
for j in range(n):
view = self.canvas.addViewBox(i, j)
view.setAspectLocked(True)
view.setRange(QtCore.QRectF(0, 0, 10, 10))
it = pg.ImageItem(None, border="w")
view.addItem(it)
self.img_items.append(it)
Update:
If you want to obtain data within a while True you must do it in a new thread to avoid the GUI being blocked, you must also give a small sleep so that the GUI can be updated:
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
import numpy as np
def sensor_data(n_sensors, x_res, y_res):
return np.random.rand(n_sensors, x_res, y_res)
class Thread(QtCore.QThread):
dataChanged = QtCore.pyqtSignal(np.ndarray)
def run(self):
while True:
data = sensor_data(4, 10, 10)
self.dataChanged.emit(data)
QtCore.QThread.msleep(10)
class App(QtGui.QMainWindow):
def __init__(self, parent=None):
super(App, self).__init__(parent)
self.mainbox = QtGui.QWidget()
self.setCentralWidget(self.mainbox)
self.canvas = pg.GraphicsLayoutWidget()
self.label = QtGui.QLabel()
lay = QtGui.QVBoxLayout(self.mainbox)
lay.addWidget(self.canvas)
lay.addWidget(self.label)
self.img_items = []
n = 2
for i in range(n):
for j in range(n):
view = self.canvas.addViewBox(i, j)
view.setAspectLocked(True)
view.setRange(QtCore.QRectF(0, 0, 10, 10))
it = pg.ImageItem(None, border="w")
view.addItem(it)
self.img_items.append(it)
#QtCore.pyqtSlot(np.ndarray)
def update_data(self, data):
for i, v in enumerate(data):
self.img_items[i].setImage(v)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
thisapp = App()
thread = Thread()
thread.dataChanged.connect(thisapp.update_data)
thread.start()
thisapp.show()
sys.exit(app.exec_())
There are a few examples out there of how to do this, but after trying all of them I don't understand how to correctly implement it. I have a program that a button creates a widget with contents inside and assigns it to a grid layout, and it also creates a figure on a canvas. Clicking on the button again creates another widget filled with the same contents and another figure and assigns it to the layout.
One of the contents is a spin box which controls the rotation of the figure. I want each spin box to be able to control the figure that was created with it individually. Here is where I am stuck.
How do I have a general button that can create several widgets, but then on every value change of each spin box, be able to tell which widget it came from so it will rotate the correct figure? I want the widget id or name or however I can access it. Here is what I have so far - Thanks in advance!:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas, NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import numpy as np
import math
from scipy.optimize import fsolve
from mplwindow5 import Ui_mplMainWindow
from cU_widget import cU_Widget
class Viewer(QMainWindow, Ui_mplMainWindow):
def __init__(self, parent = None):
super(Viewer, self).__init__(parent)
self.setupUi(self)
self.count = 0
self.cU_widget = []
self.cU_rotate = []
self.lbl_cU_rotate = []
self.lbl_cU = []
self.btn_cU.clicked.connect(self.add_cU)
def add_cU(self):
self.cU_widget.append(int(self.count))
self.cU_widget[self.count] = QWidget(self.scrollAreaWidgetContents)
self.cU_widget[self.count].setMinimumSize(QSize(101, 81))
self.cU_widget[self.count].setMaximumSize(QSize(101, 81))
self.cU_widget[self.count].setObjectName("cU_widget" + str(self.count+1))
self.lbl_cU.append(int(self.count))
self.lbl_cU[self.count] = QLabel("cU " + str(self.count+1), self.cU_widget[self.count])
self.lbl_cU[self.count].setGeometry(QRect(0, 0, 101, 27))
self.lbl_cU[self.count].setObjectName("lbl_cU_" + str(self.count+1))
self.lbl_cU_rotate.append(int(self.count))
self.lbl_cU_rotate[self.count] = QLabel("R", self.cU_widget[self.count])
self.lbl_cU_rotate[self.count].setGeometry(QRect(6, 50, 20, 20))
self.lbl_cU_rotate[self.count].setObjectName("lbl_cU_rotate" + str(self.count+1))
self.cU_rotate.append(int(self.count))
self.cU_rotate[self.count] = QDoubleSpinBox(self.cU_widget[self.count])
self.cU_rotate[self.count].setGeometry(QRect(20, 40, 71, 27))
self.cU_rotate[self.count].setObjectName("cU_rotate" + str(self.count+1))
self.cU_rotate[self.count].valueChanged.connect(self.cU) # ??? What to use here
self.gridLayout.addWidget(self.cU_widget[self.count], self.count, 0)
self.cU()
def cU(self):
self.cU_rotate[self.count] = self.cU_rotate[self.count].value() # ?? What to use here
rotate = 1
tt = np.arange(0,1, 0.001)
lco_x0 = 0
lco_x1 = 4
lco_y0 = 1
lco_y1 = 3
cU_L_x0 = (lco_x0 * math.cos(math.radians(self.cU_rotate[self.count] + rotate))) - (lco_y0 * math.sin(math.radians(self.cU_rotate[self.count] + rotate)))
cU_L_x1 = (lco_x1 * math.cos(math.radians(self.cU_rotate[self.count] + rotate))) - (lco_y1 * math.sin(math.radians(self.cU_rotate[self.count] + rotate)))
#...
cU_L_y0 = (lco_x0 * math.sin(math.radians(self.cU_rotate[self.count] + rotate))) + (lco_y0 * math.cos(math.radians(self.cU_rotate[self.count] + rotate)))
cU_L_y1 = (lco_x1 * math.sin(math.radians(self.cU_rotate[self.count] + rotate))) + (lco_y1 * math.cos(math.radians(self.cU_rotate[self.count] + rotate)))
#...
cU_L_ax = ( 1 * cU_L_x0)
cU_L_bx = ((-6 * cU_L_x0) +(30 * cU_L_x1))
# ...
cU_L_ay = ( 1 * cU_L_y0)
cU_L_by = ((-6 * cU_L_y0) +(30 * cU_L_y1))
#...
cU_L_xtt = (cU_L_ax * tt**2) + (cU_L_bx * tt) + 1
cU_L_ytt = (cU_L_ay * tt**2) + (cU_L_by * tt) + 1
self.mplContainer.canvas.ax.plot(cU_L_xtt, cU_L_ytt, 'r')
self.mplContainer.canvas.ax.set_ylim([-5, 5])
self.mplContainer.canvas.ax.set_xlim([0, 10])
self.mplContainer.canvas.ax.set_aspect(1)
self.mplContainer.canvas.draw()
self.count += 1
app = QApplication(sys.argv)
viewer = Viewer()
viewer.show()
sys.exit(app.exec_())
here is mplwindow5:
from PyQt4 import QtCore, QtGui
class Ui_mplMainWindow(object):
def setupUi(self, mplMainWindow):
mplMainWindow.setObjectName("mplMainWindow")
mplMainWindow.resize(1171, 826)
self.centralwidget = QtGui.QWidget(mplMainWindow)
self.centralwidget.setObjectName("centralwidget")
self.mplContainer = MplWidget(self.centralwidget)
self.mplContainer.setGeometry(QtCore.QRect(259, 20, 861, 741))
self.mplContainer.setObjectName("mplContainer")
self.inputContainer = QtGui.QWidget(self.centralwidget)
self.inputContainer.setGeometry(QtCore.QRect(10, 20, 251, 741))
self.inputContainer.setObjectName("inputContainer")
self.scrollArea = QtGui.QScrollArea(self.inputContainer)
self.scrollArea.setGeometry(QtCore.QRect(0, 160, 241, 581))
self.scrollArea.setFrameShape(QtGui.QFrame.WinPanel)
self.scrollArea.setLineWidth(1)
self.scrollArea.setMidLineWidth(10)
self.scrollArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setObjectName("scrollArea")
self.scrollAreaWidgetContents = QtGui.QWidget()
self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 226, 577))
self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
self.scrollLayout = QtGui.QVBoxLayout(self.scrollAreaWidgetContents)
self.gridLayout = QtGui.QGridLayout()
self.gridLayout.setObjectName("formLayout")
self.gridLayout.setColumnStretch(0, 0)
self.gridLayout.setColumnStretch(2, 4)
self.scrollLayout.addLayout(self.gridLayout)
self.scrollLayout.addStretch()
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
mplMainWindow.setCentralWidget(self.centralwidget)
self.btn_cU = QtGui.QPushButton("cU", self.inputContainer)
self.btn_cU.setGeometry(QtCore.QRect(0, 0, 31, 27))
self.btn_cU.setObjectName("btn_cU")
QtCore.QMetaObject.connectSlotsByName(mplMainWindow)
from mplwidget import MplWidget
mplwidget:
from PyQt4.QtGui import *
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas, NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
class MplCanvas(FigureCanvas):
def __init__(self):
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
FigureCanvas.__init__(self, self.fig)
FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Preferred)
FigureCanvas.updateGeometry(self)
class MplWidget(QWidget):
def __init__(self, parent = None):
QWidget.__init__(self, parent)
self.main_widget = QWidget(self)
self.canvas = MplCanvas()
self.ntb = NavigationToolbar(self.canvas, self.main_widget)
self.vbl = QGridLayout()
self.vbl.addWidget(self.canvas)
self.vbl.addWidget(self.ntb)
self.setLayout(self.vbl)
As it's not necessary to make extensive mathematical manipulations to solve this issue, I ignored that part. So let's assume that we want to change the slope of several lines using SpinBoxes.
An option would be to make the lines be part of a class that controls the SpinBoxes as well as the matplotlib lines. I called it LineWidget in the code below. When the button is pressed a new LineWidget instance is created and added to a scroll area, where you can manipulate the parameter. Once the parameter changes the line is updated.
Here is a full example where I also simplified the rest of the code.
import sys
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import numpy as np
from PyQt4 import QtGui , QtCore
class Viewer(QtGui.QMainWindow):
def __init__(self, parent = None):
super(Viewer, self).__init__(parent)
self.setupUI()
self.ax = self.fig.add_subplot(111)
self.count = 0
self.container = []
self.button.clicked.connect(self.addLine)
def setupUI(self):
self.centralwidget = QtGui.QWidget(self)
self.setCentralWidget(self.centralwidget)
self.centralwidget.setLayout(QtGui.QHBoxLayout())
self.leftWidget = QtGui.QWidget(self)
self.leftWidget.setMinimumWidth(200)
self.leftWidget.setLayout(QtGui.QVBoxLayout())
self.mplWidget = QtGui.QWidget(self)
self.mplWidget.setLayout(QtGui.QVBoxLayout())
self.fig = Figure()
self.canvas = FigureCanvas(self.fig)
self.ntb = NavigationToolbar(self.canvas, self.mplWidget)
self.mplWidget.layout().addWidget(self.canvas)
self.mplWidget.layout().addWidget(self.ntb)
self.button = QtGui.QPushButton("Push")
self.scrollWidget = QtGui.QWidget()
self.scrollLayout = QtGui.QVBoxLayout()
self.scrollWidget.setLayout(self.scrollLayout)
self.scrollLayout.addStretch()
self.scrollArea = QtGui.QScrollArea()
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setWidget(self.scrollWidget)
self.leftWidget.layout().addWidget(self.button)
self.leftWidget.layout().addWidget(self.scrollArea)
self.centralwidget.layout().addWidget(self.leftWidget)
self.centralwidget.layout().addWidget(self.mplWidget)
def addLine(self):
b = LineWidget(self.count, self.ax)
self.container.append(b)
self.scrollLayout.insertWidget(self.scrollLayout.count() - 1, b)
self.count += 1
class LineWidget(QtGui.QWidget):
def __init__( self, number, ax, R=0, parent=None, **kwargs):
super(LineWidget, self).__init__(parent)
self.number = number
label = QtGui.QLabel("cU " + str(self.number))
self.spin = QtGui.QDoubleSpinBox()
self.spin.setSingleStep(0.2)
self.spin.setRange(-100,100)
self.setLayout(QtGui.QHBoxLayout())
self.layout().addWidget(label)
self.layout().addWidget(self.spin)
self.R = R
self.t = np.linspace(0,1)
self.f = lambda t, R: R*t
self.ax = ax
self.line, = self.ax.plot([],[], **kwargs)
self.update()
self.spin.valueChanged.connect(self.changed)
def changed(self):
self.R = self.spin.value()
self.update()
def update(self):
self.line.set_data(self.t, self.f(self.t, self.R))
self.ax.relim()
self.ax.autoscale_view()
self.ax.figure.canvas.draw_idle()
app = QtGui.QApplication(sys.argv)
viewer = Viewer()
viewer.show()
sys.exit(app.exec_())