How to change width of a plot while zooming with tkinter toolbar - python

I have a problem with the zoom of the NavigationToolbar2Tk from tkinter. I set a fixed width=200 for my plot. When I zoom into the plot, this width remains (logical). The problem is, when I zoom in, the bars with a width=200 are too big and the plot becomes confusing when two bars are side by side or behind each other. How can I change the width=10 when zooming is activ?
My previous code is below:
# ___________________________________________________________________________
# Library
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import tkinter as tk
import numpy as np
# ___________________________________________________________________________
# Maintenance Array
Maintenance_km = np.array([0,3,400,400,1700,1850,1600,4,1000,1000,2000,3000,3040,3040,80000,80090])
Maintenance_cost = np.array([4,500,500,1000,2000,2040,2040,10,40,500,2400,2700,2850,2600,3000,3150])
# ___________________________________________________________________________
# Main
Vis = tk.Tk()
Vis.title("Main") # titel
# ___________________________________________________________________________
# Plot
fig, ax = plt.subplots(1, 1, figsize=(20,5), facecolor = "white")
Plot_Maintenace_cost2 = plt.bar(Maintenance_km, Maintenance_cost,
bottom=-0,
color="#C5E0B4",
ec="black",
width=200,
label="Maintenance_cost")
ax.spines["bottom"].set_position("zero")
ax.spines["top"].set_color("none")
ax.spines["right"].set_color("none")
ax.spines["left"].set_color("none")
ax.tick_params(axis="x", length=20)
_, xmax = ax.get_xlim()
ymin, ymax = ax.get_ylim()
ax.set_xlim(-15, xmax)
ax.set_ylim(ymin, ymax+10) # legend
ax.text(xmax, -5, "km", ha="right", va="top", size=14)
plt.legend(ncol=5, loc="upper left")
plt.tight_layout()
# ___________________________________________________________________________
# Canvas, Toolbar
canvas = FigureCanvasTkAgg(fig, master=Vis)
canvas.draw() # TK-Drawingarea
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
canvas._tkcanvas.pack(side = tk.TOP, fill = tk.BOTH, expand = True)
toolbar = NavigationToolbar2Tk(canvas, Vis)
toolbar.update()
Vis.mainloop()

You could updated the plot when the limits of the x-axis change (like in this question)
def on_xlims_change(axes):
# Get the current limits of the x axis
x_lim = ax.get_xlim()
x_width = x_lim[1] - x_lim[0]
# Just an example, you can use your own logic here
new_width = x_width / 200 # Ensure a fixed ratio of xaxis to bar width
# new_width = 200 if x_width > 10000 else 10
# Update all bars
for b in Plot_Maintenace_cost2:
b.set_width(new_width)
ax.callbacks.connect('xlim_changed', on_xlims_change)

Related

MultiCursor in matplotlib over multiple subplot does not work

