Colorbar Help when updating using a combobox - matplotlib

I have been working with ttk to update two figures with a combobox event, the problem is that when updating the figure the colorbar isnt clearing, according to other threads I used d.remove() or d.ax.clear(), or fig.clear(), but is not working I really dont know why isnt removing old colorbar
The code just ask for a file directory where the excel files are stores(ill provide some files) and then just playing with combobox
the link (mediafire) to folder with excelfiles
folder with excel files
import tkinter as tk
from tkinter import ttk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
import matplotlib.cm as cm
import matplotlib
import os
from msilib.schema import ComboBox
newWindow = tk.Tk()
newWindow.state('zoomed')
newWindow.title('Compare traces')
newWindow.attributes('-topmost',True)
file_path = askdirectory(parent=newWindow)
main_frame2_1=tk.Frame(newWindow,bg="pink")
main_frame2_1.pack(fill=tk.BOTH,expand=True)
main_frame2_1.grid_columnconfigure(0, weight=1)
main_frame2_1.grid_rowconfigure(0, weight=1,uniform=1)
main_frame2_1.grid_columnconfigure(1, weight=1)
main_frame2_1.grid_rowconfigure(1, weight=1)
frame1_1=tk.Frame(main_frame2_1,bg="red")
frame1_1.grid(row=0, column=0, sticky='nsew')
frame1_2=tk.Frame(main_frame2_1,bg="green")
frame1_2.grid(row=0, column=1, sticky='nsew')
frame1_3=tk.Frame(main_frame2_1,bg="black",width=500,height=500)
frame1_3.grid(column=0,row=1,sticky='nsew')
frame1_4=tk.Frame(main_frame2_1,bg="pink",width=500,height=500)
frame1_4.grid(column=1,row=1,sticky='nsew')
frame1_1.grid_columnconfigure(0, weight=1)
frame1_1.grid_rowconfigure(0, weight=1)
frame1_1.grid_columnconfigure(1, weight=1)
frame1_1.grid_columnconfigure(2, weight=1)
frame1_2.grid_columnconfigure(0, weight=1)
frame1_2.grid_rowconfigure(0, weight=1)
frame1_2.grid_columnconfigure(1, weight=1)
frame1_2.grid_columnconfigure(2, weight=1)
class VerticalNavigationToolbar2Tk(NavigationToolbar2Tk):
def __init__(self, canvas, window):
super().__init__(canvas, window, pack_toolbar=False)
def _Button(self, text, file,toggle, command):
img_file = os.path.join(matplotlib.get_data_path(), 'images', file )
im = tk.PhotoImage(master=self, file=img_file)
im = im.subsample(2, 2)
b = tk.Button(master=self, text=text, padx=2, pady=2, image=im, command=command)
b._ntimage = im
b.pack(side=tk.TOP)
return b
def _Spacer(self):
s = tk.Frame(self, width=10, relief=tk.RIDGE, bg="DarkGray", padx=2)
s.pack(side=tk.TOP, pady=1)
return s
def set_message(self, s):
pass
def selection(event):
trace=textVar.get()
fullname = os.path.join(file_path,trace)
df=pd.read_excel(fullname, header=[3])
df = df.fillna(0)
df1=df.iloc[1:, :].reset_index(drop=True).set_index("distance")
df2=df1.T
temperature=df2.to_numpy(dtype='float')
depth=df1.index
depth.to_numpy(dtype='float')
df3=pd.read_excel(fullname)
df3= df3.fillna(0)
df5=df3.head(2)
df6=df5.T
df6.columns = df6.iloc[0]
df6= df6[1:]
df6 = pd.DataFrame(df6)
df6['Column'] = range(1, len(df6)+1)
df6=df6.set_index("Column")
time1=df6.index
time1.to_numpy()
time2 = time1.astype("float")
b, a = np.meshgrid(depth, time2)
b=b.astype('float64')
c=temperature
c=c.astype('float64')
l_a=a.min()
r_a=a.max()
l_b=b.min()
r_b=b.max()
l_c,r_c =0, np.abs(c).max()
ax4.clear()
c1 = ax4.pcolormesh(a, b, c, cmap='jet', vmin=l_c, vmax=r_c)
ax4.set_title('Heatmap'+ trace)
ax4.axis([l_a, r_a, l_b, r_b])
plt.gca().xaxis.set_major_locator(plt.MultipleLocator(2))
vmin=np.min(temperature)
vmax=np.max(temperature)
c1.set_clim(0,110)
ax4.set_ylabel('Distance')
ax4.set_xlabel('Column')
d=fig4.colorbar(c1,ax=ax4)
d.set_label('ºF')
canvas2.draw_idle()
values1=sorted(os.listdir(file_path), key=len)
textVar = tk.StringVar(frame1_1)
textVar.set('')
ccbox=ttk.Combobox(frame1_1,values=values1,height=20,width=10,textvariable=textVar)
ccbox.grid(column=1,row=0,sticky= "we")
ccbox.bind("<<ComboboxSelected>>", selection)
def selection1(event):
trace1=textVar1.get()
print(trace1)
values11=sorted(os.listdir(file_path), key=len)
textVar1 = tk.StringVar(frame1_2)
textVar1.set('')
ccbox1=ttk.Combobox(frame1_2,values=values11,height=20,width=10,textvariable=textVar1)
ccbox1.grid(column=1,row=0,sticky= "we")
ccbox1.bind("<<ComboboxSelected>>", selection1)
np.random.seed(19680801)
Z = np.random.rand(6, 10)
x = np.arange(-0.5, 10, 1) # len = 11
y = np.arange(4.5, 11, 1) # len = 7
fig4, ax4 = plt.subplots()
c1=ax4.pcolormesh(x, y, Z)
c1.set_clim(0,110)
d=fig4.colorbar(c1,ax=ax4)
canvas2 = FigureCanvasTkAgg(fig4, master=frame1_3)
fig4.canvas.draw()
canvas2.get_tk_widget().pack(side=tk.RIGHT, fill=tk.BOTH, expand=1)
toolbar2 = VerticalNavigationToolbar2Tk(canvas2, frame1_3)
toolbar2.update()
toolbar2.pack(side=tk.LEFT, fill=tk.Y)
newWindow.mainloop()
Heres is a SS of the problem:
Thanks in advance for any help, and Merry Christmas!!!!

