Is it possible to recreate a pyqtgraph without data - pyqt5

I have a pyqt5 based application where I open a file and plot 2 different plots based on the data from the file. Now I want to open another similar file, but I want to save the status/view of the 2 plots, so that I could come quickly back to the previous plots/views, without having to plot it again by reading the data. Is it possible at all to save the state/view of the plots to recreate it very quickly?

You can guide from this answer .
Basically, you can use the PlotWidget class from pyqtgraph.
Generate a PlotWidget.
import pyqtgraph as pg
plot_widget = pg.PlotWidget()
Use the plot() method and store the PlotDataItem in a variable. The PlotDataItem will contain the information of that specific plot: x-data, y-data, the color of the line, the line width, ...
plot_item = plot_widget.plot(xData, yData)
With this you can add/remove the item from the plot every time you want with the addItem() and removeItem() methods
plot_widget.removeItem(plot_item)
plot_widget.addItem(plot_item)
EDIT:
To get the view state of the plot, you can use the viewRect() method of the PlotWidget class. It will return a QRectF object which contains the information of the view state like this:
PyQt5.QtCore.QRectF(x_0, y_0, w, h)
Where:
x_0 and y_0 are the coordinates where the view starts.
w and h are the width and height of the view area.
Also, you can restore the view using the setRange() method of the PlotWidget class.
Example:
Here is an example of the implementation of this:
import sys
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui
class MyApp(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.central_layout = QtGui.QVBoxLayout()
self.buttons_layout = QtGui.QVBoxLayout()
self.boxes_layout = QtGui.QHBoxLayout()
self.save = QtGui.QPushButton('Save View')
self.set = QtGui.QPushButton('Set View')
self.boxes = [QtGui.QCheckBox(f"Box {i+1}") for i in range(3)]
self.plot_widget = pg.PlotWidget()
self.plot_data = [None for _ in range(3)]
self.state = [False for _ in range(3)]
self.setLayout(self.central_layout)
self.central_layout.addWidget(self.plot_widget)
self.central_layout.addLayout(self.buttons_layout)
self.buttons_layout.addLayout(self.boxes_layout)
self.buttons_layout.addWidget(self.save)
self.buttons_layout.addWidget(self.set)
for i in range(3):
self.boxes_layout.addWidget(self.boxes[i])
self.boxes[i].stateChanged.connect(self.box_changed)
self.create_data()
self.save.clicked.connect(self.save_view)
self.set.clicked.connect(self.set_view)
self.view_state = None
self.save_view()
def create_data(self):
x = np.linspace(0, 3.14, 100)
y = [np.sin(x), np.cos(x), np.sin(x)**2]
for i in range(3):
self.plot_data[i] = pg.PlotDataItem(x, y[i])
def box_changed(self):
for i in range(3):
if self.boxes[i].isChecked() != self.state[i]:
self.state[i] = self.boxes[i].isChecked()
if self.state[i]:
if self.plot_data[i] is not None:
self.plot_widget.addItem(self.plot_data[i])
else:
self.plot_data[i] = self.plot_widget.plot(*self.box_data[i])
else:
self.plot_widget.removeItem(self.plot_data[i])
break
def save_view(self):
self.view_state = self.plot_widget.viewRect()
def set_view(self):
self.plot_widget.setRange(self.view_state)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MyApp()
window.show()
sys.exit(app.exec_())

Related

How to create a user input to update the y-axes of the Matplotlib graph on Canvas in PysimpleGUI

I want to create a field in PysimpleGUI where the user can choose the y-axis value of the Matplotlib graph and that the program update the graph. I started learning PysimpleGUI and I am not very experienced with this. I didn't find the answer via google.
I didn't try too much as I am not very experienced and didn't find a solution via google. I expect to get an idea how to create such an user input which updates the y-axis of the graph in PysimpleGUI
Example code to embed matplotlib into PySimpleGUI.
import math, random
from pathlib import Path
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import PySimpleGUI as sg
# 1. Define the class as the interface between matplotlib and PySimpleGUI
class Canvas(FigureCanvasTkAgg):
"""
Create a canvas for matplotlib pyplot under tkinter/PySimpleGUI canvas
"""
def __init__(self, figure=None, master=None):
super().__init__(figure=figure, master=master)
self.canvas = self.get_tk_widget()
self.canvas.pack(side='top', fill='both', expand=1)
# 2. create PySimpleGUI window, a fixed-size Frame with Canvas which expand in both x and y.
font = ("Courier New", 11)
sg.theme("DarkBlue3")
sg.set_options(font=font)
layout = [
[sg.Input(expand_x=True, key='Path'),
sg.FileBrowse(file_types=(("ALL CSV Files", "*.csv"), ("ALL Files", "*.*"))),
sg.Button('Plot')],
[sg.Frame("", [[sg.Canvas(background_color='green', expand_x=True, expand_y=True, key='Canvas')]], size=(640, 480))],
[sg.Push(), sg.Button('Exit')]
]
window = sg.Window('Matplotlib', layout, finalize=True)
# 3. Create a matplotlib canvas under sg.Canvas or sg.Graph
fig = Figure(figsize=(5, 4), dpi=100)
ax = fig.add_subplot()
canvas = Canvas(fig, window['Canvas'].Widget)
# 4. initial for figure
ax.set_title(f"Sensor Data")
ax.set_xlabel("X axis")
ax.set_ylabel("Y axis")
ax.set_xlim(0, 1079)
ax.set_ylim(-1.1, 1.1)
ax.grid()
canvas.draw() # do Update to GUI canvas
# 5. PySimpleGUI event loop
while True:
event, values = window.read()
if event in (sg.WINDOW_CLOSED, 'Exit'):
break
elif event == 'Plot':
"""
path = values['Path']
if not Path(path).is_file():
continue
"""
# 6. Get data from path and plot from here
ax.cla() # Clear axes first if required
ax.set_title(f"Sensor Data")
ax.set_xlabel("X axis")
ax.set_ylabel("Y axis")
ax.grid()
theta = random.randint(0, 359)
x = [degree for degree in range(1080)]
y = [math.sin((degree+theta)/180*math.pi) for degree in range(1080)]
ax.plot(x, y)
canvas.draw() # do Update to GUI canvas
# 7. Close window to exit
window.close()
You can add Input elements with different keys, get the value of element by values[key] when plot to update your y-axis.
Update - Example Code
Add Input elements for y-axis data
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import PySimpleGUI as sg
class Canvas(FigureCanvasTkAgg):
"""
Create a canvas for matplotlib pyplot under tkinter/PySimpleGUI canvas
"""
def __init__(self, figure=None, master=None):
super().__init__(figure=figure, master=master)
self.canvas = self.get_tk_widget()
self.canvas.pack(side='top', fill='both', expand=1)
days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
sg.set_options(font=("Courier New", 16))
layout = [
[sg.Text("Income last week")],
[sg.Canvas(size=(640, 480), background_color='green', expand_x=True, expand_y=True, key='Canvas')],
[sg.Text(day, size=10, justification='center') for day in days],
[sg.Input(size=10, justification='right', key=('Day', i)) for i in range(len(days))],
[sg.Push(), sg.Button('Plot')]
]
window = sg.Window('Title', layout, finalize=True)
fig = Figure(figsize=(5, 4), dpi=100)
ax = fig.add_subplot()
canvas = Canvas(fig, window['Canvas'].Widget)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
elif event == 'Plot':
# x = [i for i in range(len(days))]
y = []
for i in range(len(days)):
try:
value = float(values[('Day', i)])
except:
value = 0
y.append(value)
ax.cla()
ax.grid()
ax.plot(days, y)
canvas.draw()
window.close()

PyQt - not showing instance of FigureCanvasQTAgg on QtWidget of TabPane

I'm continuing project described more in that question: PyQt - can't read Excel file
Basically my code looks like this right now:
# This is a sample Python script.
# Press Shift+F10 to execute it or replace it with your code.
# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings.
import csv
import sys
import numpy as np
from PyQt6 import QtWidgets
from PyQt6.QtWidgets import QDialog, QApplication, QFileDialog, QTableWidget, QTableWidgetItem, QTabWidget, QWidget
from PySide6.QtCore import Slot, SIGNAL
from PyQt6.uic import loadUi
import pandas as pd
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.figure import Figure
class MplCanvas(FigureCanvasQTAgg):
def __init__(self, parent=None, width=12, height=5, dpi=100):
fig = Figure(figsize=(width, height), dpi=100)
self.axes = fig.add_subplot(111)
super(MplCanvas, self).__init__(fig)
class MainWindow(QDialog):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent=parent)
self.initUI()
def initUI(self):
loadUi('gui.ui', self)
self.btnShow.setEnabled(False)
self.btnLoad.setEnabled(False)
self.btnBrowse.clicked.connect(self.browseFiles)
self.btnLoad.clicked.connect(self.loadExcelData)
self.btnClean.clicked.connect(self.cleanData)
self.btnShow.clicked.connect(self.showGraphs)
#Slot()
def browseFiles(self):
fname = QFileDialog.getOpenFileName(self, 'Open a file', 'C:\\', "Excel (*.xls *.xlsx)")
self.filename.setText(fname[0])
self.btnLoad.setEnabled(True)
#Slot()
def loadExcelData(self):
column_names = ["Action", "TimeOfFailure", "ReverseRankR", "S(i)", "Cdf", "Ppf", "LogTime"]
df = pd.read_excel(self.filename.text(), "Sheet1", names=column_names)
if df.size == 0:
return
self.tableExcelData.setRowCount(df.shape[0])
self.tableExcelData.setColumnCount(df.shape[1])
self.tableExcelData.setHorizontalHeaderLabels(df.columns)
for row in df.iterrows():
values = row[1]
for col_index, value in enumerate(values):
tableItem = QTableWidgetItem(str(value))
self.tableExcelData.setItem(row[0], col_index, tableItem)
self.btnLoad.setEnabled(False)
self.btnShow.setEnabled(True)
#Slot()
def cleanData(self):
self.btnLoad.setEnabled(True)
self.btnShow.setEnabled(False)
self.tableExcelData.setRowCount(0)
self.tableExcelData.setColumnCount(0)
#Slot()
def showGraphs(self):
timeOfDays = []
cdf = []
ppf = []
logTime = []
for row in range(self.tableExcelData.rowCount()):
isFailure = False
for column in range(self.tableExcelData.columnCount()):
value = self.tableExcelData.item(row, column)
if(column == 0 and str(value.text()) == 'F'):
isFailure = True
if isFailure == True:
if(column == 1): #TimeOfDays
value = int(value.text())
timeOfDays.append(value)
elif(column == 4): #CDF
value = float(value.text())
cdf.append(value)
elif(column == 5):
value = float(value.text())
ppf.append(value)
elif(column == 6):
value = float(value.text())
logTime.append(value)
print(timeOfDays)
print(cdf)
print(ppf)
print(logTime)
#fig = Figure(figsize=(12,5), dpi=100)
#firstSubplot = fig.add_subplot(111)
#firstSubplot.scatter(timeOfDays, ppf, '*')
#firstSubplot.plot(timeOfDays, ppf)
#fig.show()
#plt.plot(timeOfDays, ppf)
#plt.show()
try:
canvasFig = MplCanvas()
canvasFig.axes.scatter(timeOfDays, ppf, s=5, color='red')
canvasFig.axes.plot(timeOfDays, ppf)
canvasFig.draw()
self.tabFirstGraph.setCentralWidget(canvasFig)
except Exception as e:
print('Error: ' + str(e))
#canvas = FigureCanvasTkAgg(fig, master=self)
#canvas.get_tk_widget().pack()
#canvas.draw()
# Press the green button in the gutter to run the script.
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWindow = MainWindow()
mainWidget = QtWidgets.QStackedWidget()
mainWidget.addWidget(mainWindow)
mainWidget.show()
sys.exit(app.exec())
# See PyCharm help at https://www.jetbrains.com/help/pycharm/
I'm trying to generate two graphs (now it's code for only creation of one):
try:
canvasFig = MplCanvas()
canvasFig.axes.scatter(timeOfDays, ppf, s=5, color='red')
canvasFig.axes.plot(timeOfDays, ppf)
canvasFig.draw()
self.tabFirstGraph.setCentralWidget(canvasFig) #
except Exception as e:
print('Error: ' + str(e))
I tried to create another TabPane ("tabFirstGraph" as name of this object) and set canvas figure object to fill this QWidget instance. But I'm getting constantly this error:
Error: 'QWidget' object has no attribute 'setCentralWidget'
I assumed already that problem is with line above (QWidget, QTableWidget don't have this method). But how can I show my canvas figure graph on "First Graph" Tab Pane?
Thanks in advance for your all answers. :)