i am developing a GUI application for which there will be multiple dynamic subplots for which i would like to have a cursor integration for which is used "Matplotlib's MultiCursor widget" the code seems to be all good without any errors but the cursor alone is not being displayed on the screen
The below is a small snippet function which iam currently using
import numpy as np
import PySimpleGUI as sg
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
NavigationToolbar2Tk)
from matplotlib.widgets import Cursor
from matplotlib.widgets import MultiCursor
from matplotlib.gridspec import GridSpec
from matplotlib.figure import Figure
class Toolbar(NavigationToolbar2Tk):
def __init__(self, *args, **kwargs):
super(Toolbar, self).__init__(*args, **kwargs)
def repack(widget, option):
pack_info = widget.pack_info()
pack_info.update(option)
widget.pack(**pack_info)
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_idle()
toolbar = Toolbar(figure_canvas_agg, canvas_toolbar)
toolbar.update()
figure_canvas_agg.get_tk_widget().pack(side='right', fill='both', expand=1)
layout=[
[
sg.Frame(
'Controls',
expand_x=True,
layout=[[
sg.Canvas(key='controls_cv')
]])
],
[
sg.Canvas(
key='fig_cv',
size=(1024, 525), #(W,H)
expand_x=True,
expand_y=True,
background_color='black')
],
]
window = sg.Window('Data Visualization Tool',
layout=layout,
location=(0, 0),
resizable=True,
margins=(0, 0),
finalize=True)
fig = Figure()
def get_data_ax(x_data, y_data, y_label, marker_sym=None):
ax = fig.add_subplot()
lines = ax.step(
x_data,
y_data,
marker=marker_sym,
where='post')
return ax, lines
cursor_handle_list = []
axes_handle_list = []
def on_move(event, flag=False, c_flag=False):
global cursor_present_flag, cursor_handle_list, axes_handle_list
if event.dblclick:
def change_cursor_position(handle_obj):
for handles in handle_obj:
handles.set_xdata(float(event.xdata))
if event.inaxes is not None:
ax = event.inaxes
if ax not in axes_handle_list:
cursor_handle = ax.axvline(event.xdata, color ='red', lw = .75, alpha = 0.65, visible=True)
cursor_handle_list.append(cursor_handle)
axes_handle_list.append(ax)
else:
change_cursor_position(cursor_handle_list)
draw_figure_w_toolbar(window['fig_cv'].TKCanvas, fig,
window['controls_cv'].TKCanvas)
window.refresh()
fig.canvas.mpl_connect("button_press_event", on_move)
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
num_rows = 2
gs = GridSpec(num_rows, 1, hspace=0)
ax0, lines = get_data_ax(t, s, 'test')
ax0.set_subplotspec(gs[0])
fig.add_subplot(ax0)
ax, lines = get_data_ax(t, s, 'test')
ax.set_subplotspec(gs[1])
fig.add_subplot(ax)
ax.get_shared_x_axes().join(ax0, ax)
graph = FigureCanvasTkAgg(fig, master=window['fig_cv'].TKCanvas)
cursor_multi = MultiCursor(graph, [ax,], color='r', lw=2.0, vertOn=True)
graph.draw()
draw_figure_w_toolbar(window['fig_cv'].TKCanvas, fig,
window['controls_cv'].TKCanvas)
while True:
event, values = window.read()
if event in (sg.WIN_CLOSED, 'Exit'):
break
I tried using axvline with matplotlib events but the results were not satisfactory
i saw somewhere that defining the return value of MultiCursor as gloabl should fix the issue but that did not happen in my case
next i tried simple putting plt.var_name as a variable for catching the returned object from MultiCursor but even this did not yeild me any result
Note: The code with mpl_connect and on_move callback with axvline integration were just an workaround i was trying as MultiCursor was not working you can discard that part if not required
It's because you created the MultiCursor widget before you created the FigureCanvasTkAgg for the axes objects. So do like this.
draw_figure_w_toolbar(window['fig_cv'].TKCanvas, ...)
cursor_multi = MultiCursor(None, [ax,], ...)
Also note that the first argument of the MultiCursor() is recommended to be set to None because it is ignored. Also your code contain many problems, such as an unused object graph.

How switch between two graphs and maintain radio button and slider updates working?

