I'm trying use VTK to plot points, then interactively update their locations with a given set of point locations.
I can interactively use a polydata object to plot points, however they do not update when I call self.polydata.Update(). The points will update when I call self.polydata.GetCellData().SetScalars(someCharArray)
Is this a bug in VTK, or am I not updating the point coordinates correctly?
I have included an example script. If you comment out self.polydata.GetCellData().SetScalars(someCharArray) in sliderCallback, the plot will not update the point's coordinates when you use the slider. However they will update if you leave that line in.
Thanks!
import numpy as np
import vtk
from vtk.qt4.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
from PyQt4 import QtGui
import sys
class ViewerWithScrollBar(QtGui.QMainWindow):
def __init__(self, parent=None):
super(ViewerWithScrollBar, self).__init__(parent)
# Define the renderer and Qt window ------------------------
self.frame = QtGui.QFrame()
self.hl = QtGui.QHBoxLayout()
self.vtkWidget = QVTKRenderWindowInteractor(self.frame)
self.hl.addWidget(self.vtkWidget)
self.ren = vtk.vtkRenderer()
self.vtkWidget.GetRenderWindow().AddRenderer(self.ren)
self.iren = self.vtkWidget.GetRenderWindow().GetInteractor()
self.iren.SetInteractorStyle(vtk.vtkInteractorStyleTrackballCamera())
self.ren.ResetCamera()
self.frame.setLayout(self.hl)
self.setCentralWidget(self.frame)
# Point coordinate data ---------------------------------
self.coordData = {}
self.coordData[0] = np.array([[0,0,0], [1,0,0], [1,1,0]])
self.coordData[1] = self.coordData[0] + np.array([[0.2, 0.1, -0.05], [0,0,0], [0,0,0]])
self.coordData[2] = self.coordData[1] + np.array([[0.2, 0.1, -0.05], [0,0,0], [0,0,0]])
# Define the slider bar and add it to the window ---------------
slider = QtGui.QSlider()
slider.setAccessibleName('Time index')
slider.setRange(0, len(self.coordData)-1)
slider.valueChanged.connect(self.sliderCallback)
self.hl.addWidget(slider)
# Create the polydata object -----------------------------
points = vtk.vtkPoints()
points.SetNumberOfPoints(len(self.coordData[0]))
self.polydata = vtk.vtkPolyData()
for i in range(len(self.coordData[0])):
points.SetPoint(i, self.coordData[0][i])
self.polydata.SetPoints(points)
ptsFilter = vtk.vtkVertexGlyphFilter()
ptsFilter.SetInputConnection(self.polydata.GetProducerPort())
ptsMapper = vtk.vtkPolyDataMapper()
ptsMapper.SetInputConnection(ptsFilter.GetOutputPort())
ptsActor = vtk.vtkActor()
ptsActor.SetMapper(ptsMapper)
ptsActor.GetProperty().SetPointSize(10)
self.ren.AddActor(ptsActor)
self.show()
self.iren.Initialize()
def sliderCallback(self):
index = self.sender().value() # The index that the slider bar is currently on
someCharArray = vtk.vtkUnsignedCharArray()
points = self.polydata.GetPoints()
for i in range(len(self.coordData[index])):
points.SetPoint(i, self.coordData[index][i])
self.polydata.GetCellData().SetScalars(someCharArray) # For some reason the polydata won't update unless this is called.
# self.polydata.Update()
self.iren.Render()
return
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = ViewerWithScrollBar()
sys.exit(app.exec_())
With lib's advice, I modified the code. Calling self.polydata.Modified() in the sliderCallback method fixed the problem
import numpy as np
import vtk
from vtk.qt4.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
from PyQt4 import QtGui
import sys
class ViewerWithScrollBar(QtGui.QMainWindow):
def __init__(self, parent=None):
super(ViewerWithScrollBar, self).__init__(parent)
# Define the renderer and Qt window ------------------------
self.frame = QtGui.QFrame()
self.hl = QtGui.QHBoxLayout()
self.vtkWidget = QVTKRenderWindowInteractor(self.frame)
self.hl.addWidget(self.vtkWidget)
self.ren = vtk.vtkRenderer()
self.vtkWidget.GetRenderWindow().AddRenderer(self.ren)
self.iren = self.vtkWidget.GetRenderWindow().GetInteractor()
self.iren.SetInteractorStyle(vtk.vtkInteractorStyleTrackballCamera())
self.ren.ResetCamera()
self.frame.setLayout(self.hl)
self.setCentralWidget(self.frame)
# Point coordinate data ---------------------------------
self.coordData = {}
self.coordData[0] = np.array([[0,0,0], [1,0,0], [1,1,0]])
self.coordData[1] = self.coordData[0] + np.array([[0.2, 0.1, -0.05], [0,0,0], [0,0,0]])
self.coordData[2] = self.coordData[1] + np.array([[0.2, 0.1, -0.05], [0,0,0], [0,0,0]])
# Define the slider bar and add it to the window ---------------
slider = QtGui.QSlider()
slider.setAccessibleName('Time index')
slider.setRange(0, len(self.coordData)-1)
slider.valueChanged.connect(self.sliderCallback)
self.hl.addWidget(slider)
# Create the polydata object -----------------------------
points = vtk.vtkPoints()
points.SetNumberOfPoints(len(self.coordData[0]))
self.polydata = vtk.vtkPolyData()
for i in range(len(self.coordData[0])):
points.SetPoint(i, self.coordData[0][i])
self.polydata.SetPoints(points)
ptsFilter = vtk.vtkVertexGlyphFilter()
ptsFilter.SetInputData(self.polydata)
ptsMapper = vtk.vtkPolyDataMapper()
ptsMapper.SetInputConnection(ptsFilter.GetOutputPort())
ptsActor = vtk.vtkActor()
ptsActor.SetMapper(ptsMapper)
ptsActor.GetProperty().SetPointSize(10)
self.ren.AddActor(ptsActor)
self.show()
self.iren.Initialize()
def sliderCallback(self):
index = self.sender().value() # The index that the slider bar is currently on
points = self.polydata.GetPoints()
for i in range(len(self.coordData[index])):
points.SetPoint(i, self.coordData[index][i])
self.polydata.Modified()
self.iren.Render()
return
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = ViewerWithScrollBar()
sys.exit(app.exec_())
I revised it to the version of PyQt5 and ran and tested the code above using latest vtk 8.1.0 and PyQt5 5.10.1 under Windows 10, PyCharm 2018.1 (Community Edition). Look like "points" needs to be modified instead of "self.polydata". Otherwise, it won't get the updated point shown.
import numpy as np
import vtk
from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
from PyQt5 import Qt
import sys
class ViewerWithScrollBar(Qt.QMainWindow):
def __init__(self, parent=None):
super(ViewerWithScrollBar, self).__init__(parent)
# Define the renderer and Qt window ------------------------
self.frame = Qt.QFrame()
self.hl = Qt.QHBoxLayout()
self.vtkWidget = QVTKRenderWindowInteractor(self.frame)
self.hl.addWidget(self.vtkWidget)
self.ren = vtk.vtkRenderer()
self.vtkWidget.GetRenderWindow().AddRenderer(self.ren)
self.iren = self.vtkWidget.GetRenderWindow().GetInteractor()
self.iren.SetInteractorStyle(vtk.vtkInteractorStyleTrackballCamera())
self.ren.ResetCamera()
self.frame.setLayout(self.hl)
self.setCentralWidget(self.frame)
# Point coordinate data ---------------------------------
self.coordData = {}
self.coordData[0] = np.array([[0,0,0], [1,0,0], [1,1,0]])
self.coordData[1] = self.coordData[0] + np.array([[0.2, 0.0, -0.05], [0,2,0], [0,0,3.5]])
self.coordData[2] = self.coordData[1] + np.array([[0.2, 10.0, -0.05], [0,5.0,0], [0,0,0]])
# Define the slider bar and add it to the window ---------------
slider = Qt.QSlider()
slider.setAccessibleName('Time index')
slider.setRange(0, len(self.coordData)-1)
slider.valueChanged.connect(self.sliderCallback)
self.hl.addWidget(slider)
# Create the polydata object -----------------------------
points = vtk.vtkPoints()
points.SetNumberOfPoints(len(self.coordData[0]))
self.polydata = vtk.vtkPolyData()
for i in range(len(self.coordData[0])):
points.SetPoint(i, self.coordData[0][i])
self.polydata.SetPoints(points)
self.ptsFilter = vtk.vtkVertexGlyphFilter()
self.ptsFilter.SetInputData(self.polydata)
ptsMapper = vtk.vtkPolyDataMapper()
ptsMapper.SetInputConnection(self.ptsFilter.GetOutputPort())
self.ptsActor = vtk.vtkActor()
self.ptsActor.SetMapper(ptsMapper)
self.ptsActor.GetProperty().SetPointSize(10)
self.ren.AddActor(self.ptsActor)
self.show()
self.iren.Initialize()
self.iren.Start()
def sliderCallback(self):
index = self.sender().value() # The index that the slider bar is currently on
points = self.polydata.GetPoints()
for i in range(len(self.coordData[index])):
points.SetPoint(i, self.coordData[index][i])
points.Modified() # Here you need to call Modified for points
self.show()
self.iren.Render()
return
if __name__ == "__main__":
app = Qt.QApplication(sys.argv)
window = ViewerWithScrollBar()
sys.exit(app.exec_())
Related
I want to seperate the widgets of the mainwindow into 3 layouts and add them into a mainlayout
the center layout contains a tablewidget and shoud have a bigger width then the left/right one. so far I did not found a way to use a grid layout and adjust each column width individualy.
therefore I tryed to use 3 vboxlayouts and insert them into an hboxlayout
what if read so far is that the widgets size are fixed so they expand at the same rate if stretched.
is there an easier way to make the center layout widht bigger with for example inserting an spacer without changing the SizePolicy of each widget ?
import sys
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg
class Stretchme(qtw.QWidget):
def __init__(self):
super().__init__()
# # View
table_widget = qtw.QTableWidget()
# -----interface--widgets--------------------------#
# -------- parameter_widgets------------#
parameter_label = qtw.QLabel("Parameter")
test_time_label = qtw.QLabel("Time")
self.clocktime = qtw.QTimeEdit()
test_date_label = qtw.QLabel("Date")
self.date_time = qtw.QDateEdit()
grob_fein_label = qtw.QLabel("Grobe")
grob_fein_combo_box = qtw.QComboBox()
vr_label = qtw.QLabel("Lame")
self.vr_feuchte_input = qtw.QLineEdit()
# leftvboxlayout
left_vboxlayout = qtw.QVBoxLayout()
left_vboxlayout.addStretch(1)
left_vboxlayout.addWidget(test_time_label)
left_vboxlayout.addWidget(self.clocktime)
left_vboxlayout.addWidget(test_date_label)
left_vboxlayout.addWidget(self.date_time)
left_vboxlayout.addWidget(grob_fein_label)
left_vboxlayout.addWidget(grob_fein_combo_box)
left_vboxlayout.addWidget(vr_label)
left_vboxlayout.addWidget(self.vr_feuchte_input)
# centervboxlayout
center_voboxlayout = qtw.QVBoxLayout()
#
# center_voboxlayout.addStretch()
# center_voboxlayout.setStretchFactor()
# center_voboxlayout.addSpacing()
# center_voboxlayout.setStretchFactor()
#
# table_widget.setMinimumSize(90,80)
center_voboxlayout.addWidget(table_widget)
# righvobxlayout
# buttons
self.button_1 = qtw.QPushButton("Button 1")
self.button_2 = qtw.QPushButton("Button 2")
self.button_2 = qtw.QPushButton("Button 3")
rightvboxlayout = qtw.QVBoxLayout()
rightvboxlayout.addStretch(1)
rightvboxlayout.addWidget(self.button_1)
rightvboxlayout.addWidget(self.button_2)
rightvboxlayout.addWidget(self.button_2)
#
#
# Separador = qtw.QFrame()
# # Separador.Shape(QFrame.HLine)
# Separador.setFrameShape(qtw.QFrame.HLine)
#
# Separador.setSizePolicy(qtw.QSizePolicy.Minimum,qtw.QSizePolicy.Expanding)
# Separador.setLineWidth(5)
# # HPOUT1_layout = QVBoxLayout()
# # HPOUT1_layout.addLayout(HPOUT1L_layout)
# # HPOUT1_layout.addWidget(Separador)
# # HPOUT1_layout.addLayout(HPOUT1R_layout)
## main layout
self.qhboxlayout = qtw.QHBoxLayout()
self.qhboxlayout.addLayout(left_vboxlayout)
# self.qhboxlayout.addWidget(Separador)
self.qhboxlayout.addLayout(center_voboxlayout)
self.qhboxlayout.addLayout(rightvboxlayout)
#
# ### Main Grid
# -------------------------------------------------- #
selected_color = qtg.QColor(0,0,255)
# self.setGeometry(300, 300, 1850, 950)
self.setStyleSheet("background-color: {}")
self.setLayout(self.qhboxlayout)
self.show()
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
w = Stretchme()
sys.exit(app.exec_())
Try this :
you will decide how much space allocate for each layout, by the following way :
self.qhboxlayout.addLayout(left_vboxlayout,20)
self.qhboxlayout.addLayout(center_voboxlayout,80)
self.qhboxlayout.addLayout(rightvboxlayout,20)
Your Code
import sys
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg
class Stretchme(qtw.QWidget):
def __init__(self):
super().__init__()
# # View
table_widget = qtw.QTableWidget()
# -----interface--widgets--------------------------#
# -------- parameter_widgets------------#
parameter_label = qtw.QLabel("Parameter")
test_time_label = qtw.QLabel("Time")
self.clocktime = qtw.QTimeEdit()
test_date_label = qtw.QLabel("Date")
self.date_time = qtw.QDateEdit()
grob_fein_label = qtw.QLabel("Grobe")
grob_fein_combo_box = qtw.QComboBox()
vr_label = qtw.QLabel("Lame")
self.vr_feuchte_input = qtw.QLineEdit()
# leftvboxlayout
left_vboxlayout = qtw.QVBoxLayout()
left_vboxlayout.addStretch(1)
left_vboxlayout.addWidget(test_time_label)
left_vboxlayout.addWidget(self.clocktime)
left_vboxlayout.addWidget(test_date_label)
left_vboxlayout.addWidget(self.date_time)
left_vboxlayout.addWidget(grob_fein_label)
left_vboxlayout.addWidget(grob_fein_combo_box)
left_vboxlayout.addWidget(vr_label)
left_vboxlayout.addWidget(self.vr_feuchte_input)
# centervboxlayout
center_voboxlayout = qtw.QVBoxLayout()
#
# center_voboxlayout.addStretch()
# center_voboxlayout.setStretchFactor()
# center_voboxlayout.addSpacing()
# center_voboxlayout.setStretchFactor()
#
# table_widget.setMinimumSize(90,80)
center_voboxlayout.addWidget(table_widget)
# righvobxlayout
# buttons
self.button_1 = qtw.QPushButton("Button 1")
self.button_2 = qtw.QPushButton("Button 2")
self.button_2 = qtw.QPushButton("Button 3")
rightvboxlayout = qtw.QVBoxLayout()
rightvboxlayout.addStretch(1)
rightvboxlayout.addWidget(self.button_1)
rightvboxlayout.addWidget(self.button_2)
rightvboxlayout.addWidget(self.button_2)
#
#
# Separador = qtw.QFrame()
# # Separador.Shape(QFrame.HLine)
# Separador.setFrameShape(qtw.QFrame.HLine)
#
# Separador.setSizePolicy(qtw.QSizePolicy.Minimum,qtw.QSizePolicy.Expanding)
# Separador.setLineWidth(5)
# # HPOUT1_layout = QVBoxLayout()
# # HPOUT1_layout.addLayout(HPOUT1L_layout)
# # HPOUT1_layout.addWidget(Separador)
# # HPOUT1_layout.addLayout(HPOUT1R_layout)
## main layout
self.qhboxlayout = qtw.QHBoxLayout()
self.qhboxlayout.addLayout(left_vboxlayout,20)
self.qhboxlayout.addLayout(center_voboxlayout,80)
self.qhboxlayout.addLayout(rightvboxlayout,20)
#
# ### Main Grid
# -------------------------------------------------- #
selected_color = qtg.QColor(0,0,255)
# self.setGeometry(300, 300, 1850, 950)
self.setStyleSheet("background-color: {}")
self.setLayout(self.qhboxlayout)
self.show()
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
w = Stretchme()
sys.exit(app.exec_())
You have to set a stretch factor when adding the central layout:
# ...
self.qhboxlayout.addLayout(center_voboxlayout, stretch=1)
# ...
My ui file contains a widget container with a vertical layout named "VL" and a line edit named "Radiance". I created a single bar graph that I want to change as I input values into the line edit. At the moment, it does just that, except it creates a new plot every time. If I use my "remove" function it doesn't make a whole separate plot, but it ruins the layout of the one. I think the problem lies with my "remove" function and where to put it, please help.
I imported QtWidgets, uic, matplot.figure, and necessary backends:
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
uic.loadUi('PyQt_App1.ui', self)
self.setWindowTitle("Window Title")
self.Radiance.textChanged.connect(self.animate)
def animate(self):
self.remove()
r = self.Radiance.text()
if r:
rad = float(r)
positions = [0.25]
fig1 = Figure()
ax1f1 = fig1.add_subplot(111)
ax1f1.set_ylim([0, 100])
ax1f1.set_xlim([0, 0.5])
ax1f1.bar(positions, rad, width=0.2, color="g")
self.addmpl(fig1)
else:
r = 0
rad = float(r)
positions = [0.25]
fig1 = Figure()
ax1f1 = fig1.add_subplot(111)
ax1f1.set_ylim([0, 100])
ax1f1.set_xlim([0, 0.5])
ax1f1.bar(positions, rad, width=0.2, color="g")
self.addmpl(fig1)
def addmpl(self, fig):
self.canvas = FigureCanvas(fig)
self.VL.addWidget(self.canvas)
# self.canvas.setParent(self.Frame)
self.canvas.draw()
def remove(self):
self.VL.removeWidget(self.canvas)
self.canvas.close()
if __name__ == '__main__':
import sys
from PyQt5 import QtWidgets
app = QtWidgets.QApplication(sys.argv)
main = MyWindow()
main.show()
sys.exit(app.exec_())
Instead of creating a new figure every time I would just keep references to the current bar plot and the current axes, and use those to update the figure, e.g.
from PyQt5 import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.pyplot import Figure
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
central = QtWidgets.QWidget(self)
self.VL = QtWidgets.QVBoxLayout(central)
self.Radiance = QtWidgets.QLineEdit(self)
self.VL.addWidget(self.Radiance)
self.canvas = FigureCanvas(Figure())
self.VL.addWidget(self.canvas)
self.ax1f1 = self.canvas.figure.subplots()
self.ax1f1.set_ylim([0, 100])
self.ax1f1.set_xlim([0, 0.5])
self.bar = None
self.setWindowTitle("Window Title")
self.setCentralWidget(central)
self.Radiance.textChanged.connect(self.animate)
def animate(self):
r = self.Radiance.text()
try:
rad = float(r)
except ValueError:
rad = 0
positions = [0.25]
if self.bar:
self.bar.remove()
self.bar = self.ax1f1.bar(positions, rad, width=0.2, color="g")
self.canvas.draw()
if __name__ == '__main__':
import sys
from PyQt5 import QtWidgets
app = QtWidgets.QApplication(sys.argv)
main = MyWindow()
main.show()
sys.exit(app.exec_())
Is it possible link the auto-scale of several plots?
I want to scale all the plots with which ever is the biggest range on all curves of all plots.
Is there way to make it with a pyqtgraph function, or should I find the max, min and set the scale with a custom function?
I am using pyqtgraph on PyQt5
Pyqtgraph should automatically scale the y-axis on multiple plots with which ever has the largest range. Here's an example widget with auto-scaling plots in PyQt4 but the concept should be the same for PyQt5.
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
import sys
import random
class AutoScaleMultiplePlotWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(AutoScaleMultiplePlotWidget, self).__init__(parent)
self.NUMBER_OF_PLOTS = 4
self.LEFT_X = 0
self.RIGHT_X = 5
self.SPACING = 1
self.x_axis = np.arange(self.LEFT_X, self.RIGHT_X + 1, self.SPACING)
self.buffer_size = int((abs(self.LEFT_X) + abs(self.RIGHT_X) + 1)/self.SPACING)
self.auto_scale_plot_widget = pg.PlotWidget()
self.auto_scale_plot_widget.setLabel('left', 'left axis')
# Create plots
self.left_plot1 = self.auto_scale_plot_widget.plot()
self.left_plot2 = self.auto_scale_plot_widget.plot()
self.left_plot3 = self.auto_scale_plot_widget.plot()
self.left_plot4 = self.auto_scale_plot_widget.plot()
self.left_plot1.setPen((173,255,129), width=1)
self.left_plot2.setPen((172,187,255), width=1)
self.left_plot3.setPen((255,190,116), width=1)
self.left_plot4.setPen((204,120,255), width=1)
self.initialize_plot_buffers()
self.initialize_data_buffers()
self.layout = QtGui.QGridLayout()
self.layout.addWidget(self.auto_scale_plot_widget)
self.start()
def initialize_data_buffers(self):
"""Create blank data buffers for each curve"""
self.data_buffers = []
for trace in range(self.NUMBER_OF_PLOTS):
self.data_buffers.append([0])
def initialize_plot_buffers(self):
"""Add plots into buffer for each curve"""
self.plots = []
self.plots.append(self.left_plot1)
self.plots.append(self.left_plot2)
self.plots.append(self.left_plot3)
self.plots.append(self.left_plot4)
def update_plot(self):
"""Generates new random value and plots curve onto plot"""
for trace in range(self.NUMBER_OF_PLOTS):
if len(self.data_buffers[trace]) >= self.buffer_size:
self.data_buffers[trace].pop(0)
data_point = self.data_buffers[trace][-1] + random.randint(10,50)
self.data_buffers[trace].append(float(data_point))
self.plots[trace].setData(self.x_axis[len(self.x_axis) - len(self.data_buffers[trace]):], self.data_buffers[trace])
def get_auto_scale_plot_layout(self):
return self.layout
def start(self):
self.multiple_axis_plot_timer = QtCore.QTimer()
self.multiple_axis_plot_timer.timeout.connect(self.update_plot)
self.multiple_axis_plot_timer.start(500)
if __name__ == '__main__':
# Create main application window
app = QtGui.QApplication([])
app.setStyle(QtGui.QStyleFactory.create("Cleanlooks"))
mw = QtGui.QMainWindow()
mw.setWindowTitle('Auto Scale Multiple Plot Example')
# Create plot
auto_scale_plot = AutoScaleMultiplePlotWidget()
# Create and set widget layout
# Main widget container
cw = QtGui.QWidget()
ml = QtGui.QGridLayout()
cw.setLayout(ml)
mw.setCentralWidget(cw)
# Add plot to main layout
ml.addLayout(auto_scale_plot.get_auto_scale_plot_layout(),0,0)
mw.show()
if(sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
I am trying to write a GUI in Python3 using PyQt4.
For data visualization, I need to isolate a specific point on a curve plotted by the function whole_plot(). To do so, I am currently using a slider that let the GUI user choose a point of interest. When the slider_value is changed, the point is selected and plotted by calling the function point_plot().
Regarding some previous answers, I am now trying to update my graph through matplotlib.animation (cf. post python matplotlib update scatter plot from a function). But for some reasons, I still got the wrong updating, can someone help me figure out what is the problem in my code?
import sys
import numpy as np
from PyQt4 import QtGui
from PyQt4 import QtCore
import matplotlib.pyplot as plt
import matplotlib.animation
#
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
#%%
# some Arbitrary data
nbr_points = 500
my_X_data = np.linspace(-10,10,nbr_points)
my_Y_data = my_X_data**3 + 100*np.cos(my_X_data*5)
class MyWidget(QtGui.QWidget):
def __init__(self):
super(MyWidget, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(600,300,1000,600)
grid = QtGui.QGridLayout()
self.setLayout(grid)
self.figure_1 = plt.figure(figsize=(15,5))
self.canvas_1 = FigureCanvas(self.figure_1)
self.toolbar = NavigationToolbar(self.canvas_1, self)
grid.addWidget(self.canvas_1, 2,0,1,2)
grid.addWidget(self.toolbar, 0,0,1,2)
# Slider
self.slider = QtGui.QSlider(QtCore.Qt.Horizontal)
self.slider.setMinimum(0)
self.slider.setMaximum(nbr_points-1)
self.slider.setTickInterval(1)
self.slider.valueChanged.connect(self.point_plot)
# Slider info box
self.label = QtGui.QLabel(self)
grid.addWidget(self.label,4,0)
# +1 / -1 buttons
btn_plus_one = QtGui.QPushButton('+1', self)
btn_plus_one.clicked.connect(self.value_plus_one)
btn_minus_one = QtGui.QPushButton('-1', self)
btn_minus_one.clicked.connect(self.value_minus_one)
hbox = QtGui.QHBoxLayout()
hbox.addWidget(btn_minus_one)
hbox.addWidget(self.slider)
hbox.addWidget(btn_plus_one)
grid.addLayout(hbox, 3,0,1,3)
self.whole_plot()
self.point_plot()
self.show()
def whole_plot(self):
ax1 = self.figure_1.add_subplot(111)
ax1.clear()
ax1.cla()
#
ax1.plot(my_X_data,my_Y_data,'b-')
#
ax1.set_xlim([-10,10])
ax1.set_ylim([-1000,1000])
ax1.set_xlabel('X')
ax1.set_ylabel('Y')
#
self.canvas_1.draw()
def point_plot(self):
ax1 = self.figure_1.add_subplot(111)
#
X_point, Y_point = [],[]
scat = ax1.scatter(X_point,Y_point, s=100,c='k')
def animate(i):
index_slider_value = self.slider.value()
X_point = my_X_data[index_slider_value,]
Y_point = my_Y_data[index_slider_value,]
scat.set_offsets(np.c_[X_point,Y_point])
anim = matplotlib.animation.FuncAnimation(self.figure_1,animate, frames=my_X_data, interval=200, repeat=True)
self.canvas_1.draw()
def value_plus_one(self):
# slider +1
if self.slider.value() < (my_X_data.shape[0]-1):
index_slider_value = self.slider.value() + 1
self.slider.setValue(index_slider_value)
def value_minus_one(self):
# slider -1
if self.slider.value() > 0:
index_slider_value = self.slider.value() - 1
self.slider.setValue(index_slider_value)
def main():
app = QtGui.QApplication(sys.argv)
MyWidget()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You have to learn what is better to reuse than create, in this case you just need to create a scatter and update the data with set_offsets() when the value of the slider changes, so FuncAnimation is not necessary.
import numpy as np
from PyQt4 import QtCore, QtGui
import matplotlib.pyplot as plt
import matplotlib.animation
#
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
#%%
# some Arbitrary data
nbr_points = 500
my_X_data = np.linspace(-10,10,nbr_points)
my_Y_data = my_X_data**3 + 100*np.cos(my_X_data*5)
class MyWidget(QtGui.QWidget):
def __init__(self):
super(MyWidget, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(600,300,1000,600)
self.figure_1 = plt.figure(figsize=(15,5))
self.canvas = FigureCanvas(self.figure_1)
self.toolbar = NavigationToolbar(self.canvas, self)
# Slider
self.slider = QtGui.QSlider(minimum=0,
maximum= nbr_points-1,
orientation=QtCore.Qt.Horizontal,
tickInterval=1)
self.slider.valueChanged.connect(self.on_valueChanged)
# Slider info box
self.label = QtGui.QLabel()
# +1 / -1 buttons
btn_plus_one = QtGui.QPushButton('+1')
btn_plus_one.clicked.connect(self.value_plus_one)
btn_minus_one = QtGui.QPushButton('-1')
btn_minus_one.clicked.connect(self.value_minus_one)
grid = QtGui.QGridLayout(self)
grid.addWidget(self.canvas, 2, 0, 1, 2)
grid.addWidget(self.toolbar, 0, 0, 1, 2)
grid.addWidget(self.label, 4, 0)
hbox = QtGui.QHBoxLayout()
hbox.addWidget(btn_minus_one)
hbox.addWidget(self.slider)
hbox.addWidget(btn_plus_one)
grid.addLayout(hbox, 3, 0, 1, 3)
self.whole_plot()
def whole_plot(self):
ax = self.figure_1.add_subplot(111)
ax.clear()
ax.plot(my_X_data,my_Y_data,'b-')
ax.set_xlim([-10,10])
ax.set_ylim([-1000,1000])
ax.set_xlabel('X')
ax.set_ylabel('Y')
self.canvas.draw()
X_point, Y_point = [],[]
self.scat = ax.scatter(X_point, Y_point, s=100,c='k')
# set initial
self.on_valueChanged(self.slider.value())
#QtCore.pyqtSlot(int)
def on_valueChanged(self, value):
X_point = my_X_data[value,]
Y_point = my_Y_data[value,]
self.scat.set_offsets(np.c_[X_point,Y_point])
self.canvas.draw()
#QtCore.pyqtSlot()
def value_plus_one(self):
self.slider.setValue(self.slider.value() + 1)
#QtCore.pyqtSlot()
def value_minus_one(self):
self.slider.setValue(self.slider.value() - 1)
def main():
import sys
app = QtGui.QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
On the other hand QSlider will not update the value if it is less than minimum or greater than maximum
I have some code very similar to the example given by PyQtGraph below. I also want to add to the label the color of the exact pixel being moused over (the pixel at the crosshair). How would I do this?
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph.Point import Point
#generate layout
app = QtGui.QApplication([])
win = pg.GraphicsWindow()
win.setWindowTitle('pyqtgraph example: crosshair')
label = pg.LabelItem(justify='right')
win.addItem(label)
p1 = win.addPlot(row=1, col=0)
p2 = win.addPlot(row=2, col=0)
region = pg.LinearRegionItem()
region.setZValue(10)
# Add the LinearRegionItem to the ViewBox, but tell the ViewBox to exclude this
# item when doing auto-range calculations.
p2.addItem(region, ignoreBounds=True)
#pg.dbg()
p1.setAutoVisible(y=True)
#create numpy arrays
#make the numbers large to show that the xrange shows data from 10000 to all the way 0
data1 = 10000 + 15000 * pg.gaussianFilter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000)
data2 = 15000 + 15000 * pg.gaussianFilter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000)
p1.plot(data1, pen="r")
p1.plot(data2, pen="g")
p2.plot(data1, pen="w")
def update():
region.setZValue(10)
minX, maxX = region.getRegion()
p1.setXRange(minX, maxX, padding=0)
region.sigRegionChanged.connect(update)
def updateRegion(window, viewRange):
rgn = viewRange[0]
region.setRegion(rgn)
p1.sigRangeChanged.connect(updateRegion)
region.setRegion([1000, 2000])
#cross hair
vLine = pg.InfiniteLine(angle=90, movable=False)
hLine = pg.InfiniteLine(angle=0, movable=False)
p1.addItem(vLine, ignoreBounds=True)
p1.addItem(hLine, ignoreBounds=True)
vb = p1.vb
def mouseMoved(evt):
pos = evt[0] ## using signal proxy turns original arguments into a tuple
if p1.sceneBoundingRect().contains(pos):
mousePoint = vb.mapSceneToView(pos)
index = int(mousePoint.x())
#######
GET THE PIXEL COLOR AND ADD TO LABEL??? HOW?
#######
if index > 0 and index < len(data1):
label.setText("<span style='font-size: 12pt'>x=%0.1f, <span style='color: red'>y1=%0.1f</span>, <span style='color: green'>y2=%0.1f</span>" % (mousePoint.x(), data1[index], data2[index]))
vLine.setPos(mousePoint.x())
hLine.setPos(mousePoint.y())
proxy = pg.SignalProxy(p1.scene().sigMouseMoved, rateLimit=60, slot=mouseMoved)
#p1.scene().sigMouseMoved.connect(mouseMoved)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
You must override the mouseMoveEvent method, record the widget and get the pixel.
For this we will create a class that inherits from pg.GraphicsWindow:
class MyGraphicsWindow(pg.GraphicsWindow):
def __init__(self, parent=None):
pg.GraphicsWindow.__init__(self, parent=parent)
self.setMouseTracking(True)
def mouseMoveEvent(self, e):
image = QtGui.QPixmap.grabWidget(self).toImage()
color = QtGui.QColor(image.pixel(e.pos()))
print(color.name())
print("red:{}, green:{}, blue:{}".format(color.red(), color.green(), color.blue()))
pg.GraphicsWindow.mouseMoveEvent(self, e)
Then we will replace win = pg.GraphicsWindow() with win = MyGraphicsWindow()
Complete code:
class MyGraphicsWindow(pg.GraphicsWindow):
def __init__(self, parent=None):
pg.GraphicsWindow.__init__(self, parent=parent)
self.setMouseTracking(True)
def mouseMoveEvent(self, e):
image = QtGui.QPixmap.grabWidget(self).toImage()
color = QtGui.QColor(image.pixel(e.pos()))
print(color.name())
print("{}, {}, {}".format(color.red(), color.green(), color.blue()))
pg.GraphicsWindow.mouseMoveEvent(self, e)
#generate layout
app = QtGui.QApplication(sys.argv)
win = MyGraphicsWindow()
win.setWindowTitle('pyqtgraph example: crosshair')
label = pg.LabelItem(justify='right')
win.addItem(label)
p1 = win.addPlot(row=1, col=0)
p2 = win.addPlot(row=2, col=0)
region = pg.LinearRegionItem()
region.setZValue(10)
# Add the LinearRegionItem to the ViewBox, but tell the ViewBox to exclude this
# item when doing auto-range calculations.
p2.addItem(region, ignoreBounds=True)
#pg.dbg()
p1.setAutoVisible(y=True)
#create numpy arrays
#make the numbers large to show that the xrange shows data from 10000 to all the way 0
data1 = 10000 + 15000 * pg.gaussianFilter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000)
data2 = 15000 + 15000 * pg.gaussianFilter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000)
p1.plot(data1, pen="r")
p1.plot(data2, pen="g")
p2.plot(data1, pen="w")
def update():
region.setZValue(10)
minX, maxX = region.getRegion()
sys.exit(app.exec_())