I have the following code, I want my GUI to change my two graphs everytime I press the button, but only the one in the left changes. Can someone please help me.
It's very strange because I took care of saving all the canvas and that way I can edit them with first using .forget() but for some reason it really doesn't work for the first graph.
import PySimpleGUI as sg
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
# VARS CONSTS:
# New figure and plot variables so we can manipulate them
_VARS = {'window': False,
'fig_agg1': False,
'pltFig1': False,
'fig_agg2': False,
'pltFig2': False}
dataSize = 1000 # For synthetic data
# Helper Functions
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 update_graph(zoom,or1,or2):
updateChart(1,zoom,or1,or2)
updateChart(2,zoom,or1,or2)
# \\ -------- PYSIMPLEGUI -------- //
AppFont = 'Any 16'
sg.theme('DarkTeal12')
#layout = [[sg.Canvas(key='figCanvas')],
# [sg.Button('Update', font=AppFont), sg.Button('Exit', font=AppFont)]]
control_col=sg.Column([
[sg.Frame('Zoom',layout = [[sg.Slider(range = (0,100), orientation = 'h', key = '-ZOOM-')]])],
[sg.Frame('Orientation 1',layout = [[sg.Slider(range = (-180,180), orientation = 'h', key = '-OR1-')]])],
[sg.Frame('Orientation 2',layout = [[sg.Slider(range = (-180,180), orientation = 'h', key = '-OR2-')]])],
[sg.Checkbox('Blackhole 1' , key = '-BH1-')],
[sg.Checkbox('Blackhole 2' , key = '-BH2-')],
[sg.Checkbox('Blackhole 3' , key = '-BH3-')],
[sg.Button('Show', key = '-SHOW-')]
])
graph1_col=sg.Column([[sg.Canvas(key = '-CANVAS1-')]])
graph2_col=sg.Column([[sg.Canvas(key = '-CANVAS2-')]])
layout=[[control_col,graph1_col,graph2_col]]
#_VARS['window'] = sg.Window('Such Window',
# layout,
# finalize=True,
# resizable=True,
# location=(100, 100),
# element_justification="right")
_VARS['window'] = sg.Window('Visualization', layout, finalize = True)
# \\ -------- PYSIMPLEGUI -------- //
# \\ -------- PYPLOT -------- //
def makeSynthData():
xData = np.random.randint(100, size=dataSize)
yData = np.random.randint(100, size=dataSize)
zData = np.random.randint(100, size=dataSize)
return (xData, yData, zData)
def drawChart(number):
_VARS['pltFig'+str(number)] = plt.figure()
dataXYZ = makeSynthData()
#plt.plot(dataXYZ[0], dataXYZ[1],dataXYZ[2] '.k')
ax = plt.axes(projection='3d')
ax.scatter3D(dataXYZ[0], dataXYZ[1], dataXYZ[2], c=dataXYZ[2], cmap='Greens')
_VARS['fig_agg'+str(number)] = draw_figure(
_VARS['window']['-CANVAS'+str(number)+'-'].TKCanvas, _VARS['pltFig'+str(number)])
# Recreate Synthetic data, clear existing figre and redraw plot.
def updateChart(number,zoom,or1,or2):
_VARS['fig_agg'+str(number)].get_tk_widget().forget()
dataXYZ = makeSynthData()
# plt.cla()
plt.clf()
#plt.plot(dataXYZ[0], dataXYZ[1], '.k')
ax = plt.axes(projection='3d')
ax.scatter3D(dataXYZ[0], dataXYZ[1], dataXYZ[2], c=dataXYZ[2], cmap='Greens')
ax.view_init(or1, or2)
_VARS['fig_agg'+str(number)] = draw_figure(
_VARS['window']['-CANVAS'+str(number)+'-'].TKCanvas, _VARS['pltFig'+str(number)])
# \\ -------- PYPLOT -------- //
drawChart(1)
drawChart(2)
# MAIN LOOP
while True:
event, values = _VARS['window'].read(timeout = 50)
if event == sg.WIN_CLOSED:
break
if event == '-SHOW-':
print(values)
update_graph(
values['-ZOOM-'],
values['-OR1-'],
values['-OR2-'])
_VARS['window'].close()
For plt.figure
Option num: A unique identifier for the figure.
If a figure with that identifier already exists, this figure is made active and returned.
Following code set which figure activated to draw
def drawChart(number):
_VARS['pltFig'+str(number)] = plt.figure()
After
drawChart(1)
drawChart(2)
Active figure set to figure 2 and not been changed until script end.
Try to update following statement in your code.
def drawChart(number):
_VARS['pltFig'+str(number)] = plt.figure(num=number)
def updateChart(number,zoom,or1,or2):
_VARS['fig_agg'+str(number)].get_tk_widget().forget()
dataXYZ = makeSynthData()
plt.figure(num=number)
Related
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 new to programming, and I tried to find the solution to related questions here, but it get me nowhere and I'm starting to bang the walls with my head now.
The problem is the following: I need to create a program with GUI for a university project. The idea is that I take data from big datasets, then a user can input a country name, and data for the selected countries will form a plot. I have 2 plots with different data I want to use, and I have 2 separate buttons for each type:
```
#function that graphs the first plot
def vaccine_cases_plot(vaccine_list, cases_list):
plt.scatter(
y = vaccine_list,
x = cases_list)
plt.ylabel("Percentage of fully vaccinated people")
plt.xlabel("Number of new cases per Million as of 01.12.2021")
plt.ioff()
return plt.gcf()
#function that graphs the second plot
def vaccine_gdp_plot(vaccine_list, gdp_list):
plt.scatter(
y = vaccine_list,
x = gdp_list)
plt.ylabel("Percentage of fully vaccinated people")
plt.xlabel("GDP per capita in 2019(USD)")
plt.ioff()
return plt.gcf()
#helper function to display plot on canvas
plt.gcf().subplots_adjust(left=0.15)
matplotlib.use("TkAgg")
def draw_figure(canvas, figure):
figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
figure_canvas_agg.get_tk_widget().pack(side="right", fill="both", expand=1)
figure_canvas_agg.draw()
return figure_canvas_agg
#empty lists where data for countries, selected by user, will be stored
vaccines_to_plot = []
cases_to_plot = []
gdp_to_plot = []
#pysimplegui interface layout
interface_column = [
[
sg.Text("Select a countries you would like to see on the graph:", font=("Arial", 14))
],
[
sg.In(size =(25, 1), enable_events=True, key="country_selected"),
sg.Button("Add country")
],
[
sg.Text("Selected countries are: ", size=(20,10), font=("Arial", 14), key = "selected_countries")
],
[
sg.Button("Clear selection", key = "clear")
],
[
sg.Button("GDP vs. Vaccination rate", key = "gdp_vaccine"),
sg.Button("Vaccination vs. new cases per day", key = "vaccine_cases")
],
[
sg.Text(font=("Arial", 12), key="warning_message")
]
]
graph_column = [
[sg.Canvas(size=(500,500), key="canvas")]
]
layout = [
[
sg.Column(interface_column),
sg.VSeperator(),
sg.Column(graph_column),
]
]
window = sg.Window("Vaxxi-nation", layout, margins=(50, 50), finalize=True)
canv = window["canvas"].TKCanvas
#main loop
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
#User inputs name of the country, respective data goes to the lists, name of the country is
displayed
if event == "Add country":
selected_country = values["country_selected"]
if selected_country in new_cases_per_m and selected_country in fully_vaccinated and
selected_country in countries_gdp:
try:
vaccines_to_plot.append(float(fully_vaccinated[selected_country]))
cases_to_plot.append(float(new_cases_per_m[selected_country]))
gdp_to_plot.append(round(float(countries_gdp[selected_country]), 2))
display = window["selected_countries"]
display.update(display.get() + selected_country + '\n')
except:
window["warning_message"].update("One of the datasets doesn't have data for this country")
else:
window["warning_message"].update("One of the datasets doesn't have data for this country")
#Button to graph the first plot
if event == "gdp_vaccine":
draw_figure(canv, vaccine_gdp_plot(vaccines_to_plot, gdp_to_plot))
#button to graph the second plot
if event == "vaccine_cases":
draw_figure(canv, vaccine_cases_plot(vaccines_to_plot, cases_to_plot))
if event == "clear":
display.update('')
vaccines_to_plot = []
cases_to_plot = []
gdp_to_plot = []
event, values = window.read()
window.close()
```
Now, when I press one of the buttons for the first time, the plot displays on canvas just as I want it to be. But if I'd like to display the other plot, instead of being rewritten over the previous one, it creates a new plot, replaces the old one with it, and makes a copy of a new plot on the right.
What I want to have, is when a new button is pressed (or the old one is pressed again) the new plot should replace the old one. I looked up for many ways to do that, but I have a feeling that there should be something simple and obvious that I fail to see here. The last thing I tried and gave up after was to delete the canvas each time the button is pressed and then draw it anew. I tried to do it like this:
#first button is clicked
if event == "gdp_vaccine":
canv.TKCanvas.delete("all")
draw_figure(canv, vaccine_gdp_plot(vaccines_to_plot, gdp_to_plot))
But it literally did nothing (although no error was thrown). I will really appreciate any help here since I've been struggling with it for the whole day already and there's no way this thing should be that complicated.
Following code just to delete all items in sg.Canvas, not canvas of matplotlib figure in sg.Canvas.
canv.TKCanvas.delete("all")
It is not easy to read your long and incomplete code.
Here's my code to demo two graphs and redraw again and again, maybe it can help you.
import math
from matplotlib import use as use_agg
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
import PySimpleGUI as sg
def pack_figure(graph, figure):
canvas = FigureCanvasTkAgg(figure, graph.Widget)
plot_widget = canvas.get_tk_widget()
plot_widget.pack(side='top', fill='both', expand=1)
return plot_widget
def plot_figure(index, theta):
fig = plt.figure(index) # Active an existing figure
ax = plt.gca() # Get the current axes
x = [degree for degree in range(1080)]
y = [math.sin((degree+theta)/180*math.pi) for degree in range(1080)]
ax.cla() # Clear the current axes
ax.set_title(f"Sensor Data {index}")
ax.set_xlabel("X axis")
ax.set_ylabel("Y axis")
ax.set_xscale('log')
ax.grid()
plt.plot(x, y) # Plot y versus x as lines and/or markers
fig.canvas.draw() # Rendor figure into canvas
# Use Tkinter Agg
use_agg('TkAgg')
layout = [[sg.Graph((640, 480), (0, 0), (640, 480), key='Graph1'), sg.Graph((640, 480), (0, 0), (640, 480), key='Graph2')]]
window = sg.Window('Matplotlib', layout, finalize=True)
# Initial
graph1 = window['Graph1']
graph2 = window['Graph2']
plt.ioff() # Turn the interactive mode off
fig1 = plt.figure(1) # Create a new figure
ax1 = plt.subplot(111) # Add a subplot to the current figure.
fig2 = plt.figure(2) # Create a new figure
ax2 = plt.subplot(111) # Add a subplot to the current figure.
pack_figure(graph1, fig1) # Pack figure under graph
pack_figure(graph2, fig2)
theta1 = 0 # theta for fig1
theta2 = 90 # theta for fig2
plot_figure(1, theta1)
plot_figure(2, theta2)
while True:
event, values = window.read(timeout=10)
if event == sg.WINDOW_CLOSED:
break
elif event == sg.TIMEOUT_EVENT:
theta1 = (theta1 + 40) % 360
plot_figure(1, theta1)
theta2 = (theta2 + 40) % 260
plot_figure(2, theta2)
window.close()
I wanted to give my little program a nice GUI with PySimpleGui when I ran into the problem that after the PySimpleGui window was closed the show() function from matplotlib blocked, even though the window of the figure is closed.
Here is an example code which doesn't determinate:
import PySimpleGUI as sg
import matplotlib.pyplot as plt
sg.theme('DarkAmber')
layout = [ [sg.Text('Some text on Row 1')],
[sg.Text('Enter something on Row 2'), sg.InputText()],
[sg.Button('Ok'), sg.Button('Cancel')] ]
window = sg.Window('Window Title', layout)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED or event == 'Cancel': # if user closes window or clicks cancel
break
print('You entered ', values[0])
window.close()
plt.plot([1,2,3,4,5,6])
plt.show()
Yes, it also work on my platform.
WIN10, Python 3.9.5, PySimpleGUI 4.40.0.4, tkinter 8.6.9, Matplotlib 3.4.2
Following code show how I embed Matplotlib into PySimpleGUI.
import math
from matplotlib import use as use_agg
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
import PySimpleGUI as sg
def pack_figure(graph, figure):
canvas = FigureCanvasTkAgg(figure, graph.Widget)
plot_widget = canvas.get_tk_widget()
plot_widget.pack(side='top', fill='both', expand=1)
return plot_widget
def plot_figure(index, theta):
fig = plt.figure(index) # Active an existing figure
ax = plt.gca() # Get the current axes
x = [degree for degree in range(1080)]
y = [math.sin((degree+theta)/180*math.pi) for degree in range(1080)]
ax.cla() # Clear the current axes
ax.set_title("Sensor Data")
ax.set_xlabel("X axis")
ax.set_ylabel("Y axis")
ax.set_xscale('log')
ax.grid()
plt.plot(x, y) # Plot y versus x as lines and/or markers
fig.canvas.draw() # Rendor figure into canvas
# Use Tkinter Agg
use_agg('TkAgg')
Firsttab = [[sg.Graph((640, 480), (0, 0), (640, 480),key='Graph1')]]
Secondtab = [[sg.Graph((640, 480), (0, 0), (640, 480),key='Graph2')]]
tab_group_layout = [
[sg.Tab('test', Firsttab,font='Courier 15', key='FirstTAB')],
[sg.Tab('test2', Secondtab,font='Courier 15', key='SecondTAB')],
]
column_layout= [[sg.TabGroup(tab_group_layout, enable_events=True,key='TABGROUP')]]
# PySimplGUI window
layout = [[sg.Column(column_layout, visible = True, key='GRAPHPANEL')],
[sg.Button(button_text = 'Graph', key='Start')]]
window = sg.Window('Matplotlib', layout, finalize=True)
# Set Button to repeat
window['Start'].Widget.configure(repeatdelay=50, repeatinterval=50)
# Initial
graph1 = window['Graph1']
graph2 = window['Graph2']
plt.ioff() # Turn the interactive mode off
fig1 = plt.figure(1) # Create a new figure
ax1 = plt.subplot(111) # Add a subplot to the current figure.
fig2 = plt.figure(2) # Create a new figure
ax2 = plt.subplot(111) # Add a subplot to the current figure.
pack_figure(graph1, fig1) # Pack figure under graph
pack_figure(graph2, fig2)
theta1 = 0 # theta for fig1
theta2 = 0 # theta for fig2
index = 1 # Current Tab
plot_figure(1, theta1)
plot_figure(2, theta2)
flag1, flag2 = False, False
while True:
event, values = window.read(timeout=100)
if event == sg.WINDOW_CLOSED:
break
elif event == sg.TIMEOUT_KEY:
if index == 1 and flag1:
theta1 = (theta1 + 10) % 360
plot_figure(index, theta1)
elif index == 2 and flag2:
theta2 = (theta2 + 10) % 260
plot_figure(index, theta2)
elif event == 'Start':
print('Start', index)
if index == 1:
flag1 = not flag1
elif index == 2:
flag2 = not flag2
print(flag1, flag2)
elif event == 'TABGROUP':
index = 1 if values[event].startswith('First') else 2
window.close()
Your code works fine on my machine. After pressing cancel on the GUI, the plot shows:
I have been running this script:
from threading import Thread
import serial
import time
import collections
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import struct
import copy
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import tkinter as Tk
from tkinter.ttk import Frame
class serialPlot:
def __init__(self, serialPort='/dev/ttyACM0', serialBaud=38400, plotLength=100, dataNumBytes=2, numPlots=4):
self.port = serialPort
self.baud = serialBaud
self.plotMaxLength = plotLength
self.dataNumBytes = dataNumBytes
self.numPlots = numPlots
self.rawData = bytearray(numPlots * dataNumBytes)
self.dataType = None
if dataNumBytes == 2:
self.dataType = 'h' # 2 byte integer
elif dataNumBytes == 4:
self.dataType = 'f' # 4 byte float
self.data = []
self.privateData = None
for i in range(numPlots): # give an array for each type of data and store them in a list
self.data.append(collections.deque([0] * plotLength, maxlen=plotLength))
self.isRun = True
self.isReceiving = False
self.thread = None
self.plotTimer = 0
self.previousTimer = 0
# self.csvData = []
print('Trying to connect to: ' + str(serialPort) + ' at ' + str(serialBaud) + ' BAUD.')
try:
self.serialConnection = serial.Serial(serialPort, serialBaud, timeout=4)
print('Connected to ' + str(serialPort) + ' at ' + str(serialBaud) + ' BAUD.')
except:
print("Failed to connect with " + str(serialPort) + ' at ' + str(serialBaud) + ' BAUD.')
def readSerialStart(self):
if self.thread == None:
self.thread = Thread(target=self.backgroundThread)
self.thread.start()
# Block till we start receiving values
while self.isReceiving != True:
time.sleep(0.1)
def getSerialData(self, frame, lines, lineValueText, lineLabel, timeText, pltNumber):
if pltNumber == 0: # in order to make all the clocks show the same reading
currentTimer = time.perf_counter()
self.plotTimer = int((currentTimer - self.previousTimer) * 1000) # the first reading will be erroneous
self.previousTimer = currentTimer
self.privateData = copy.deepcopy(self.rawData) # so that the 3 values in our plots will be synchronized to the same sample time
timeText.set_text('' + str(self.plotTimer) + '')
data = self.privateData[(pltNumber*self.dataNumBytes):(self.dataNumBytes + pltNumber*self.dataNumBytes)]
value, = struct.unpack(self.dataType, data)
self.data[pltNumber].append(value) # we get the latest data point and append it to our array
lines.set_data(range(self.plotMaxLength), self.data[pltNumber])
lineValueText.set_text('[' + lineLabel + '] = ' + str(value))
def backgroundThread(self): # retrieve data
time.sleep(1.0) # give some buffer time for retrieving data
self.serialConnection.reset_input_buffer()
while (self.isRun):
self.serialConnection.readinto(self.rawData)
self.isReceiving = True
#print(self.rawData)
def sendSerialData(self, data):
self.serialConnection.write(data.encode('utf-8'))
def close(self):
self.isRun = False
self.thread.join()
self.serialConnection.close()
print('Disconnected...')
# df = pd.DataFrame(self.csvData)
# df.to_csv('/home/rikisenia/Desktop/data.csv')
class Window(Frame):
def __init__(self, figure, master, SerialReference):
Frame.__init__(self, master)
self.entry = []
self.setPoint = None
self.master = master # a reference to the master window
self.serialReference = SerialReference # keep a reference to our serial connection so that we can use it for bi-directional communicate from this class
self.initWindow(figure) # initialize the window with our settings
def initWindow(self, figure):
self.master.title("Haptic Feedback Grasping Controller")
canvas = FigureCanvasTkAgg(figure, master=self.master)
toolbar = NavigationToolbar2Tk(canvas, self.master)
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
# create out widgets in the master frame
lbl1 = Tk.Label(self.master, text="Distance")
lbl1.pack(padx=5, pady=5)
self.entry = Tk.Entry(self.master)
self.entry.insert(0, '0') # (index, string)
self.entry.pack(padx=5)
SendButton = Tk.Button(self.master, text='Send', command=self.sendFactorToMCU)
SendButton.pack(padx=5)
def sendFactorToMCU(self):
self.serialReference.sendSerialData(self.entry.get() + '%') # '%' is our ending marker
def main():
# portName = 'COM5'
portName = '/dev/ttyACM0'
baudRate = 38400
maxPlotLength = 100 # number of points in x-axis of real time plot
dataNumBytes = 4 # number of bytes of 1 data point
numPlots = 1 # number of plots in 1 graph
s = serialPlot(portName, baudRate, maxPlotLength, dataNumBytes, numPlots) # initializes all required variables
s.readSerialStart() # starts background thread
# plotting starts below
pltInterval = 50 # Period at which the plot animation updates [ms]
xmin = 0
xmax = maxPlotLength
ymin = -(1)
ymax = 200
fig = plt.figure()
ax = plt.axes(xlim=(xmin, xmax), ylim=(float(ymin - (ymax - ymin) / 10), float(ymax + (ymax - ymin) / 10)))
ax.set_title('Strain Gauge/ToF')
ax.set_xlabel("Time")
ax.set_ylabel("Force/Distance")
# put our plot onto Tkinter's GUI
root = Tk.Tk()
app = Window(fig, root, s)
lineLabel = ['W', 'X', 'Y', 'Z']
style = ['y-', 'r-', 'c-', 'b-'] # linestyles for the different plots
timeText = ax.text(0.70, 0.95, '', transform=ax.transAxes)
lines = []
lineValueText = []
for i in range(numPlots):
lines.append(ax.plot([], [], style[i], label=lineLabel[i])[0])
lineValueText.append(ax.text(0.70, 0.90-i*0.05, '', transform=ax.transAxes))
anim = animation.FuncAnimation(fig, s.getSerialData, fargs=(lines, lineValueText, lineLabel, timeText), interval=pltInterval) # fargs has to be a tuple
plt.legend(loc="upper left")
root.mainloop() # use this instead of plt.show() since we are encapsulating everything in Tkinter
s.close()
if __name__ == '__main__':
main()
A window shows up with no data passing through it even though I have 4 sensors that have data coming from an Arduino. The window contains 1 graph with 4 plots in it currently. I want 4 graphs each with one plot all in one window. I have been using https://thepoorengineer.com/en/python-gui/ as a reference to make graphs within python. The code for the data transfer is within the link as well. I tried to combine his 2 different codes and debugging it to make 4 graphs each with one plot to work with one Tkinter GUI window but it doesn't work. I also get an error of TypeError: getSerialData() missing 1 required positional argument: 'pltNumber' . Not sure why I get this error if pltNumber is in the parentheses in the code. I'm a beginner at python. What should I change to make the code work?
Script that can generate 4 separate graphs each with one plot that are not within a Tkinter GUI(works with 4 sensors but I need them within a Tkinter window):
from threading import Thread
import serial
import time
import collections
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import struct
import copy
class serialPlot:
def __init__(self, serialPort='/dev/ttyACM0', serialBaud=38400, plotLength=100, dataNumBytes=2, numPlots=1):
self.port = serialPort
self.baud = serialBaud
self.plotMaxLength = plotLength
self.dataNumBytes = dataNumBytes
self.numPlots = numPlots
self.rawData = bytearray(numPlots * dataNumBytes)
self.dataType = None
if dataNumBytes == 2:
self.dataType = 'h' # 2 byte integer
elif dataNumBytes == 4:
self.dataType = 'f' # 4 byte float
self.data = []
self.privateData = None # for storing a copy of the data so all plots are synchronized
for i in range(numPlots): # give an array for each type of data and store them in a list
self.data.append(collections.deque([0] * plotLength, maxlen=plotLength))
self.isRun = True
self.isReceiving = False
self.thread = None
self.plotTimer = 0
self.previousTimer = 0
print('Trying to connect to: ' + str(serialPort) + ' at ' + str(serialBaud) + ' BAUD.')
try:
self.serialConnection = serial.Serial(serialPort, serialBaud, timeout=4)
print('Connected to ' + str(serialPort) + ' at ' + str(serialBaud) + ' BAUD.')
except:
print("Failed to connect with " + str(serialPort) + ' at ' + str(serialBaud) + ' BAUD.')
def readSerialStart(self):
if self.thread == None:
self.thread = Thread(target=self.backgroundThread)
self.thread.start()
# Block till we start receiving values
while self.isReceiving != True:
time.sleep(0.1)
def getSerialData(self, frame, lines, lineValueText, lineLabel, timeText, pltNumber):
if pltNumber == 0: # in order to make all the clocks show the same reading
currentTimer = time.perf_counter()
self.plotTimer = int((currentTimer - self.previousTimer) * 1000) # the first reading will be erroneous
self.previousTimer = currentTimer
self.privateData = copy.deepcopy(self.rawData) # so that the 3 values in our plots will be synchronized to the same sample time
timeText.set_text('' + str(self.plotTimer) + '')
data = self.privateData[(pltNumber*self.dataNumBytes):(self.dataNumBytes + pltNumber*self.dataNumBytes)]
value, = struct.unpack(self.dataType, data)
self.data[pltNumber].append(value) # we get the latest data point and append it to our array
lines.set_data(range(self.plotMaxLength), self.data[pltNumber])
lineValueText.set_text('[' + lineLabel + '] = ' + str(value))
def backgroundThread(self): # retrieve data
time.sleep(1.0) # give some buffer time for retrieving data
self.serialConnection.reset_input_buffer()
while (self.isRun):
self.serialConnection.readinto(self.rawData)
self.isReceiving = True
def close(self):
self.isRun = False
self.thread.join()
self.serialConnection.close()
print('Disconnected...')
def makeFigure(xLimit, yLimit, title):
xmin, xmax = xLimit
ymin, ymax = yLimit
fig = plt.figure()
ax = plt.axes(xlim=(xmin, xmax), ylim=(int(ymin - (ymax - ymin) / 10), int(ymax + (ymax - ymin) / 10)))
ax.set_title(title)
ax.set_xlabel("Time")
ax.set_ylabel("Force/Distance")
return fig, ax
def main():
# portName = 'COM5'
portName = '/dev/ttyACM0'
baudRate = 38400
maxPlotLength = 100 # number of points in x-axis of real time plot
dataNumBytes = 4 # number of bytes of 1 data point
numPlots = 4 # number of plots in 1 graph
s = serialPlot(portName, baudRate, maxPlotLength, dataNumBytes, numPlots) # initializes all required variables
s.readSerialStart() # starts background thread
# plotting starts below
pltInterval = 50 # Period at which the plot animation updates [ms]
lineLabelText = ['W', 'X', 'Y', 'Z']
title = ['Strain Gauge 1 Force', 'Strain Gauge 2 Force', 'ToF 1 Distance', 'ToF 2 Distance']
xLimit = [(0, maxPlotLength), (0, maxPlotLength), (0, maxPlotLength), (0, maxPlotLength)]
yLimit = [(-1, 1), (-1, 1), (-1, 1), (-1, 1)]
style = ['y-', 'r-', 'g-', 'b-'] # linestyles for the different plots
anim = []
for i in range(numPlots):
fig, ax = makeFigure(xLimit[i], yLimit[i], title[i])
lines = ax.plot([], [], style[i], label=lineLabelText[i])[0]
timeText = ax.text(0.50, 0.95, '', transform=ax.transAxes)
lineValueText = ax.text(0.50, 0.90, '', transform=ax.transAxes)
anim.append(animation.FuncAnimation(fig, s.getSerialData, fargs=(lines, lineValueText, lineLabelText[i], timeText, i), interval=pltInterval)) # fargs has to be a tuple
plt.legend(loc="upper left")
plt.show()
s.close()
if __name__ == '__main__':
main()
I think something like this can be useful.
A similar issue was addressed in this post too.
Here we can use the backend class of matplotlib namely FigureCanvasTkAgg.
It works like a tkinter canvas but with the additional ability to be able to plot figures into it.
This means that we can initialize multiple matplotlib figures, plot graphs on them and then plot those figures onto the canvas.
This allows us to plot multiple graphs on the same tkinter window.
To import this class -:
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
Then a figure object of matplotlib can be used to plot a graph on the canvas like so -:
from matplotlib.figure import Figure
fig = Figure(...) # Initializing the figure object.
canvas = FigureCanvasTkAgg(fig, master=root) # Initializing the FigureCanvasTkAgg Class Object.
tk_canvas = canvas.get_tk_widget() # Getting the Figure canvas as a tkinter widget.
tk_canvas.pack() # Packing it into it's master window.
canvas.draw() # Drawing the canvas onto the screen.
Similarly multiple canvases can be initialized and packed into the tk window, thus giving multiple plotted graphs.
The plotting of the figure object can be done using matplotlib methods.
The full code for two such figures will become -:
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
# FOR FIRST GRAPH
fig = Figure(...) # Initializing the figure object.
canvas = FigureCanvasTkAgg(fig, master=root) # Initializing the FigureCanvasTkAgg Class Object.
tk_canvas = canvas.get_tk_widget() # Getting the Figure canvas as a tkinter widget.
tk_canvas.pack() # Packing it into it's master window.
canvas.draw() # Drawing the canvas onto the screen.
# FOR SECOND GRAPH
fig_2 = Figure(...) # Initializing the second figure object.
canvas_2 = FigureCanvasTkAgg(fig_2, master=root) # Initializing the second FigureCanvasTkAgg Class Object.
tk_canvas_2 = canvas_2.get_tk_widget() # Getting the second Figure canvas as a tkinter widget.
tk_canvas_2.pack() # Packing it into it's master window.
canvas_2.draw() # Drawing the second canvas onto the screen.
# CAN BE REPEATED FOR MULTIPLE SUCH GRAPHS....
EDIT: Misunderstood question, but will still leave this here as it'll be useful for you in terms of manipulating the graphs
Here is some code that I use to generate 5 listboxes, and append them to a dictionary so I can reference them later in the code.
self.listboxes = []
for i in range(5):
self.lb = tk.Listbox(self.modeSelect)
self.lb.configure(background='#2f2a2d', exportselection='false', font='{Arial} 12 {}', foreground='#feffff', height='23')
self.lb.configure(relief='flat', width='9', justify='center', selectbackground='#feffff', selectforeground='#000000')
self.lb.pack(side='left')
self.listboxes.append(self.lb)
self.lb.bind("<Double-1>", self.getIndexLB)
You can then assign functions or call attributes by using
self.listboxes[0].get() #for example
That way you can assign points to each graph, it will also allow you to control all the graphs simultaneously by doing something like:
for child in self.modeSelect.winfo_children():
if isinstance(child, tk.Listbox):
child.delete(index)
how do i delete data from a figure when it is used in tktinter via FigureCanvasTkAgg?
I have tried to clear the figure with cla, clf and to delete the canvas with .delete, .clear.
My code: (If you have any advise how i can improve my code let me now. This is my first project in coding)
import tkinter as tk
from tkinter import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
#createplot
fig_CDF = Figure(figsize = (6, 5), facecolor = "white")
axis_CDF = fig_CDF.add_subplot(111)
canvas_CDF = FigureCanvasTkAgg(fig_CDF, master = window_main)
canvas_CDF._tkcanvas.pack(side = tk.TOP, fill = tk.BOTH, expand = 1)
canvas_CDF.get_tk_widget().place(x=400,y=50)
#plotdata
axis_CDF.plot(datax, datay, label = "data",marker=".", linestyle = "")
canvas_CDF.draw()
Thanks for your help!
In this code I added three buttons which shows three methods - all of them need canvas_CDF.draw() to redraw element in window.
first replaces data in plot and it removes points from chart but it still show axes.
second clears axis - so it remove data and set axis to 0..1
third clears figure - it removes even axis.
First version needs
plot = axis_CDF.plot(...)
to have access to data.
I use random only to create some data.
import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import random
# --- functions ---
def replace_data():
datax = []
datay = []
#datax = range(10)
#datay = [x/10 for x in random.sample(range(10), 10)]
plot[0].set_data(datax, datay)
canvas_CDF.draw()
def clear_axis():
#axis_CDF.clear()
axis_CDF.cla()
canvas_CDF.draw()
def clear_figure():
fig_CDF.clear()
#fig_CDF.clf()
canvas_CDF.draw()
# --- main ---
window_main = tk.Tk()
datax = range(10)
datay = [x/10 for x in random.sample(range(10), 10)]
# create plot
fig_CDF = Figure(figsize=(6, 5), facecolor="white")
axis_CDF = fig_CDF.add_subplot(111)
canvas_CDF = FigureCanvasTkAgg(fig_CDF, master=window_main)
canvas_CDF._tkcanvas.pack(side='top', fill='both', expand=True)
canvas_CDF.get_tk_widget().pack()
# plot data
plot = axis_CDF.plot(datax, datay, label="data", marker=".", linestyle="")
canvas_CDF.draw()
# buttons
b = tk.Button(window_main, text='Replace data', command=replace_data)
b.pack()
b = tk.Button(window_main, text='Clear axis', command=clear_axis)
b.pack()
b = tk.Button(window_main, text='Clear figure', command=clear_figure)
b.pack()
window_main.mainloop()