I made a graph viewer GUI program in Python using Tkinter and matplotlib, where I switch between two graphs.
I have three problems I don't know how to fix:
Can't change the radiobutton after I move the slider it stops updating.
Can't change radiobutton after I switch the graph.
I would like to switch between graphs with 1 subplot and 2 subplots, but when I switch to graph with 2 subplots with slider and radiobar I can't move back to first.
I think the problem might be in the way I update the slider and radiobutton
Here is the code:
import matplotlib
import tkinter as Tk
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
import numpy as np
from tkinter import *
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.backend_bases import key_press_handler
from matplotlib.widgets import Slider, RadioButtons
# Seperated out config of plot to just do it once
def config_plot():
fig, ax = plt.subplots()
ax.set(xlabel='time (s)', ylabel='voltage (mV)',
title='Graph One')
return (fig, ax)
class matplotlibSwitchGraphs:
def __init__(self, master):
self.master = master
self.frame = Frame(self.master)
self.fig, self.ax = config_plot()
self.graphIndex = 0
self.canvas = FigureCanvasTkAgg(self.fig, self.master)
self.config_window()
self.draw_graph_one()
self.frame.pack(expand=YES, fill=BOTH)
def config_window(self):
self.canvas.mpl_connect("key_press_event", self.on_key_press)
toolbar = NavigationToolbar2Tk(self.canvas, self.master)
toolbar.update()
self.canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1)
self.button = Button(self.master, text="Quit", command=self._quit)
self.button.pack(side=BOTTOM)
self.button_switch = Button(self.master, text="Switch Graphs", command=self.switch_graphs)
self.button_switch.pack(side=BOTTOM)
def plot_data(self):
def func3(x, y):
return (1 - x / 2 + x ** 5 + y ** 3) * np.exp(-(x ** 2 + y ** 2))
dx, dy = 0.05, 0.05
x = np.arange(-3.0, 3.0, dx)
y = np.arange(-3.0, 3.0, dy)
X, Y = np.meshgrid(x, y)
self.extent = np.min(x), np.max(x), np.min(y), np.max(y)
self.Z1 = np.add.outer(range(8), range(8)) % 2 # chessboard
self.Z2 = func3(X, Y)
def draw_graph_one(self):
self.plot_data()
self.ax.remove()
self.ax = plt.imshow(self.Z2, cmap=plt.cm.viridis, alpha=.9, interpolation='bilinear',
extent=self.extent)
self.canvas.draw()
def draw_graph_two(self):
self.plot_data()
self.ax.remove()
self.ax = plt.subplot(1, 2, 1)
self.ax = plt.imshow(self.Z1, cmap=plt.cm.gray, interpolation='nearest',
extent=self.extent)
self.ax = plt.subplot(1, 2, 2)
self.a = plt.imshow(self.Z2, cmap=plt.cm.viridis, alpha=.9, interpolation='bilinear',
extent=self.extent)
self.b = plt.imshow(self.Z1, cmap=plt.cm.gray, interpolation='nearest',
extent=self.extent)
self.canvas.draw()
plt.subplots_adjust(left=0.15, bottom=0.15)
slider_ax = plt.axes([0.06, 0.25, 0.0225, 0.5])
alfa_slider = Slider(slider_ax,
label="Transparency",
valmin=0,
valmax=1,
valinit=0,
orientation="vertical"
)
alfa_slider.on_changed(self.update_slider)
rax = plt.axes([0.06, 0.05, 0.15, 0.15])
radio = RadioButtons(rax, ('Reds', 'Greens', 'Blues', 'Oranges', 'Wistia', 'plasma', 'inferno'), active=0)
radio.on_clicked(self.update_radio)
self.canvas.draw()
def update_slider(self,alpha_):
self.b.set_alpha(alpha_)
self.canvas.draw()
def update_radio(self,label_):
self.b.set_cmap(label_)
self.canvas.draw()
def on_key_press(self,event,toolbar):
print("you pressed {}".format(event.key))
key_press_handler(event, self.canvas, toolbar)
def _quit(self):
self.master.quit() # stops mainloop
def switch_graphs(self):
# Need to call the correct draw, whether we're on graph one or two
self.graphIndex = (self.graphIndex + 1) % 2
if self.graphIndex == 0:
self.draw_graph_one()
else:
self.draw_graph_two()
def main():
root = Tk()
matplotlibSwitchGraphs(root)
root.mainloop()
if __name__ == '__main__':
main()
I tried to input update_slider and update_radio function inside of function, but than I cant use self.canvas.draw().
Also .remove() and .cla() doesn't clear the window.
Here is the view of the graphs I would like to switch between:
Answering your last question first, your big issue is that you are trying to use one figure to display two seperate graphs, without understanding how pyplot actually works. It is possible to do so, as I will outline below, but you lose any modifications you make to the graphs when you switch between them. I would recommend reading the working with multiple axes and figures section of the pyplot tutorial. The key takeaway as that in MATLAB and pyplot, all plotting functions are applied to the last figure and axes.
When you call self.draw_graph_two(), the last axes you create are the RadioButtons. Therefore, pyplot has these axes as the current axes. When you then call self.draw_graph_one() for the second time (the first time was when you intialised), it draws the plot on the current figure and current axes, where the RadioButtons are.
To solve this, call plt.clf() as the first line of draw_graph_one and draw_graph_two, before attempting to draw the new graph. This clears the figure, allowing for a new plot to be drawn from scratch (which is what your two draw functions do). This allows you to continuously switch between your two plots, although each time you switch the plot is cleared and any modifications to the plot are lost. You can also remove the self.ax.remove() lines, as they cause errors and are not needed.
For your first two questions, these can be answered by reading the documentation for the RadioButtons and Slider here. You must keep a reference to the widgets for them to remain responsive. So modifying your draw_graph_two to return alfa_slider and radio keeps these widgets responsive. Alternatively, declaring them as instance variables also keeps a reference (self.alfa_slider and self.radio).
So your working graph viewer is:
import matplotlib
import tkinter as tk
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.backend_bases import key_press_handler
from matplotlib.widgets import Slider, RadioButtons
# Seperated out config of plot to just do it once
def config_plot():
fig, ax = plt.subplots()
ax.set(xlabel='time (s)', ylabel='voltage (mV)',
title='Graph One')
return (fig, ax)
class matplotlibSwitchGraphs:
def __init__(self, master):
self.master = master
self.frame = tk.Frame(self.master)
self.fig, self.ax = config_plot()
self.graphIndex = 0
self.canvas = FigureCanvasTkAgg(self.fig, self.master)
self.config_window()
self.plot_data()
self.draw_graph_one()
self.frame.pack(expand=True, fill=tk.BOTH)
def config_window(self):
self.canvas.mpl_connect("key_press_event", self.on_key_press)
toolbar = NavigationToolbar2Tk(self.canvas, self.master)
toolbar.update()
self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.button = tk.Button(self.master, text="Quit", command=self._quit)
self.button.pack(side=tk.BOTTOM)
self.button_switch = tk.Button(self.master, text="Switch Graphs", command=self.switch_graphs)
self.button_switch.pack(side=tk.BOTTOM)
def plot_data(self):
def func3(x, y):
return (1 - x / 2 + x ** 5 + y ** 3) * np.exp(-(x ** 2 + y ** 2))
dx, dy = 0.05, 0.05
x = np.arange(-3.0, 3.0, dx)
y = np.arange(-3.0, 3.0, dy)
X, Y = np.meshgrid(x, y)
self.extent = np.min(x), np.max(x), np.min(y), np.max(y)
self.Z1 = np.add.outer(range(8), range(8)) % 2 # chessboard
self.Z2 = func3(X, Y)
def draw_graph_one(self):
plt.clf()
self.ax = plt.imshow(self.Z2, cmap=plt.cm.viridis, alpha=.9, interpolation='bilinear',
extent=self.extent)
self.canvas.draw()
def draw_graph_two(self):
plt.clf()
self.ax = plt.subplot(1, 2, 1)
self.ax = plt.imshow(self.Z1, cmap=plt.cm.gray, interpolation='nearest',
extent=self.extent)
self.ax = plt.subplot(1, 2, 2)
self.a = plt.imshow(self.Z2, cmap=plt.cm.viridis, alpha=.9, interpolation='bilinear',
extent=self.extent)
self.b = plt.imshow(self.Z1, cmap=plt.cm.gray, interpolation='nearest',
extent=self.extent)
self.canvas.draw()
plt.subplots_adjust(left=0.15, bottom=0.15)
slider_ax = plt.axes([0.06, 0.25, 0.0225, 0.5])
self.alfa_slider = Slider(slider_ax,
label="Transparency",
valmin=0,
valmax=1,
valinit=0,
orientation="vertical"
)
self.alfa_slider.on_changed(self.update_slider)
rax = plt.axes([0.06, 0.05, 0.15, 0.15])
self.radio = RadioButtons(rax, ('Reds', 'Greens', 'Blues', 'Oranges', 'Wistia', 'plasma', 'inferno'), active=0)
self.radio.on_clicked(self.update_radio)
self.canvas.draw()
def update_slider(self,alpha_):
self.b.set_alpha(1-alpha_)
self.canvas.draw()
def update_radio(self,label_):
self.b.set_cmap(label_)
self.canvas.draw()
def on_key_press(self,event,toolbar):
print("you pressed {}".format(event.key))
key_press_handler(event, self.canvas, toolbar)
def _quit(self):
self.master.quit() # stops mainloop
def switch_graphs(self):
# Need to call the correct draw, whether we're on graph one or two
self.graphIndex = (self.graphIndex + 1) % 2
if self.graphIndex == 0:
self.draw_graph_one()
else:
self.draw_graph_two()
def main():
root = tk.Tk()
matplotlibSwitchGraphs(root)
root.mainloop()
if __name__ == '__main__':
main()
Note I also made some other slight modifications. Don't use from tkinter import *, you had already imported it above and using import * is a bad habit. I reversed the slider so that the transparency makes sense. self.plot_data() is only called once in __init__ as it doesn't change.