Related

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. :)

Matplotlib cross hair cursor in PyQt5

I want to add a cross hair that snaps to data points and be updated on mouse move. I found this example that works well:
import numpy as np
import matplotlib.pyplot as plt
class SnappingCursor:
"""
A cross hair cursor that snaps to the data point of a line, which is
closest to the *x* position of the cursor.
For simplicity, this assumes that *x* values of the data are sorted.
"""
def __init__(self, ax, line):
self.ax = ax
self.horizontal_line = ax.axhline(color='k', lw=0.8, ls='--')
self.vertical_line = ax.axvline(color='k', lw=0.8, ls='--')
self.x, self.y = line.get_data()
self._last_index = None
# text location in axes coords
self.text = ax.text(0.72, 0.9, '', transform=ax.transAxes)
def set_cross_hair_visible(self, visible):
need_redraw = self.vertical_line.get_visible() != visible
self.vertical_line.set_visible(visible)
self.horizontal_line.set_visible(visible)
self.text.set_visible(visible)
return need_redraw
def on_mouse_move(self, event):
if not event.inaxes:
self._last_index = None
need_redraw = self.set_cross_hair_visible(False)
if need_redraw:
self.ax.figure.canvas.draw()
else:
self.set_cross_hair_visible(True)
x, y = event.xdata, event.ydata
index = min(np.searchsorted(self.y, y), len(self.y) - 1)
if index == self._last_index:
return # still on the same data point. Nothing to do.
self._last_index = index
x = self.x[index]
y = self.y[index]
# update the line positions
self.horizontal_line.set_ydata(y)
self.vertical_line.set_xdata(x)
self.text.set_text('x=%1.2f, y=%1.2f' % (x, y))
self.ax.figure.canvas.draw()
y = np.arange(0, 1, 0.01)
x = np.sin(2 * 2 * np.pi * y)
fig, ax = plt.subplots()
ax.set_title('Snapping cursor')
line, = ax.plot(x, y, 'o')
snap_cursor = SnappingCursor(ax, line)
fig.canvas.mpl_connect('motion_notify_event', snap_cursor.on_mouse_move)
plt.show()
But I get into trouble when I want to adapt the code with the PyQt5 and show the plot in a GUI. My code is:
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout
import sys
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import numpy as np
class SnappingCursor:
"""
A cross hair cursor that snaps to the data point of a line, which is
closest to the *x* position of the cursor.
For simplicity, this assumes that *x* values of the data are sorted.
"""
def __init__(self, ax, line):
self.ax = ax
self.horizontal_line = ax.axhline(color='k', lw=0.8, ls='--')
self.vertical_line = ax.axvline(color='k', lw=0.8, ls='--')
self.x, self.y = line.get_data()
self._last_index = None
# text location in axes coords
self.text = ax.text(0.72, 0.9, '', transform=ax.transAxes)
def set_cross_hair_visible(self, visible):
need_redraw = self.vertical_line.get_visible() != visible
self.vertical_line.set_visible(visible)
self.horizontal_line.set_visible(visible)
self.text.set_visible(visible)
return need_redraw
def on_mouse_move(self, event):
if not event.inaxes:
self._last_index = None
need_redraw = self.set_cross_hair_visible(False)
if need_redraw:
self.ax.figure.canvas.draw()
else:
self.set_cross_hair_visible(True)
x, y = event.xdata, event.ydata
index = min(np.searchsorted(self.y, y), len(self.y) - 1)
if index == self._last_index:
return # still on the same data point. Nothing to do.
self._last_index = index
x = self.x[index]
y = self.y[index]
# update the line positions
self.horizontal_line.set_ydata(y)
self.vertical_line.set_xdata(x)
self.text.set_text('x=%1.2f, y=%1.2f' % (x, y))
self.ax.figure.canvas.draw()
class Window(QMainWindow):
def __init__(self):
super().__init__()
widget=QWidget()
vbox=QVBoxLayout()
plot1 = FigureCanvas(Figure(tight_layout=True, linewidth=3))
ax = plot1.figure.subplots()
x = np.arange(0, 1, 0.01)
y = np.sin(2 * 2 * np.pi * x)
line, = ax.plot(x, y, 'o')
snap_cursor = SnappingCursor(ax, line)
plot1.mpl_connect('motion_notify_event', snap_cursor.on_mouse_move)
vbox.addWidget(plot1)
widget.setLayout(vbox)
self.setCentralWidget(widget)
self.setWindowTitle('Example')
self.show()
App = QApplication(sys.argv)
window = Window()
sys.exit(App.exec())
By running the above code, the data is plotted properly, but the cross hair is only shown in its initial position and does not move by mouse movement. Data values are also not displayed.
I have found a similar question here too, but the question is not answered clearly.
There are 2 problems:
snap_cursor is a local variable that will be removed when __init__ finishes executing. You must make him a member of the class.
The initial code of the tutorial is designed so that the point that information is displayed is the horizontal line that passes through the cursor and intersects the curve. In your initial code it differs from the example and also does not work for your new curve so I restored the logic of the tutorial.
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
import numpy as np
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
class SnappingCursor:
"""
A cross hair cursor that snaps to the data point of a line, which is
closest to the *x* position of the cursor.
For simplicity, this assumes that *x* values of the data are sorted.
"""
def __init__(self, ax, line):
self.ax = ax
self.horizontal_line = ax.axhline(color="k", lw=0.8, ls="--")
self.vertical_line = ax.axvline(color="k", lw=0.8, ls="--")
self.x, self.y = line.get_data()
self._last_index = None
# text location in axes coords
self.text = ax.text(0.72, 0.9, "", transform=ax.transAxes)
def set_cross_hair_visible(self, visible):
need_redraw = self.vertical_line.get_visible() != visible
self.vertical_line.set_visible(visible)
self.horizontal_line.set_visible(visible)
self.text.set_visible(visible)
return need_redraw
def on_mouse_move(self, event):
if not event.inaxes:
self._last_index = None
need_redraw = self.set_cross_hair_visible(False)
if need_redraw:
self.ax.figure.canvas.draw()
else:
self.set_cross_hair_visible(True)
x, y = event.xdata, event.ydata
index = min(np.searchsorted(self.x, x), len(self.x) - 1)
if index == self._last_index:
return # still on the same data point. Nothing to do.
self._last_index = index
x = self.x[index]
y = self.y[index]
# update the line positions
self.horizontal_line.set_ydata(y)
self.vertical_line.set_xdata(x)
self.text.set_text("x=%1.2f, y=%1.2f" % (x, y))
self.ax.figure.canvas.draw()
class Window(QMainWindow):
def __init__(self):
super().__init__()
widget = QWidget()
vbox = QVBoxLayout(widget)
x = np.arange(0, 1, 0.01)
y = np.sin(2 * 2 * np.pi * x)
canvas = FigureCanvas(Figure(tight_layout=True, linewidth=3))
ax = canvas.figure.subplots()
ax.set_title("Snapping cursor")
(line,) = ax.plot(x, y, "o")
self.snap_cursor = SnappingCursor(ax, line)
canvas.mpl_connect("motion_notify_event", self.snap_cursor.on_mouse_move)
vbox.addWidget(canvas)
self.setCentralWidget(widget)
self.setWindowTitle("Example")
app = QApplication(sys.argv)
w = Window()
w.show()
app.exec()