Python MATPLOTLIB ANIMATION without the use of Global Variables?

QUESTION: Whats the cleanest and simplest way to use Python's MATPLOTLIB animation function without the use of global array's or constantly appending a global "list of data points" to a plot?
Here is an example of a animated graph that plots the bid and ask sizes of a stock ticker. In this example the variables time[], ask[], and bid[] are used as global variables.
How do we modify the matplotlib animate() function to not use global variables?
so I'm trying to remove "all" global variables and just run one function call...
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
from random import randint
stock = {'ask': 12.82, 'askSize': 21900, 'bid': 12.81, 'bidSize': 17800}
def get_askSize():
return stock["askSize"] + randint(1,9000) # grab a random integer to be the next y-value in the animation
def get_bidSize():
return stock["bidSize"] + randint(1,9000) # grab a random integer to be the next y-value in the animation
def animate(i):
pt_ask = get_askSize()
pt_bid = get_bidSize()
time.append(i) #x
ask.append(pt_ask) #y
bid.append(pt_bid) #y
ax.clear()
ax.plot(time, ask)
ax.plot(time, bid)
ax.set_xlabel('Time')
ax.set_ylabel('Volume')
ax.set_title('ask and bid size')
ax.set_xlim([0,40])
#axis = axis_size(get_bidSize, get_askSize)
ylim_min = (get_askSize() + get_bidSize())/6
ylim_max = (get_askSize() + get_bidSize())
ax.set_ylim([ylim_min,ylim_max])
# create empty lists for the x and y data
time = []
ask = []
bid = []
# create the figure and axes objects
fig, ax = plt.subplots()
# run the animation
ani = FuncAnimation(fig, animate, frames=40, interval=500, repeat=False)
plt.show()
As #Warren mentioned, you can use the fargs parameter to pass in shared variables to be used in your animation function.
You should also precompute all of your points, and then use your frames to merely act as an expanding window on those frames. This will be a much more performant solution and prevents you from needing to convert between numpy arrays and lists on every tick of your animation in order to update the underlying data for your lines.
This also enables you to precompute your y-limits to prevent your resultant plot from jumping all over the place.
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
rng = np.random.default_rng(0)
def animate(i, ask_line, bid_line, data):
i += 1
x = data['x'][:i]
ask_line.set_data(x, data['ask'][:i])
bid_line.set_data(x, data['bid'][:i])
stock = {'ask': 12.82, 'askSize': 21900, 'bid': 12.81, 'bidSize': 17800}
frames = 40
data = {
'x': np.arange(0, frames),
'ask': stock['askSize'] + rng.integers(0, 9000, size=frames),
'bid': stock['bidSize'] + rng.integers(0, 9000, size=frames),
}
fig, ax = plt.subplots()
ask_line, = ax.plot([], [])
bid_line, = ax.plot([], [])
ax.set(xlabel='Time', ylabel='Volume', title='ask and bid size', xlim=(0, 40))
ax.set_ylim(
min(data['ask'].min(), data['bid'].min()),
max(data['ask'].max(), data['bid'].max()),
)
# run the animation
ani = FuncAnimation(
fig, animate, fargs=(ask_line, bid_line, data),
frames=40, interval=500, repeat=False
)
plt.show()
You can use the fargs parameter of FuncAnimation to provide additional arguments to your animate callback function. So animate might start like
def animate(i, askSize, bidSize):
...
and in the call of FuncAnimation, you would add the parameter fargs=(askSize, bidSize). Add whatever variables (in whatever form) that you need to make available within the animate function.
I use this in my example of the use of FuncAnimation with AnimatedPNGWriter in the package numpngw; see Example 8. In that example, my callback function is
def update_line(num, x, data, line):
"""
Animation "call back" function for each frame.
"""
line.set_data(x, data[num, :])
return line,
and FuncAnimation is created with
ani = animation.FuncAnimation(fig, update_line, frames=len(t),
init_func=lambda : None,
fargs=(x, sol, lineplot))
You are using animation wrong, as you are adding and removing lines at each iteration, which makes the animation a lot slower. For line plots, the best way to proceed is:
initialize the figure and axes
initialize empty lines
inside the animate function, update the data of each line.
Something like this:
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
from random import randint
stock = {'ask': 12.82, 'askSize': 21900, 'bid': 12.81, 'bidSize': 17800}
def get_askSize():
return stock["askSize"] + randint(1,9000) # grab a random integer to be the next y-value in the animation
def get_bidSize():
return stock["bidSize"] + randint(1,9000) # grab a random integer to be the next y-value in the animation
def add_point_to_line(x, y, line):
# retrieve the previous data in the line
xd, yd = [list(t) for t in line.get_data()]
# append the new point
xd.append(x)
yd.append(y)
# set the new data
line.set_data(xd, yd)
def animate(i):
pt_ask = get_askSize()
pt_bid = get_bidSize()
# append a new value to the lines
add_point_to_line(i, pt_ask, ax.lines[0])
add_point_to_line(i, pt_bid, ax.lines[1])
# update axis limits if necessary
ylim_min = (get_askSize() + get_bidSize())/6
ylim_max = (get_askSize() + get_bidSize())
ax.set_ylim([ylim_min,ylim_max])
# create the figure and axes objects
fig, ax = plt.subplots()
# create empty lines that will be populated on the animate function
ax.plot([], [])
ax.plot([], [])
ax.set_xlabel('Time')
ax.set_ylabel('Volume')
ax.set_title('ask and bid size')
ax.set_xlim([0,40])
# run the animation
ani = FuncAnimation(fig, animate, frames=40, interval=500, repeat=False)
plt.show()