PySimpleGui how to make plot in canvas without it appearing also in seperate window

Problem: My plot appears on the canvas inside the window, but also a separate window figure gets created with the plot.
I would like remove that extra window, since it seems that it is taking resources and time and maybe makes the system occasionally to stop responding.
Help would be appreciated, here is a sample of my code that reproduces the problem.
Code sample:
import matplotlib.pyplot as plt
from os import path
import matplotlib
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
matplotlib.use("TkAgg")
import pickle
from numpy import arange
import PySimpleGUI as sg
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)
class Toolbar(NavigationToolbar2Tk):
def __init__(self, *args, **kwargs):
super(Toolbar, self).__init__(*args, **kwargs)
data_folder = ''
filename = 'plot_data.p'
[t0,p0, t_filt, p_filt, band,t,p] = pickle.load(open(path.join(data_folder, path.basename(filename)),'rb'))
plot_column = [
[
sg.Button(enable_events=True, key="Plot",button_text="Plot")],
[sg.T('Controls:')],
[sg.Canvas(key='controls_cv')],
[sg.T('Figure:')],
[sg.Column(
layout=[
[sg.Canvas(key='canvas',
# it's important that you set this size
size=(400 * 2, 600)
)]
],
background_color='#DAE0E6',
pad=(0, 0)
)],]
layout_Main_window = [
[
sg.Column(plot_column),
]
]
window = sg.Window("Click detection", layout_Main_window, finalize=True, location=(30, 80))
while True:
event, values = window.read()
if event == "Exit" or event == sg.WIN_CLOSED:
break
# --------- Data folder ---------
elif event == "Plot":
fig = plt.figure(figsize=(11, 10))
plt.subplot(2,1,1)
plt.title('Original signals')
plt.plot(t0, p0,'.', label='Original signal', zorder = 0)
plt.plot(t_filt, p_filt, label='filtered ['+str(band[0])+','+str(band[1])+']', zorder = 5)
plt.legend(loc='best')
plt.ylabel('Intensity')
x_axis1 = [round(min(min(t0),min(t_filt)),1), round(max(max(t0),max(t_filt)),1)]
plt.gca().set_xlim(x_axis1)
plt.grid()
plt.subplot(2,1,2)
plt.title('Pen + equipment signal')
plt.plot(t, p, color='#ff7f0e', label='clicking period')
plt.legend(loc='best')
plt.ylabel('relative intensity')
plt.gca().set_xlim(x_axis1)
plt.xticks(arange(x_axis1[0], x_axis1[1], 0.5))
plt.xlabel('time (s)')
plt.grid()
plot_name = path.join(data_folder, path.basename(filename.replace('.p', '.png')))
plt.savefig(data_folder)
draw_figure_w_toolbar(window['canvas'].TKCanvas, fig, window['controls_cv'].TKCanvas)
fig = plt.figure(figsize=(11, 10))
should be changed to:
plt.ioff()
plt.figure(figsize=(11, 10))
fig = plt.gcf()