TKinter : Load Matplotlib Graphs Faster without Flicker when resize and close the application

Load Graphs quickly without a lag or flicker when user switch from one screen to another or even when close the application. Currently, I am using a small data to recreate this problem but in actual application I have 1000 data points to display on graph.
Steps to recreate the issue.
Click on Maximize Button to see the flicker or lag.
Close the application , you will see graphs rearranging on screen before close.
from tkinter import *
from pandas import DataFrame
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import threading as th
class AppLayout(Tk):
def __init__(self):
Tk.__init__(self)
self.masterPane = PanedWindow(self )
self.leftPane = Frame(self.masterPane,relief = 'raised',bg='black',width =100)
self.masterPane.add(self.leftPane)
self.rightPane = Frame(self.masterPane,bg ='#0C0026')
self.rightPane.columnconfigure(2, weight=1)
self.masterPane.add(self.rightPane)
self.masterPane.pack(fill = 'both',expand = True)
self.dashBoard()
def dashBoard(self):
self.Dashboard_Frame = Frame(self.rightPane,bg ='#0C0026')
self.Dashboard_Frame.grid(row=0,column =0,sticky ='nsew')
def Display_Chart():
def Draw_Graph(fig_dim,fig_pady,fig_padx,fig_row,fig_column,fig_sticky):
data1 = {'Country': ['US','CA','GER','UK','FR','IN'],
'GDP_Per_Capita': [45000,42000,52000,49000,47000,85000]
}
df1 = DataFrame(data1,columns=['Country','GDP_Per_Capita'])
figure1 = plt.Figure(figsize= fig_dim, dpi=100)
ax1 = figure1.add_subplot(111)
bar1 = FigureCanvasTkAgg(figure1, self.Dashboard_Frame)
bar1.get_tk_widget().grid(row=fig_row,column =fig_column,sticky =fig_sticky,pady=fig_pady,padx=fig_padx)
df1 = df1[['Country','GDP_Per_Capita']].groupby('Country').sum()
df1.plot(kind='line', legend=True, ax=ax1,color ='w')
ax1.set_title('Country Vs. GDP Per Capita',color ='w')
ax1.yaxis.label.set_color('w')
ax1.xaxis.label.set_color('w')
ax1.spines['bottom'].set_color('w')
ax1.spines['top'].set_color('w')
ax1.spines['right'].set_color('w')
ax1.spines['left'].set_color('w')
figure1.patch.set_facecolor('w')
ax1.tick_params(colors='w', which='both')
ax1.set_facecolor('#0a043c')
figure1.set_facecolor('#000839')
self.Dashboard_Frame.rowconfigure(1, weight=1)
self.Dashboard_Frame.columnconfigure(0, weight=1)
th.Thread(target = Draw_Graph((6,3),(40,10),20,1,0,'nswe')).start()
th.Thread(target = Draw_Graph((6,3),(0,20),20,2,0,'nswe')).start()
th.Thread(target = Draw_Graph((4,2),(40,10),0,1,1,'nswe')).start()
th.Thread(target = Draw_Graph((4,2),(0,20),0,2,1,'nswe')).start()
self.rightPane.columnconfigure(0, weight=1)
self.rightPane.rowconfigure(0, weight=1)
self.Dashboard_Frame.rowconfigure(1, weight=1)
self.Dashboard_Frame.rowconfigure(2, weight=1)
self.Dashboard_Frame.columnconfigure(0, weight=1)
self.Dashboard_Frame.columnconfigure(1, weight=1)
self.Dashboard_Frame.columnconfigure(3, weight=1)
th.Thread(target=Display_Chart()).start()
app = AppLayout()
app.mainloop()