How to embed an interactive matplotlib widget in a Class using %matplotlib notebook

I've written an interactive GUI with the Matplotlib module widgets that runs directly inside my Jupyter Notebook using %matplotlib notebook (i.e. it does not open a separate window for the GUI as with qt or tk, but runs embedded within the notebook, similar to static plots with %matplotlib inline). The purpose of the GUI is to accept a list of images (2D numpy arrays, displayed using plt.imshow()), display them, and use a button to flip back and forth between images in the list.
I have written a block of code that works just fine based on the canonical example, but it fails when I try to wrap the whole thing within a Class.
My end goal is to execute the GUI with a command like:
GUI_object = interactive_plot_class(img_list), where img_list is the list of images.
Here I've given an example of the codeblock below, which works fine:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
from functools import partial
###############################
# CLASSES
# define a class to keep count of the index being displayed
class Counter:
def __init__(self, initial=0):
self.value = initial
def increment_back(self, amount=1):
self.value -= amount
return self.value
def increment_fwd(self, amount=1):
self.value += amount
return self.value
###############################
# GUI FUNCTIONS
# define function to update plot
def update_plot(counter,img):
ax1.imshow(img, cmap=plot_dict['cmap'], vmin=plot_dict['vmin'], vmax=plot_dict['vmax'], origin='lower', interpolation='nearest')
ax1.set_title('Index: {}'.format(counter.value))
fig.canvas.draw()
# define function for iterating backward through image list
def on_click_prev(button,counter=0):
if counter.value == 0: #minor error handling
pass
else:
counter.increment_back()
img_temp = img_list[counter.value]
update_plot(counter,img_temp)
# define function for iterating forward through image list
def on_click_next(button,counter=0):
if counter.value == (n_imgs-1): #minor error handling
pass
else:
counter.increment_fwd()
img_temp = img_list[counter.value]
update_plot(counter,img_temp)
###############################
# create random images stored in numpy arrays
img_size = 55
n_imgs = 20
img_list = [np.random.random((img_size,img_size)) for x in range(n_imgs)]
# define a dict to store parameters for plotting
plot_dict = {'cmap': 'gray_r', 'vmin': 0., 'vmax':1}
# define counter to track index
counter = Counter()
# Plotting
fig = plt.figure(figsize=(10,8))
ax1 = fig.subplots(1,1)
plt.subplots_adjust(left = 0.4, bottom = 0.3)
#-------BUTTONS--------
# create axes: [xposition, yposition, width, height]
ax_button_next = plt.axes([0.12, 0.05, 0.1, 0.10])
ax_button_prev = plt.axes([0.02, 0.05, 0.1, 0.10])
# properties of the button
next_button = Button(ax_button_next, 'Next', color='white', hovercolor='gainsboro')
prev_button = Button(ax_button_prev, 'Prev', color='white', hovercolor='gainsboro')
# triggering event is the clicking
next_button.on_clicked(partial(on_click_next,counter=counter))
prev_button.on_clicked(partial(on_click_prev,counter=counter))
# define an initial image to display
img_temp = img_list[counter.value].copy()
# display initial image
ax1.imshow(img_temp, cmap=plot_dict['cmap'], vmin=plot_dict['vmin'], vmax=plot_dict['vmax'], origin='lower', interpolation='nearest')
ax1.set_title('Index: {}'.format(counter.value))
plt.show(fig)
This little snippet of code works and does exactly what I want it to do. My problem is that when I try to re-write it inside of a class, nothing happens (i.e. after the initial image is displayed, the buttons are non-responsive and do not call the callback function. In fact, they don't do anything).
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
from functools import partial
class interactive_plot_class():
### SUB-CLASSES
# define a class to keep count of the index being displayed
class Counter:
def __init__(self, initial=0):
self.value = initial
def increment_back(self, amount=1):
self.value -= amount
return self.value
def increment_fwd(self, amount=1):
self.value += amount
return self.value
### FUNCTIONS
# define initial state of class
def __init__(self, filter_dict):
self.img_list = filter_dict['images']
self.Index = self.Counter()
self.cmap = 'gray'
self.vmin = 0.
self.vmax = 1
#-------FIGURE--------
# create figure to display image
self.fig = plt.figure(figsize=(10,8))
# create axis for image display
self.ax1 = plt.axes([0.4, 0.3, 0.55, 0.55])
self.disp_img = self.img_list[self.Index.value]
self.ax1.imshow(self.disp_img,cmap=self.cmap, vmin=self.vmin, vmax=self.vmax, origin='lower', interpolation='nearest')
self.ax1.set_title('Index: {}'.format(self.Index.value))
#-------BUTTONS--------
# create axes for buttons: [xposition, yposition, width, height]
self.ax_button_next = plt.axes([0.12, 0.05, 0.1, 0.10])
self.ax_button_prev = plt.axes([0.02, 0.05, 0.1, 0.10])
# properties of the button
self.next_button = Button(self.ax_button_next, 'Next', color='white', hovercolor='gainsboro')
self.prev_button = Button(self.ax_button_prev, 'Prev', color='white', hovercolor='gainsboro')
# assign callback function, triggering event is the clicking
self.prev_button.on_clicked(self.on_click_prev)
self.next_button.on_clicked(self.on_click_next)
# display the figure interactively
self.fig.canvas.draw()
# GUI FUNCTIONS
# define function to update plot
def update_plot(img):
self.ax1.imshow(img, cmap=self.cmap, vmin=selfvmin, vmax=self.vmax, origin='lower', interpolation='nearest')
self.ax1.set_title('Index: {}'.format(self.Index.value))
self.fig.canvas.draw()
# define function for iterating forward through image list
def on_click_next(button_event):
if self.Index.value == (len(self.img_list)-1): #minor error handling
pass
else:
self.Index.increment_fwd()
self.disp_img = self.img_list[self.Index.value]
self.update_plot(self.disp_temp)
# define function for iterating backward through image list
def on_click_prev(button_event):
if self.Index.value == 0: #minor error handling
pass
else:
self.Index.increment_back()
self.disp_img = self.img_list[self.Index.value]
self.update_plot(self.disp_img)
###############
# END OF CLASS
###############
img_size = 55
n_imgs = 20
imgs = [np.random.random((img_size,img_size)) for x in range(n_imgs)]
# store relevant fields to intialize GUI
filter_dict['images'] = imgs
GUI_object = interactive_plot_class(filter_dict)
When I run this second block of code, the buttons don't do anything, nor do they increment the value of the Index counter.
Any help would be most appreciated.

Releasing pan function in NavigationToolbar2QT

I want to cancel pan function from other button. So far, my understanding is that when I want to pan&zoom image, I will click 'Pan' button. If I would like to do other function, e.g. 'Mark' function (in my case), I have to click 'Pan' Button again, then click whatever button I want to do.
I have searched for solving this and found something like 'release_pan', 'button_release_event', but I don't understand how to implement them correctly.
To be clear, I want to cancel pan function from 'Mark' button, and here is my code.
import sys
import time
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Cursor
from matplotlib.backends.qt_compat import QtCore, QtWidgets
if QtCore.qVersion() >= "5.":
from matplotlib.backends.backend_qt5agg import (
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
else:
from matplotlib.backends.backend_qt4agg import (
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.coor = [0,0] #temporary user selection
self.cid = None
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
layout = QtWidgets.QVBoxLayout(self._main)
butt = QtWidgets.QHBoxLayout()
self.static_canvas = FigureCanvas(Figure(figsize=(5, 5), dpi=100))
self.addToolBar = NavigationToolbar(self.static_canvas, self)
self.addToolBar.hide()
self.home = QtWidgets.QPushButton('Home')
self.pan = QtWidgets.QPushButton('Pan')
self.mark = QtWidgets.QPushButton('Mark')
butt.addWidget(self.home)
butt.addWidget(self.pan)
butt.addWidget(self.mark)
layout.addLayout(butt)
layout.addWidget(self.static_canvas)
self._static_ax = self.static_canvas.figure.subplots()
self.tar = plt.imread(r'my_image.tif').copy()
self._static_ax.imshow(self.tar)
# Set cursor
self.cursor = Cursor(self._static_ax, horizOn=True, vertOn=True, useblit=True,
color = 'r', linewidth = 1)
#trigger zone
self.home.clicked.connect(self.Home)
self.pan.clicked.connect(self.Pan)
self.mark.clicked.connect(self.Mark)
def coor_onclick(self, event):
"""
This function will get coordination from click and plot it on canvas
"""
#check out-figure click
if event.xdata == None or event.ydata == None:
pass
else:
self.coor[0] = int(event.xdata)
self.coor[1] = int(event.ydata)
# print(self.coor)
#show line marking on canvas
tar = self.tar.copy()
#NOTE:: self.coor = [x,y] = [col, row]
# x = self.coor[0]
# y = self.coor[1]
#marking line
for r in range(tar.shape[1]):
for c in range(tar.shape[0]):
tar[self.coor[1], c] = [255, 0, 0]
tar[r, self.coor[0]] = [255, 0, 0]
#set final mark on canvas
self._static_ax.clear()
self._static_ax.imshow(tar)
self._static_ax.axis('off')
# Set cursor
self.cursor = Cursor(self._static_ax, horizOn=True, vertOn=True, useblit=True,
color = 'r', linewidth = 1)
self.static_canvas.draw()
def Home(self):
self.cid = self.static_canvas.mpl_connect('button_press_event', self.coor_onclick)
self.addToolBar.home()
def Pan(self):
if self.cid is None:
pass
else:
#disconnect to self.coor_onclick
self.static_canvas.mpl_disconnect(self.cid)
self.addToolBar.pan()
def Mark(self):
self.cid = self.static_canvas.mpl_connect('button_press_event', self.coor_onclick)
if __name__ == "__main__":
# Check whether there is already a running QApplication (e.g., if running
# from an IDE).
qapp = QtWidgets.QApplication.instance()
if not qapp:
qapp = QtWidgets.QApplication(sys.argv)
app = ApplicationWindow()
app.show()
app.activateWindow()
app.raise_()
qapp.exec_()
I have modified from matplotlib documentation.
Check the current mode of NavigationToolbar and if the mode is "PAN", set the mode off by calling pan() again (which will uncheck the action (check out the source code for more details.)).
FYI:
You can check the current mode of the NavigationToolbar by using NavigationToolbar.mode.name, currently there are two modes: "ZOOM" and "PAN".
In your code, change function Mark like this:
def Mark(self):
# if the current mode is Pan, set the mode off by unchecking it.
if self.nav_toolbar.mode.name == "PAN":
self.nav_toolbar.pan()
self.cid = self.static_canvas.mpl_connect(
'button_press_event', self.coor_onclick)