Why are my plots not appearing with set_data using Tkinter?

I am trying to improve my plotting function. I want to plot data using my plotGraph function coming from an EEG board in real-time, pulling samples from an LSL # 250Hz. Previously, I had a functional version using the regular self.ax.plot(x,y), clearing the data with self.ax.clear() every time the plot needed to refresh. Nonetheless, some profiling showed that my code was taking way too much time to plot in comparison to the rest of it.
One of the suggestions I got was to use set_data instead of plot and clear. I have multiple lines of data that I want to plot simultaneously, so I tried following Matplotlib multiple animate multiple lines, which you can see below (adapted code). Also, I was told to use self.figure.canvas.draw_idle(), which I tried, but I'm not sure if I did it correctly.
Unfortunately, it didn't work, the graph is not updating and I can't seem to find why. I'm aware that the source I just mentioned uses animation.FuncAnimation but I'm not sure that would be the problem. Is it?
Any ideas of why none of my lines are showing in my canvas' graph?
import tkinter as tk
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
class AppWindow:
def plotGraph(self, x, y):
for lnum,line in enumerate(self.lines):
line.set_data(x[:], y[:, lnum])
self.figure.canvas.draw_idle()
plt.ylabel('Magnitude', fontsize = 9, color = tx_color)
plt.xlabel('Freq', fontsize = 9, color = tx_color)
self.figure.canvas.draw()
def __init__(self):
self.root = tk.Tk() #start of application
self.canvas = tk.Canvas(self.root, height = 420, width = 780, bg =
bg_color, highlightthickness=0)
self.canvas.pack(fill = 'both', expand = True)
self.figure = plt.figure(figsize = (5,6), dpi = 100)
self.figure.patch.set_facecolor(sc_color)
self.ax = self.figure.add_subplot(111)
self.ax.clear()
self.line, = self.ax.plot([], [], lw=1, color = tx_color)
self.line.set_data([],[])
#place graph
self.chart_type = FigureCanvasTkAgg(self.figure, self.canvas)
self.chart_type.get_tk_widget().pack()
self.lines = []
numchan = 8 #let's say I have 8 channels
for index in range(numchan):
lobj = self.ax.plot([],[], lw=2, color=tx_color)[0]
self.lines.append(lobj)
for line in self.lines:
line.set_data([],[])
def start(self):
self.root.mainloop()
You chart is empty because you are plotting empty arrays:
line.set_data([],[])
If you fill in the line arrays, the chart plots correctly.
Try this code. It updates the chart with new random data every second.
import tkinter as tk
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
import random
bg_color='grey'
tx_color='green'
sc_color='linen'
numchan = 8
chlen = 100
xvals=[(x-40)/20 for x in range(chlen)] # X coordinates
chcolors= ['gold','blue','green','maroon','red','brown','purple','cyan']
class AppWindow:
def plotGraph(self):
self.figure.canvas.draw_idle()
plt.ylabel('Magnitude', fontsize = 9, color = tx_color)
plt.xlabel('Freq', fontsize = 9, color = tx_color)
self.figure.canvas.draw()
def UpdateChannelData(self): # callback with new data
# fake random data
for i,ch in enumerate(self.chdata):
for p in range(len(ch)):
ch[p] += (random.random()-.5)/100
self.lines[i].set_data(xvals, ch)
self.plotGraph()
self.root.after(100, self.UpdateChannelData) # simulate next call
def __init__(self):
global chzero
self.root = tk.Tk() #start of application
self.canvas = tk.Canvas(self.root, height = 420, width = 780, bg = bg_color, highlightthickness=0)
self.canvas.pack(fill = 'both', expand = True)
self.figure = plt.figure(figsize = (5,6), dpi = 100)
self.figure.patch.set_facecolor(sc_color)
self.ax = self.figure.add_subplot(111)
self.ax.clear()
self.line, = self.ax.plot([], [], lw=1, color = tx_color)
self.line.set_data([],[])
#place graph
self.chart_type = FigureCanvasTkAgg(self.figure, self.canvas)
self.chart_type.get_tk_widget().pack()
self.lines = []
#numchan = 8 #let's say I have 8 channels
for index in range(numchan):
lobj = self.ax.plot([],[], lw=1, color=chcolors[index])[0]
self.lines.append(lobj)
# set flat data
self.chdata = [[0 for x in range(chlen)] for ch in range(numchan)]
self.root.after(1000, self.UpdateChannelData) # start data read
def start(self):
self.root.mainloop()
AppWindow().start()
Output:

How do I change the subplot parameters having a Figure in a window in Tkinter ? As for example I want to add the xlabel and ylabel

I have an application that should get data from a sensor into a live graph, a subplot that is added into a Figure.
I have now a problem after adding the subplot that I don't know how to change the plot parameters as xlabel, ylabel. This works if I import plt, but not if I import a Figure that will be further added to the window in Tkinter.
#file livegraph.py
import matplotlib.animation as animation
import datetime
#this is a draft for the liveGraph class
#the objective is to get live data from a sensor
class liveGraph:
#by default define the interval as being 1000 mSec
intervalAnim = 1000
def __init__(self,fig):
self.xax = 0
self.xs = []
self.ys = []
self.ax = fig.add_subplot(111)
self.ax.set_xlabel('teeeest')
#fig.title('Graph test')
#fig.set_xlabel("Time")
#fig.ylabel("% SMS")
self.anim = animation.FuncAnimation(fig, self.animate, interval = self.intervalAnim)
def animate(self,i):
self.xs.append(self.xax)
self.ys.append(datetime.datetime.now().second)
self.xax+=1
self.ax.clear()
self.ax.plot(self.xs,self.ys)
if self.xax > 90:
self.anim.event_source.stop()
from tkinter import *
from matplotlib import style
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from livegraph import liveGraph
# Define the main_screen as a tkinter
app_screen = Tk() # create a GUI window
app_screen.geometry("1920x1080") # set the configuration of GUI window
app_screen.resizable(width=True,height=True)
app_screen.title("Testare izolator") # set the title of GUI window
style.use('bmh')
#figure represents the graphic part of the system
figure = Figure(figsize=(10, 5), facecolor='white',frameon=True)
figure.suptitle('This is the figure title', fontsize=12)
#figure.add_gridspec(10,10)
#this are some parameters that I can easily change if I am using plt
# plt.title('Graph test')
#plt.xlabel("Time")
#plt.ylabel("% SMS")
#x = [ 0, 1, 2, 3, 4 ]
#y = [ 0, 1, 2, 3, 4 ]
#lines = plt.plot(x, y)
#plt.grid()
#plt.axis([0,10,0,10])
#plt.setp(lines, color= "b")
canvas = FigureCanvasTkAgg(figure, app_screen)
canvas.get_tk_widget().pack(side=TOP, anchor =NW, padx=100, pady=10)
newAnimation = liveGraph(figure)
app_screen.mainloop() # start the GUI
You should use self.ax. to add elements
self.ax.set_xlabel('teeeest')
self.ax.set_title('Graph test')
self.ax.set_xlabel("Time")
self.ax.set_ylabel("% SMS")
but then there is other problem because self.ax.clear() removes these elements.
First method:
If you use self.ax.clear() then you remove labels and you have to put labels again and again
def animate(self, i):
self.xs.append(self.xax)
#self.ys.append(datetime.datetime.now().second)
self.ys.append(random.randint(0, 10))
self.xax += 1
self.ax.clear()
self.ax.plot(self.xs,self.ys)
if self.xax > 90:
self.anim.event_source.stop()
self.ax.set_xlabel('teeeest')
self.ax.set_title('Graph test')
self.ax.set_xlabel("Time")
self.ax.set_ylabel("% SMS")
Second method:
To add elements only once you have to remove self.ax.clear() and instead of plot() you should create empty plot in `init
self.ax = fig.add_subplot(111)
self.ax.set_xlabel('teeeest')
self.ax.set_title('Graph test')
self.ax.set_xlabel("Time")
self.ax.set_ylabel("% SMS")
self.line, = self.ax.plot([], [])
and in animation use set_data() to update data in existing plot
self.line.set_data(self.xs, self.ys)
but it will not rescale plot and you will have to do it manually (if you want to rescale it)
self.ax.relim() # recalculate limits
self.ax.autoscale_view(True,True,True) # rescale using limits
Full code for first method
import matplotlib.animation as animation
import datetime
import random
#this is a draft for the liveGraph class
#the objective is to get live data from a sensor
class liveGraph:
#by default define the interval as being 1000 mSec
intervalAnim = 1000
def __init__(self, fig):
self.xax = 0
self.xs = []
self.ys = []
self.ax = fig.add_subplot(111)
self.ax.set_xlabel('teeeest')
self.anim = animation.FuncAnimation(fig, self.animate, interval=self.intervalAnim)
def animate(self, i):
self.xs.append(self.xax)
#self.ys.append(datetime.datetime.now().second)
self.ys.append(random.randint(0, 10))
self.xax += 1
self.ax.clear()
self.ax.plot(self.xs,self.ys)
if self.xax > 90:
self.anim.event_source.stop()
self.ax.set_title('Graph test')
self.ax.set_xlabel("Time")
self.ax.set_ylabel("% SMS")
from tkinter import *
from matplotlib import style
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
#from livegraph import liveGraph
# Define the main_screen as a tkinter
app_screen = Tk() # create a GUI window
app_screen.geometry("1920x1080") # set the configuration of GUI window
app_screen.resizable(width=True, height=True)
app_screen.title("Testare izolator") # set the title of GUI window
style.use('bmh')
#figure represents the graphic part of the system
figure = Figure(figsize=(10, 5), facecolor='white', frameon=True)
figure.suptitle('This is the figure title', fontsize=12)
#figure.add_gridspec(10,10)
#this are some parameters that I can easily change if I am using plt
#plt.title('Graph test')
#plt.xlabel("Time")
#plt.ylabel("% SMS")
#x = [ 0, 1, 2, 3, 4 ]
#y = [ 0, 1, 2, 3, 4 ]
#lines = plt.plot(x, y)
#plt.grid()
#plt.axis([0,10,0,10])
#plt.setp(lines, color= "b")
canvas = FigureCanvasTkAgg(figure, app_screen)
canvas.get_tk_widget().pack(side=TOP, anchor=NW, padx=100, pady=10)
newAnimation = liveGraph(figure)
#canvas.draw()
app_screen.mainloop() # start the GUI
Full code for second method
import matplotlib.animation as animation
import datetime
import random
#this is a draft for the liveGraph class
#the objective is to get live data from a sensor
class liveGraph:
#by default define the interval as being 1000 mSec
intervalAnim = 1000
def __init__(self, fig):
self.xax = 0
self.xs = []
self.ys = []
self.ax = fig.add_subplot(111)
self.ax.set_xlabel('teeeest')
self.ax.set_title('Graph test')
self.ax.set_xlabel("Time")
self.ax.set_ylabel("% SMS")
# create empty plot at start
self.line, = self.ax.plot([], [])
self.anim = animation.FuncAnimation(fig, self.animate, interval=self.intervalAnim)
def animate(self, i):
self.xs.append(self.xax)
#self.ys.append(datetime.datetime.now().second)
self.ys.append(random.randint(0, 2))
self.xax += 1
# update data in existing plot
self.line.set_data(self.xs, self.ys)
# rescale plot (if you need it)
self.ax.relim() # recalculate limits
self.ax.autoscale_view(True,True,True) # rescale using limits
if self.xax > 90:
self.anim.event_source.stop()
from tkinter import *
from matplotlib import style
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
#from livegraph import liveGraph
# Define the main_screen as a tkinter
app_screen = Tk() # create a GUI window
app_screen.geometry("1920x1080") # set the configuration of GUI window
app_screen.resizable(width=True, height=True)
app_screen.title("Testare izolator") # set the title of GUI window
style.use('bmh')
#figure represents the graphic part of the system
figure = Figure(figsize=(10, 5), facecolor='white', frameon=True)
figure.suptitle('This is the figure title', fontsize=12)
#figure.add_gridspec(10,10)
#this are some parameters that I can easily change if I am using plt
#plt.title('Graph test')
#plt.xlabel("Time")
#plt.ylabel("% SMS")
#x = [ 0, 1, 2, 3, 4 ]
#y = [ 0, 1, 2, 3, 4 ]
#lines = plt.plot(x, y)
#plt.grid()
#plt.axis([0,10,0,10])
#plt.setp(lines, color= "b")
canvas = FigureCanvasTkAgg(figure, app_screen)
canvas.get_tk_widget().pack(side=TOP, anchor=NW, padx=100, pady=10)
newAnimation = liveGraph(figure)
#canvas.draw()
app_screen.mainloop() # start the GUI

Categories