I am trying to make a simple interactive plot, where a value of single variable can be changed by the slider. I already have non-interactive code which is working, where I use pandas DataFrame. I am using PySimpleGUI, to which I am completely new. I have managed to get a window with the slider, but no plot which changes. Where do I do an error in my code?
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import copy as copy
import PySimpleGUI as sg
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
V_A=5
dfN_A=np.array([[-1, 1], [0, 3], [1, -1]])
dfN=copy.deepcopy(dfN_A)
dfN[:,-1]=dfN_A[:,-1]*V_A
DC = pd.DataFrame(dfN,
columns=['X','value'])
_VARS = {'window': False,
'fig_agg': False,
'pltFig': False,
'V_A': 5}
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
AppFont = 'Any 16'
SliderFont = 'Any 14'
sg.theme('black')
layout = [[sg.Canvas(key='figCanvas', background_color='#FDF6E3')],
# pad ((left, right), (top, bottom))
[sg.Text(text="V_A :",
font=SliderFont,
background_color='#FDF6E3',
pad=((0, 0), (10, 0)),
text_color='Black'),
sg.Slider(range=(-10,10), size=(60, 10),
orientation='h', key='-SLIDERV_A-')],
[sg.Button('Exit', font=AppFont, pad=((540, 0), (0, 0)))]]
_VARS['window'] = sg.Window('Simple GUI', layout, finalize=True,resizable=True, element_justification='center', font='Helvetica 18')
def drawChart():
_VARS['pltFig'] = plt.figure()
fig = plt.gcf()
DC.plot(y='value',x='X')
_VARS['fig_agg'] = draw_figure(
_VARS['window']['figCanvas'].TKCanvas, _VARS['pltFig'])
def updateChart():
_VARS['fig_agg'].get_tk_widget().forget()
plt.cla()
plt.clf()
_VARS['fig_agg'] = draw_figure(
_VARS['window']['figCanvas'].TKCanvas, _VARS['pltFig'])
def updateDataV_A(val):
_VARS['V_A'] = val
updateChart()
drawChart()
while True:
event, values = _VARS['window'].read(timeout=2000)
if event == sg.WIN_CLOSED or event == 'Exit':
break
elif event == '-SliderV_A-':
updateDataV_A(int(values['-SliderV_A-']))
_VARS['window'].close()
There're some issues in your code. Try not to say much about those issues, for easily, so a different code here for you.
import numpy as np
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
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. Define a function to draw the figure
def plot_figure(var):
ax.cla()
ax.plot(x0, y0*var)
canvas.draw() # Rendor figure into canvas
# 3. Initial Values
var = 5
x0, y0 = np.array([-1, 0, 1]), np.array([1, 3, -1])
# 4. create PySimpleGUI window
sg.theme('DarkBlue3')
layout = [
[sg.Canvas(size=(640, 480), key='Canvas')],
[sg.Text(text="Var"),
sg.Slider(range=(-10, 10), default_value=var, size=(10, 20), expand_x=True, enable_events=True, orientation='h', key='Slider')],
[sg.Push(), sg.Button('Exit'), sg.Push()],
]
window = sg.Window('Simple GUI', layout, finalize=True, resizable=True)
# 5. Create a matplotlib canvas under sg.Canvas or sg.Graph
fig = Figure()
ax = fig.add_subplot()
canvas = Canvas(fig, window['Canvas'].Widget)
# 6. initial for figure
plot_figure(var)
# 7. PySimpleGUI event loop
while True:
event, values = window.read()
if event in (sg.WIN_CLOSED, 'Exit'):
break
elif event == 'Slider':
var = values[event]
plot_figure(var)
# 8. Close window to exit
window.close()
Related
I'm new in PySimpleGui and I want to plot a function which takes a variable in a slider. My problem is that when I update the value of the variable through the slider, the plot window becomes bigger each time. Any solution for that?
Here is my minimal working code:
import numpy as np
import matplotlib.pyplot as plt
import PySimpleGUI as sg
import math
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg,\
NavigationToolbar2Tk
def fun(nu):
x=np.arange(1,10,0.1)
plt.plot(x,nu*x)
return plt.gcf()
sg.theme('SandyBeach')
layout = [
[sg.Text('nu_min', size =(15, 1)), sg.InputText(0.2,key='-nu_min-')],
[sg.Text('nu_max', size =(15, 1)), sg.InputText(1,key='-nu_max-')],
[sg.Text('nu_step', size =(15, 1)), sg.InputText(0.1,key='-nu_step-')],
[sg.Submit('Run'), sg.Cancel()]
]
window = sg.Window('Simple data entry window', layout)
event, values = window.read()
window.close()
nu_min=float(values['-nu_min-'])
nu_max=float(values['-nu_max-'])
nu_step=float(values['-nu_step-'])
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 delete_figure_agg(figure_agg):
figure_agg.get_tk_widget().forget()
plt.close('all')
figure_w, figure_h = 400, 400
layout = [[sg.Text('Graph Element Combined with Math!', justification='center', relief=sg.RELIEF_SUNKEN, expand_x=True, font='Courier 18')],
[sg.Button('Plot'), sg.Cancel(), sg.Button('Popup')],
[sg.Canvas(size=(figure_w, figure_h), key='-CANVAS-')],
[sg.Text('nu', font='Courier 14'), sg.Slider((nu_min,nu_max),default_value=nu_min,resolution=nu_step, orientation='h', enable_events=True, key='-slider_nu-', expand_x=True)]]
window = sg.Window('', layout, finalize=True)
figure_agg = None
while True:
event, values = window.read()
if figure_agg:
# ** IMPORTANT ** Clean up previous drawing before drawing again
delete_figure_agg(figure_agg)
fig=fun(values['-slider_nu-'])
figure_agg = draw_figure(window['-CANVAS-'].TKCanvas, fig)
window.close()
I ran your code, and see that your Y values shrink and grow as the slider is changed. Is this what you mean when you say, "the plot window becomes bigger..."
If so, try setting the Xlim and Ylim values to limit your plot values. When I inserted this into your code, the Y values are fixed at 10, and the plot updates as I move the slider.
def fun(nu):
x=np.arange(1,10,0.1)
plt.xlim(10)
plt.ylim(10)
plt.plot(x,nu*x)
return plt.gcf()
I was writing a code that Ploting variable input into a Bar Chart. the idea is: on each click, the code reads its input and refresh the ploted Bar chart to a new one, like the values increases on each click. here is the output:
enter image description here
After the second click (which is the supposed the second test), the canvas fiure doesn't refresh, but it adds another figure below.
enter image description here
How can keep display all the results in the same figure, which mean refreshing the same figure each time I click. I tried the creat a refresh function ,and I used delete(), clear()... and nothing is work properly (or may be I didn't use them right).
import time
import PySimpleGUI as sg
import pandas as pd
import tkinter as tk
from matplotlib.ticker import NullFormatter
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from tkinter import *
import matplotlib
import csv
from protoHODReader import main
from result import TestResult
matplotlib.use('TkAgg')
SW_ToT = 0 #my_variable
SW_OK = 0 #my_variable
SW_NOK = 0 #my_variable
test_result = TestResult.get_instance() #the input from another py code.
def plot_bar(plt_):#creating the plot
plt_.clf()
bar_values = (test_result.sw_total, test_result.sw_ok, test_result.sw_nok)
ind = np.arange(len(bar_values))
width = 0.4
p1 = plt_.bar(ind, bar_values, width)
plt_.ylabel('Quantity')
plt_.title('Test Results')
plt_.xticks(ind, ('Number Total of SW', 'SW oK', 'SW NOK'))
plt_.yticks(np.arange(0, 81, 10))
plt_.legend((p1[0],), ('Data Group 1',))
plt_.gcf()
def draw_figure(canvas, figure):#creating the 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 refresh(plt_, window_):#my attempt_to_refresh
plot_bar(plt_)
fig_ = plt_.gcf()
figure_x_, figure_y_, figure_w_, figure_h_ = fig_.bbox.bounds
fig_photo_ = draw_figure(window_['-CANVAS-'].TKCanvas, fig_)
canvas_bar = fig_photo_
#fig = matplotlib.figure.Figure(figsize=(5, 4))
plot_bar(plt)
sg.theme('DarkAmber')
fig = plt.gcf()
figure_x, figure_y, figure_w, figure_h = fig.bbox.bounds
layout = [[sg.Text('test', font='Any 18')],
[sg.Text('Please Scan the SW ID', size=(20, 1)), sg.InputText(key='PN'), sg.Button("LOAD")],
[sg.Canvas(size=(figure_w, figure_h), key='-CANVAS-')],
[sg.Button("Protoreader", size=(12, 3)), sg.Button("Print QR", size=(6, 3)), sg.Push(),
sg.Exit(size=(6, 3))]]
window = sg.Window('Local Application', layout, force_toplevel=True, finalize=True)
#fig_photo = draw_figure(window['-CANVAS-'].TKCanvas, fig)
event, values = window.read()
while True:
event, values = window.read()
if event == 'Exit' or event == sg.WIN_CLOSED:
break
# if event == 'LOAD':
if event == 'Protoreader':
exec(open('protoreader.py').read())
print(test_result.sw_nok)
print(test_result.sw_ok)
test_result.sw_total = test_result.sw_nok + test_result.sw_ok
print(test_result.`enter code here`sw_total)
refresh(plt, window)
test_result.saveAsCsv()
window.close()
I'm trying to change the size of my bar chart and having some difficulty. The bars are plotting correctly from my data, but the canvas does not increase in size when I adjust the size argument. I'm not getting any errors, so I'm not sure what I'm missing.
# define the window layout
layoutMain = [[sg.Text('Member Access')],
[sg.Menu(mainmenu_def, pad=(0,0))],
[sg.Button('Store To Inventory', size = (17,1)), sg.Button('Retrieve From Inventory', size = (17,1)), sg.Button('Inventory Details', size = (17,1)), sg.Button('Exit to Home', button_color = '#36454f', size = (17,1))],
[sg.Text('E-Stock', font='Any 18')],
[sg.Canvas(size=(100, 100), key='-CANVAS-')]]
windowMain = sg.Window('E-Stock', layoutMain, no_titlebar=False, size=(1000,600), finalize=True, resizable=True)
windowMain.maximize()
# add the plot to the window
fig_photo = draw_figure(windowMain['-CANVAS-'].TKCanvas, fig)
Here's example, I define the size of sg.Canvas by the figsize and dpi in matplolib figure.
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import PySimpleGUI as sg
matplotlib.use('TkAgg')
w, h = figsize = (5, 3) # figure size
fig = matplotlib.figure.Figure(figsize=figsize)
dpi = fig.get_dpi()
size = (w*dpi, h*dpi) # canvas size
t = np.arange(0, 3, .01)
fig.add_subplot(111).plot(t, 2 * np.sin(2 * np.pi * t))
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
layout = [[sg.Text('Plot test')],
[sg.Canvas(size=size, key='-CANVAS-')],
[sg.Button('Ok')]]
window = sg.Window('Embedding Matplotlib', layout, finalize=True, element_justification='center', font='Helvetica 18')
fig_canvas_agg = draw_figure(window['-CANVAS-'].TKCanvas, fig)
event, values = window.read()
window.close()
I need to reset the graph based on two buttons. It displays the first graph if button1 pressed but no the second one after pressing button2.
First graph is shown properly but need to clear the canvas
Code below:
import matplotlib.pyplot as plt
import matplotlib
import numpy
import numpy as np
import PySimpleGUI as psg
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
matplotlib.use('TkAgg')
def make_graph_and_put_on_canvas(x, y, xlabel, ylabel, graph_title, canvas):
figure,ax=plt.subplots()
ax.plot(x, y)
ax.set(xlabel=xlabel, ylabel=ylabel,
title=graph_title)
ax.grid()
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
if __name__ == '__main__':
layout = [[psg.B("Button1"),psg.B("Button2")],[psg.Canvas(key="canvas")]]
Graph = psg.Window(title="Graph", layout=layout, size=(500, 500))
while (True):
event, Value = Graph.read()
if event == psg.WINDOW_CLOSED:
Graph.close()
break
if event=="Button1":
#Make the first graph for y=2x
x=[0,1,2,3]
y=[0,2,4,6]
make_graph_and_put_on_canvas(x, y, "x", "y", "title",Graph["canvas"].TKCanvas)
if event=="Button2":
# Make the first graph for y=3x
x = [0, 1, 2, 3]
y = [0, 3, 6, 9]
make_graph_and_put_on_canvas(x, y, "x", "y", "title",Graph["canvas"].TKCanvas)
FigureCanvasTkAgg(figure, canvas) will create a new canvas in Graph["canvas"].TKCanvas.
With option side='top' when pack, you will get the new canvas from top to bottom of Graph["canvas"].TKCanvas. Second Button did make second graph but on bottom of your window, invisible for size = (500, 500) in your window.
call following code once if you want new graph show on same figure,
figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
clear figure or axis by
clf() # clear figure
cla() # clear axis
update figure on canvas after all plots on figure done
figure_canvas_agg.draw()
Following code just work, not optimized.
import matplotlib.pyplot as plt
import matplotlib
import numpy
import numpy as np
import PySimpleGUI as psg
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
matplotlib.use('TkAgg')
def make_graph_and_put_on_canvas(x, y, xlabel, ylabel, graph_title, canvas):
ax.cla()
ax.plot(x, y)
ax.set(xlabel=xlabel, ylabel=ylabel, title=graph_title)
ax.grid()
figure_canvas_agg.draw()
return figure_canvas_agg
if __name__ == '__main__':
layout = [[psg.B("Button1"),psg.B("Button2")],[psg.Canvas(key="canvas")]]
Graph = psg.Window(title="Graph", layout=layout, size=(500, 500), finalize=True)
figure, ax = plt.subplots()
canvas = Graph["canvas"].Widget
figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
while (True):
event, Value = Graph.read()
if event == psg.WINDOW_CLOSED:
break
if event=="Button1":
# Make the first graph for y=2x
x=[0,1,2,3]
y=[0,2,4,6]
make_graph_and_put_on_canvas(x, y, "x", "y", "title1", canvas)
if event=="Button2":
# Make the first graph for y=3x
x = [0, 1, 2, 3]
y = [0, 3, 6, 9]
make_graph_and_put_on_canvas(x, y, "x", "y", "title2", canvas)
Graph.close()
I would like to make an equivalent of the function FuncAnimation from matplotlib.animation, in which I could control the current plotted data using the scrollbar.
Say you have a data array which contains data points to be plotted at each time i. When using FuncAnimation, you first need to define a function ( here animate(i) ) which will be called for each time i = 1 to len(data[:,0]) :
def animate(i):
ax.plot(data[i,:])
anim = FuncAnimation(fig, animate, interval=100, frames=len(data[:,0]))
plt.draw()
plt.show()
but you cannot control the time i, like with a play/stop functionality. What I would like to do is to call the function animate(i), with i being the position of the scrollbar.
I found this example ( using the events from matplotlib:
https://matplotlib.org/3.2.1/users/event_handling.html )
but the mpl_connect doesn't have a "scrollbar_event".
import tkinter
from random import randint
import matplotlib as plt
import numpy as np
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
# Implement the default Matplotlib key bindings.
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
root = tkinter.Tk()
root.wm_title("Embedding in Tk")
#create figure
fig = Figure(figsize=(5, 4), dpi=100)
ax = fig.add_axes([0,0,1,1])
ax.imshow(np.array([[0,10],[23,40]]))
#create canvas with figure
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
def on_key_press(event):
ax.clear()
ax.imshow(np.array([[randint(0,30),randint(0,30)],[randint(0,30),randint(0,30)]]))
canvas.draw_idle()
key_press_handler(event, canvas)
print("you pressed {}".format(event.key))
#connect canvas to event function
canvas.mpl_connect("key_press_event", on_key_press)
def _quit():
root.quit() # stops mainloop
root.destroy() # this is necessary on Windows to prevent
# Fatal Python Error: PyEval_RestoreThread: NULL tstate
button = tkinter.Button(master=root, text="Quit", command=_quit)
button.pack(side=tkinter.BOTTOM)
tkinter.mainloop()
Actually the scroll functionality is given by matplotlib widgets !!
The example below works fine :
import matplotlib
import tkinter as Tk
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from myPytHeader import *
matplotlib.use('TkAgg')
root = Tk.Tk()
root.wm_title("Embedding in TK")
fig = plt.Figure(figsize=(8, 6))
canvas = FigureCanvasTkAgg(fig, root)
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
nDt = nbLines("grid.dat")
nDx = nbGridPoints("grid.dat")
grid = np.zeros( (nDt,nDx) ) ; loadData("grid.dat", grid)
valu = np.zeros( (nDt,nDx) ) ; loadData("valu.dat", valu)
ax=fig.add_subplot(111)
fig.subplots_adjust(bottom=0.25)
ax_time = fig.add_axes([0.12, 0.1, 0.78, 0.03])
s_time = Slider(ax_time, 'Time', 0, nDt, valinit=0, valstep=1)
def update(val):
frame = int(s_time.val)
ax.clear()
ax.set(xlim=(-0.05, 1.05), ylim=(-0.05, 1.25))
ax.grid()
ax.scatter(grid[frame,:], valu[frame,:], color='b', marker='.')
fig.canvas.draw_idle()
s_time.on_changed(update)
Tk.mainloop()
After all these years I've found solutions to my problems here, I am in debt !!!
Here is the final solution I came with.
I hope it can be useful to someone someday somehow !!
import matplotlib
import numpy as np
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.widgets import Slider
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
# load data
nDt = 1000
nDx = 400
grd = np.zeros( (nDt,nDx) )
val = np.zeros( (nDt,nDx) )
for t in np.arange(nDt):
for x in np.arange(nDx):
grd[t,x] = x / nDx
val[t,x] = (x / nDx) * (t/nDt) * np.sin(10 * 2*np.pi * (t-x)/nDt)
matplotlib.use('TkAgg')
root = tk.Tk()
root.wm_title("Embedding in TK")
fig = plt.Figure(figsize=(8, 6))
canvas = FigureCanvasTkAgg(fig, root)
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
ax=fig.add_subplot(111)
fig.subplots_adjust(bottom=0.25)
ax.set(xlim=(-0.05, 1.05), ylim=(-1.05, 1.05))
ax.grid()
scat = ax.scatter(grd[0,:], val[0,:], color='b', marker='.')
ax_time = fig.add_axes([0.12, 0.1, 0.78, 0.03])
s_time = Slider(ax_time, 'Time', 0, nDt, valinit=0, valstep=1)
i_anim = 0
i_relative = 0
i_current = 0
def updateGraph(i):
y_i = val[i,:]
scat.set_offsets(np.c_[grd[i,:], y_i])
def updateFromAnim(i):
global i_anim
global i_current
global i_relative
i_anim = i
i_current = i + i_relative
s_time.set_val(i_current)
updateGraph(i_current)
def updateFromScroll(val):
global i_anim
global i_current
global i_relative
i_relative = int(s_time.val) - i_anim
i_current = int(s_time.val)
updateGraph(i_current)
def onClick():
global anim_running
if anim_running:
anim.event_source.stop()
anim_running = False
else:
anim.event_source.start()
anim_running = True
start_button = tk.Button(root, text="START/STOP", command=onClick)
start_button.pack()
anim_running = True
anim = FuncAnimation(fig, updateFromAnim, interval=100, frames=nDt)
s_time.on_changed(updateFromScroll)
tk.mainloop()