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()
Related
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()
I can bind a click event to a plot (i. e. to print the coordinates that were clicked) like so:
from matplotlib.backend_bases import MouseButton
import matplotlib.pyplot as plt
x = [1, 2, 3, 4]
y = [2, 3, 1, 4]
fig, ax = plt.subplots()
ax.plot(x, y)
def plotClick(event):
if event.button == MouseButton.LEFT:
print('Clicked at x=%f, y=%f' %(event.xdata, event.ydata))
plt.connect('button_press_event', plotClick)
plt.show()
I'd like to do the same thing with a plot that is contained within a canvas inside a tkinter window like so:
from matplotlib.backend_bases import MouseButton
import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
window = tk.Tk()
fig = Figure(figsize=(5, 3))
ax = fig.add_subplot(111)
x = [1, 2, 3, 4]
y = [2, 3, 1, 4]
line, = ax.plot(x, y)
canvas = FigureCanvasTkAgg(fig)
canvas.draw()
canvas.get_tk_widget().pack()
def plotClick(event):
if event.button == MouseButton.LEFT:
print('Clicked at x=%f, y=%f' %(event.xdata, event.ydata))
window.mainloop()
What would I have to do to accomplish the same behavior here?
Note: I am aware that one can bind events directly to the canvas using
canvas.get_tk_widget().bind('<Button-1>', plotClick)
with
def plotClick(event):
print('Clicked at x=%f, y=%f' %(event.x, event.y))
That however uses pixel coordinates on the canvas instead of the coordinates in the plot.
Instead of using plt.connect, use
canvas.mpl_connect('button_press_event', plotClick)
Using this you can access the coordinates in the plot with event.xdata and event.ydata, but you can also still access the (pixel) coordinates on the canvas using event.x and event.y.
I have one, two, or three plots in a canvas of Tkinter. Let's assume I don't have any Mouse. There only is a keyboard. I want a cursor on these plots that moves at the same horizontal axis on all graphs in the canvas and of course when the Tk window shows up the yellow cursors (mplcursors) on all graphs be at the first point of graphs without any mouse movement and clicks(if you run the following code there are two cursors yellow and green, yellow appears by clicking on the line). The yellow cursors must move together (the same x-axes point) by right/left arrow keys on the keyboard.
from matplotlib.widgets import MultiCursor
import matplotlib.pyplot as plt
import numpy as np
from tkinter import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import mplcursors
root = Tk()
fig = Figure(figsize=(8,6))
fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True)
x = np.linspace(-np.pi, np.pi, 256, endpoint=True)
y = np.sin(x)
z = np.cos(x)
ax1.plot(x, y, label="sin function")
ax1.legend(loc="upper right")
ax2.plot(x, z, label="cos function")
canvas = FigureCanvasTkAgg(fig,root)
c1 = mplcursors.cursor(ax1, multiple=False,hover=False,highlight=False,bindings={"toggle_visible": "h", "toggle_enabled": "e","left": "left","right": "right"})
c2 = mplcursors.cursor(ax2, multiple=False,hover=False,highlight=False,bindings={"toggle_visible": "h", "toggle_enabled": "e","left": "left","right": "right"})
multi = MultiCursor(fig.canvas, (ax1, ax2), color='g', lw=0.5, horizOn=True, vertOn=True)
ax2.legend(loc="upper left")
canvas.draw()
#pack canavs
canvas.get_tk_widget().pack(side = BOTTOM, fill=BOTH, expand=True)
#create Navigation Toolbar
toolbar = NavigationToolbar2Tk(canvas, root) #add to canvas
canvas._tkcanvas.pack(side = TOP, fill=BOTH, expand=True)
root.mainloop()
Do you have any suggestions? Can I also make the Multi-Cursor (green lines) to move with mplcursors on plots and stick to them too?
I have tried multi-threading from this link, used mouse controlling from this link and getting pixels of first plot points by this question but I couldn't handle the first display event and it needed the mouse to be clicked on both plots separately to show the cursors.
My plots are more complicated, they are signals and I have multiple pages in my application.
The following code does what I wanted But couldn't solve to keep the green Multi-Cursor on the yellow ones! (Exploring yet)
the following code uses MouseEvent and _process_event which clicks on the plot by default, when calling it you should declare when this process happens?, on which plot, in which points!
from matplotlib.widgets import MultiCursor
import matplotlib.pyplot as plt
import numpy as np
from tkinter import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import mplcursors
import subprocess
from matplotlib.backend_bases import KeyEvent, MouseEvent
import copy
def _process_event(name, ax, coords, *args):
ax.viewLim # unstale viewLim.
if name == "__mouse_click__":
# So that the dragging callbacks don't go crazy.
_process_event("button_press_event", ax, coords, *args)
_process_event("button_release_event", ax, coords, *args)
return
display_coords = ax.transData.transform(coords)
if name in ["button_press_event", "button_release_event",
"motion_notify_event", "scroll_event"]:
event = MouseEvent(name, ax.figure.canvas, *display_coords, *args)
elif name in ["key_press_event", "key_release_event"]:
event = KeyEvent(name, ax.figure.canvas, *args, *display_coords)
else:
raise ValueError(f"Unknown event name {name!r}")
ax.figure.canvas.callbacks.process(name, event)
root = Tk()
fig = Figure(figsize=(8,6))
fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True)
x = np.linspace(-np.pi, np.pi, 256, endpoint=True)
y = np.sin(x)
z = np.cos(x)
ax1.plot(x, y, label="sin function")
ax1.legend(loc="upper right")
ax2.plot(x, z, label="cos function")
canvas = FigureCanvasTkAgg(fig,root)
c1 = mplcursors.cursor(ax1, multiple=False,hover=False,highlight=False,bindings={"toggle_visible": "h", "toggle_enabled": "e","left": "left","right": "right"})
c2 = mplcursors.cursor(ax2, multiple=False,hover=False,highlight=False,bindings={"toggle_visible": "h", "toggle_enabled": "e","left": "left","right": "right"})
multi = MultiCursor(fig.canvas, (ax1, ax2), color='g', lw=0.5, horizOn=True, vertOn=True)
ax2.legend(loc="upper left")
_process_event("__mouse_click__", ax1, (x[0], y[0]), 1)
sel, = c1.selections
c1.add_selection(copy.copy(sel))
_process_event("__mouse_click__", ax2, (x[0], z[0]), 1)
sel2, = c2.selections
c2.add_selection(copy.copy(sel2))
canvas.draw()
#pack canavs
canvas.get_tk_widget().pack(side = BOTTOM, fill=BOTH, expand=True)
#create Navigation Toolbar
toolbar = NavigationToolbar2Tk(canvas, root) #add to canvas
canvas._tkcanvas.pack(side = TOP, fill=BOTH, expand=True)
root.mainloop()
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 am trying to plot data on tkinter canvas using matplotlib "imshow()" function. When I am running the code the data is getting plotted onto the canvas and in the navigation toolbar pixel coordinates (x and y coordinates) are getting displayed along with pixel values (in bracket). Issue is I want to display only the pixel coordinates and hide pixel values which is getting displayed in the navigation toolbar.
The code which I am using is:
import tkinter
import numpy as np
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import tkinter
import numpy as np
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
root = tkinter.Tk()
fig = Figure(figsize=(5, 4), dpi=100)
fig.subplots_adjust(bottom=0, right=1, top=1, left=0, wspace=0, hspace=0)
ax = fig.add_subplot(111)
class Formatter(object):
def __init__(self, im):
self.im = im
def __call__(self, x, y):
return 'x={:.01f}, y={:.01f}'.format(x, y)
data = np.random.random((10,10))
im = ax.imshow(data, interpolation='none')
ax.format_coord = Formatter(im)
plt.show()
canvas1 = FigureCanvasTkAgg(fig, master=root)
canvas1.draw()
toolbar = NavigationToolbar2Tk(canvas1,root)
toolbar.update()
toolbar.pack(side=tkinter.TOP, fill=tkinter.X, padx=8)
canvas1.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1, padx=10, pady=5)
canvas1._tkcanvas.pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1, padx=10, pady=5)
root.mainloop()
Kindly suggest how to hide pixel values getting displayed in the navigation toolbar (inside brackets) and display only pixel coordinates (x & y coordinates).
The value in the navigation toolbar is created by the images' format_cursor_data method. You can replace that method to return an empty string.
im = ax.imshow(data, interpolation='none')
im.format_cursor_data = lambda e: ""
One way is to override the method mouse_move:
class Navigator(NavigationToolbar2Tk):
def mouse_move(self, event):
self._set_cursor(event)
if event.inaxes and event.inaxes.get_navigate():
try:
s = event.inaxes.format_coord(event.xdata, event.ydata)
self.set_message(s)
except (ValueError, OverflowError):
pass
else:
self.set_message(self.mode)
...
toolbar = Navigator(canvas1,root)
...