Resize one subplot after removing another

Problem description:
I'm building an interface for my lab, I intergrarted matplotlib with pyqt5 widget, there is a real time video display widget working on multi-thread and queue. I managed to show single shot with cross-section plot by adding divider. However, when I remove the cross-section plots, and redraw the figure_idle, the video frame can never moved back to its initial position with its initial size. I adjust the figure with navigator_tool_bar (top, bottom...), However it seems that there are blank areas left after removing the cross-section plots. Do you have any idea?
The initial figure:
Display cross-sections:
Clear cross-section and redraw:
Video widget code:
class VideoViewer(FigureCanvas):
def __init__(self, parent=None, width=4, height=4, dpi=70):
self.figwidth = width
self.figheight = height
self.fig = Figure(figsize=(self.figwidth, self.figheight), dpi=dpi)
FigureCanvas.__init__(self, self.fig)
FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
self.axes = self.fig.add_subplot()
self.fig.subplots_adjust(top=0.975,bottom=0.048,left=0.029,right=0.983,hspace=0.2,wspace=0.2)
Cross section code:
#create cross-sections
self.divider = make_axes_locatable(self.axes)
self.top_ax = self.divider.append_axes("top", 1.05, pad=0.2,sharex=self.axes)
self.right_ax = self.divider.append_axes("right", 1.05,pad=0.2,sharey=self.axes)
#create lines
self.v_line = self.axes.axvline(xvlinepos, color='r')
self.h_line = self.axes.axhline(yhlinepos, color='g')
self.v_cross, = self.right_ax.plot(Norm_xvlinedata,np.arange(self.ImageData.shape[0]), 'r-')
self.h_cross, = self.top_ax.plot(np.arange(self.ImageData.shape[1]),Norm_yhlinedata, 'g-')
Clear cross-section code:
def ClearCrossSection(self):
self.fig.canvas.mpl_disconnect(self.pick_event_v)
self.fig.canvas.mpl_disconnect(self.pick_event_h)
self.h_line.remove()
self.v_line.remove()
self.v_cross.remove()
self.h_cross.remove()
self.right_ax.remove()
self.top_ax.remove()
self.fig.canvas.draw_idle()
What I did:
Light_layout + subplot_adjust -----> does not work.
Constrained_layout -----> does not work.
Constrained_layout + GridSpec by declaring at beginning self.axes takes all cols and rows.-----> Does not work.
An exemple of the problem:
import sys
# GUI
from PyQt5.QtWidgets import*
from PyQt5.QtCore import *
from PyQt5.QtGui import *
# Matplotlib
import matplotlib
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
import matplotlib.gridspec as gridspec
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5 import NavigationToolbar2QT as NavigationToolbar
from mpl_toolkits.axes_grid1 import make_axes_locatable
from PIL import Image
import matplotlib.lines as lines
# Generate data
import numpy as np
'''
need to update video frame, I'm using blitting, so better not clear the whole figure.
the whole code could be too long to show here.
'''
class Window(QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.resize(800,600)
self.setGeometry(350,250,950,600)
# Creat MainWidget
self.MainWidget = QWidget()
self.LayoutMainWidget = QGridLayout()
self.MainWidget.setLayout(self.LayoutMainWidget)
# Matplotlib widget
self.MatplotViewer = VideoViewer()
self.FigureTool = NavigationToolbar(self.MatplotViewer, self)
# Button plot image
self.ButtPltImg = QPushButton("Plot Image")
# BUtton plot cross
self.ButtPltCross = QPushButton("Cross View")
self.ButtPltCross.setCheckable(True)
self.ButtPltCross.setStyleSheet("background-color: Green")
# add widgets
self.LayoutMainWidget.addWidget(self.MatplotViewer,0,0,7,7)
self.LayoutMainWidget.addWidget(self.FigureTool, 7,0,1,7)
self.LayoutMainWidget.addWidget(self.ButtPltImg, 2,7,1,1)
self.LayoutMainWidget.addWidget(self.ButtPltCross, 3,7,1,1)
# Set central widget
self.setCentralWidget(self.MainWidget)
self.connection()
def GenerateImage(self, state):
if self.ButtPltCross.isChecked():
self.ButtPltCross.setChecked(False)
self.MatplotViewer.ClearCrossSection()
self.MatplotViewer.UpdateFrame()
else:
self.MatplotViewer.UpdateFrame()
def PlotCrossSection(self, state):
if self.ButtPltCross.isChecked():
self.MatplotViewer.PlotCrossSection()
def ClearCrossSection(self, state):
if not(self.ButtPltCross.isChecked()):
self.MatplotViewer.ClearCrossSection()
def connection(self):
self.ButtPltImg.clicked.connect(lambda state=True: self.GenerateImage(state))
self.ButtPltCross.clicked.connect(lambda state=True: self.PlotCrossSection(state))
self.ButtPltCross.clicked.connect(lambda state=True: self.ClearCrossSection(state))
class VideoViewer(FigureCanvas):
def __init__(self, parent=None, width=4, height=4, dpi=70):
# Figure
self.fig = Figure(figsize=(width, height), dpi=dpi)
# Init Parent
FigureCanvas.__init__(self, self.fig)
FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
# Ax
self.axes = self.fig.add_subplot(111)
self.fig.subplots_adjust(top=0.975,bottom=0.048,left=0.029,right=0.983,hspace=0.2,wspace=0.2)
# Plot init image
self.PlotInitFrame()
def PlotInitFrame(self):
self.ImageData = self.ImageGenerate()
self.image = self.axes.imshow(self.ImageData, cmap='Greys', interpolation='none')
self.fig.canvas.draw_idle()
def UpdateFrame(self):
self.ImageData = self.ImageGenerate()
self.image.set_data(self.ImageData)
self.fig.canvas.draw_idle()
def PlotCrossSection(self):
# create axes
self.divider = make_axes_locatable(self.axes)
self.top_ax = self.divider.append_axes("top", 1.05, pad=0.2,sharex=self.axes)
self.right_ax = self.divider.append_axes("right", 1.05, pad=0.2,sharey=self.axes)
self.top_ax.xaxis.set_tick_params(labelbottom=False)
self.right_ax.yaxis.set_tick_params(labelleft=False)
# set cross section limit
self.right_ax.set_xlim(right=1.05)
self.top_ax.set_ylim(top=1.05)
# some pars
xmin, xmax = self.axes.get_xlim()
ymin, ymax = self.axes.get_ylim()
v_mid = int((xmin + xmax)/2)
h_mid = int((ymin + ymax)/2)
# set line
self.v_line = lines.Line2D([v_mid, v_mid], [ymin, ymax], color='r', pickradius=5)
self.axes.add_line(self.v_line)
self.h_line = lines.Line2D([xmin, xmax], [h_mid, h_mid], color='g', pickradius=5)
self.axes.add_line(self.h_line)
# set cross section data
Norm_xvlinedata = self.NormalizeData(self.ImageData[:,v_mid])
self.v_cross, = self.right_ax.plot(Norm_xvlinedata, np.arange(self.ImageData.shape[0]), 'r-')
Norm_yhlinedata = self.NormalizeData(self.ImageData[h_mid,:])
self.h_cross, = self.top_ax.plot(np.arange(self.ImageData.shape[1]), Norm_yhlinedata, 'g-')
self.fig.canvas.draw_idle()
def NormalizeData(self, data_temp):
min_temp = np.min(data_temp)
max_temp = np.max(data_temp)
if min_temp != max_temp:
return (data_temp-min_temp)/(max_temp-min_temp)
else:
return data_temp/data_temp
def ClearCrossSection(self):
self.v_line.remove()
self.h_line.remove()
self.top_ax.remove()
self.right_ax.remove()
self.fig.canvas.draw_idle()
def ImageGenerate(self):
xx,yy = np.meshgrid(np.linspace(-502,502,1024),np.linspace(-502,502,1024))
r = np.sqrt(xx**2+yy**2)
AMP = np.random.randint(150,250)
SIG = np.random.randint(200,250)
T = np.random.randint(115,135)
return AMP*np.exp(-(r)**2/(2*SIG**2))*np.cos(2*np.pi/T*r)
if __name__ == '__main__':
app = QApplication(sys.argv)
MainWindow = Window()
MainWindow.showMaximized()
sys.exit(app.exec_())

Interactive matplotlib plot in PySimpleGUI

I'm trying to get the RectangleSelector form matplotlib.widgets to work with PySimpleGUI.
I'm basing my test code on the RectangleSelector demo shown in the accepted answer on this question.
I'm getting the plot to show in PySimpleGUI but it's not interactive. Is it even possible in PySimpleGUI to have interactive matplotlib widgets?
import PySimpleGUI as sg
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.widgets import RectangleSelector
import matplotlib
matplotlib.use('TkAgg')
xdata = np.linspace(0,9*np.pi, num=301)
ydata = np.sin(xdata)
fig, ax = plt.subplots()
line, = ax.plot(xdata, ydata)
def draw_figure(canvas, figure):
figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
figure_canvas_agg.draw()
figure_canvas_agg.get_tk_widget().pack(side="top", fill="both", expand=1)
return figure_canvas_agg
def line_select_callback(eclick, erelease):
x1, y1 = eclick.xdata, eclick.ydata
x2, y2 = erelease.xdata, erelease.ydata
rect = plt.Rectangle( (min(x1,x2),min(y1,y2)), np.abs(x1-x2), np.abs(y1-y2) )
ax.add_patch(rect)
rs = RectangleSelector(ax, line_select_callback,
drawtype='box', useblit=False, button=[1],
minspanx=5, minspany=5, spancoords='pixels',
interactive=True)
layout = [[sg.Canvas(key="-CANVAS-")]]
window = sg.Window('test', layout, finalize=True, element_justification='center', font='Helvetica 16')
draw_figure(window["-CANVAS-"].TKCanvas, fig)
event, values = window.read()
Edit: Thanks to MikeyB for the pointer, I now have the following code, which shows an interactive plot, but it's still not possible to draw rectangles. The callback function doesn't seem to be firing. New code below:
import PySimpleGUI as sg
import numpy as np
from matplotlib.widgets import RectangleSelector
import matplotlib.figure as figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
# instantiate matplotlib figure
fig = figure.Figure()
ax = fig.add_subplot(111)
DPI = fig.get_dpi()
fig.set_size_inches(505 * 2 / float(DPI), 707 / float(DPI))
# ------------------------------- This is to include a matplotlib figure in a Tkinter canvas
def draw_figure_w_toolbar(canvas, fig, canvas_toolbar):
if canvas.children:
for child in canvas.winfo_children():
child.destroy()
if canvas_toolbar.children:
for child in canvas_toolbar.winfo_children():
child.destroy()
figure_canvas_agg = FigureCanvasTkAgg(fig, master=canvas)
figure_canvas_agg.draw()
toolbar = Toolbar(figure_canvas_agg, canvas_toolbar)
toolbar.update()
figure_canvas_agg.get_tk_widget().pack(side='right', fill='both', expand=1)
def line_select_callback(eclick, erelease):
x1, y1 = eclick.xdata, eclick.ydata
x2, y2 = erelease.xdata, erelease.ydata
rect = plt.Rectangle( (min(x1,x2),min(y1,y2)), np.abs(x1-x2), np.abs(y1-y2) )
print(rect)
ax.add_patch(rect)
class Toolbar(NavigationToolbar2Tk):
def __init__(self, *args, **kwargs):
super(Toolbar, self).__init__(*args, **kwargs)
# ------------------------------- PySimpleGUI CODE
layout = [
[sg.B('start', key='start')],
[sg.Canvas(key='controls_cv')],
[sg.Column(
layout=[
[sg.Canvas(key='fig_cv',
# it's important that you set this size
size=(500 * 2, 700)
)]
],
background_color='#DAE0E6',
pad=(0, 0)
)],
]
window = sg.Window('Test', layout)
while True:
event, values = window.read()
print(event, values)
if event == sg.WIN_CLOSED:
break
elif event == 'start':
x = np.linspace(0, 2 * np.pi)
y = np.sin(x)
line, = ax.plot(x, y)
rs = RectangleSelector(ax, line_select_callback,
drawtype='box', useblit=False, button=[1],
minspanx=5, minspany=5, spancoords='pixels',
interactive=True)
draw_figure_w_toolbar(window['fig_cv'].TKCanvas, fig, window['controls_cv'].TKCanvas)
window.close()
Is it even possible in PySimpleGUI to have interactive matplotlib widgets?
Yes.
The demo program on the project's GitHub shows how to make an interactive Matplotlib drawing.
https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Matplotlib_Embedded_Toolbar.py
You need to embed the controls into the window.
you need to add
fig.canvas.draw()
to your callback-function if you want the plot to be updated after the callback has triggered!
here's an updated version of your code that works just fine:
import PySimpleGUI as sg
import numpy as np
from matplotlib.widgets import RectangleSelector
import matplotlib.figure as figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import matplotlib.pyplot as plt
# instantiate matplotlib figure
fig = figure.Figure()
ax = fig.add_subplot(111)
DPI = fig.get_dpi()
fig.set_size_inches(505 * 2 / float(DPI), 707 / float(DPI))
# ------------------------------- This is to include a matplotlib figure in a Tkinter canvas
def draw_figure_w_toolbar(canvas, fig, canvas_toolbar):
if canvas.children:
for child in canvas.winfo_children():
child.destroy()
if canvas_toolbar.children:
for child in canvas_toolbar.winfo_children():
child.destroy()
figure_canvas_agg = FigureCanvasTkAgg(fig, master=canvas)
figure_canvas_agg.draw()
toolbar = Toolbar(figure_canvas_agg, canvas_toolbar)
toolbar.update()
figure_canvas_agg.get_tk_widget().pack(side='right', fill='both', expand=1)
def line_select_callback(eclick, erelease):
x1, y1 = eclick.xdata, eclick.ydata
x2, y2 = erelease.xdata, erelease.ydata
rect = plt.Rectangle( (min(x1,x2),min(y1,y2)), np.abs(x1-x2), np.abs(y1-y2) )
print(rect)
ax.add_patch(rect)
fig.canvas.draw()
class Toolbar(NavigationToolbar2Tk):
def __init__(self, *args, **kwargs):
super(Toolbar, self).__init__(*args, **kwargs)
# ------------------------------- PySimpleGUI CODE
layout = [
[sg.B('start', key='start')],
[sg.Canvas(key='controls_cv')],
[sg.Column(
layout=[
[sg.Canvas(key='fig_cv',
# it's important that you set this size
size=(500 * 2, 700)
)]
],
background_color='#DAE0E6',
pad=(0, 0)
)],
]
window = sg.Window('Test', layout)
while True:
event, values = window.read()
print(event, values)
if event == sg.WIN_CLOSED:
break
elif event == 'start':
x = np.linspace(0, 2 * np.pi)
y = np.sin(x)
line, = ax.plot(x, y)
rs = RectangleSelector(ax, line_select_callback,
drawtype='box', useblit=False, button=[1],
minspanx=5, minspany=5, spancoords='pixels',
interactive=True)
draw_figure_w_toolbar(window['fig_cv'].TKCanvas, fig, window['controls_cv'].TKCanvas)
window.close()