I have the following mpl plot:
def __init__(...):
self.fig = plt.figure()
self.axis = self.fig.add_axes([0.05, 0.08, 0.90, 0.92])
# plot some stuff...
self.mpl_animation = ani.FuncAnimation(
fig=self.fig,
func=self.animate_func,
interval=50,
blit=True,
init_func=lambda: [self.title],
frames=range(start_time, end_time, 10),
repeat=True,
repeat_delay=1000,
)
self.slider_ax = self.fig.add_axes([0.3, 0.05, 0.4, 0.03])
self.slider = Slider(ax=self.slider_ax, label='Time', valmin=start_time, valmax=end_time, valinit=start_time)
self.slider.on_changed(self._set_time)
def animate_func(self, time) -> List[Artist]:
artists = self.animation.step(time)
self.title.set_text(to_hh_mm_ss(time))
self.slider.set_val(time) # this causes the problem
return artists + [self.title]
Now as you can see, In the animate_func I want to set the value of the slider to the current time but this operation s responsible that the returned artists are not redrawn. How can I solve this issue?
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.
I have the following code. The grid is visible without the Button widget. But when the grid is not shown if I add the button. What am I doing wrong?
from matplotlib import pyplot as plot
from matplotlib.widgets import Button
plot.plot([1,2,3], [1,2,3])
ax = plot.axes([0.5, 0.5, 0.05, 0.05])
Button(ax, "A")
plot.grid()
plot.show()
For me you code is working fine! (Except the button is in the middle of the screen)
Maybe you should try the following snippet:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
freqs = np.arange(2, 20, 3)
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.2)
t = np.arange(0.0, 1.0, 0.001)
s = np.sin(2*np.pi*freqs[0]*t)
l, = plt.plot(t, s, lw=2)
class Index:
ind = 0
def next(self, event):
self.ind += 1
i = self.ind % len(freqs)
ydata = np.sin(2*np.pi*freqs[i]*t)
l.set_ydata(ydata)
plt.draw()
def prev(self, event):
self.ind -= 1
i = self.ind % len(freqs)
ydata = np.sin(2*np.pi*freqs[i]*t)
l.set_ydata(ydata)
plt.draw()
callback = Index()
axprev = plt.axes([0.7, 0.05, 0.1, 0.075])
axnext = plt.axes([0.81, 0.05, 0.1, 0.075])
bnext = Button(axnext, 'Next')
bnext.on_clicked(callback.next)
bprev = Button(axprev, 'Previous')
bprev.on_clicked(callback.prev)
plt.show()
To read more about matplotlib buttons read and see the snippet: https://matplotlib.org/stable/gallery/widgets/buttons.html
Also you might want to rename the button to have a bigger name like "TEST" to avoid problems regarding the size of the label.
The Button() instantiation changes the current axes. So when I call plot.grid() it is operated on Button axes. I changed the order of calling plot.grid() and it worked. I have shown the modified code below.
from matplotlib import pyplot as plot
from matplotlib.widgets import Button
plot.plot([1,2,3], [1,2,3])
# note grid() is called before Button
plot.grid()
ax = plot.axes([0.5, 0.5, 0.05, 0.05])
Button(ax, "A", hovercolor="red")
plot.show()
I am writing a routine where I can append a set of datafiles. The first part of the code imports a single datafile and displays it using matplotlib. I can then use a slider to define a specific x range (i.e. to exclude noisy or irrelevant parts). The second part of the code involves an edit routine where I edit the range of all the datafiles in the specific folder based on this newly set x range (i.e. removing all of the rows).
The first part of the code is not a problem for me and results in the following interface. My problem is with the second part. I want the Edit button to close the figure and continue the rest of the code (the datafile editing part). This is necessary because I need the slider values to define the new datarange.
My initial thought was to place part 2 of the code after plt.show(). The Edit button would simply close the figure and the rest of the code would continue. An example of my code without using my actual data:
### Part 1 ###
import os
import sys
import six
import tkinter as tk
from tkinter import Tk
from tkinter import filedialog
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import matplotlib.gridspec as gridspec
from matplotlib.widgets import Slider, Button
class MinMaxSlider(Slider):
def __init__(self, ax, label, valmin, valmax, **kwargs):
self.valinit2 = kwargs.pop("valinit2", valmax)
self.val2 = self.valinit2
Slider.__init__(self,ax, label, valmin, valmax, **kwargs)
self.poly.xy = np.array([[self.valinit,0],[self.valinit,1],
[self.valinit2,1],[self.valinit2,0]])
self.vline.set_visible(False)
def set_val(self, val):
if np.abs(val-self.val) < np.abs(val-self.val2):
self.val = val
else:
self.val2 = val
self.poly.xy = np.array([[self.val,0],[self.val,1],
[self.val2,1],[self.val2,0]])
self.valtext.set_text(self.valfmt % self.val +"\n"+self.valfmt % self.val2)
if self.drawon:
self.ax.figure.canvas.draw_idle()
if not self.eventson:
return
for cid, func in six.iteritems(self.observers):
func(self.val,self.val2)
def update(mini,maxi):
ax.set_xlim(mini,maxi)
def edit(event):
plt.close()
def find_nearest(array,value):
return (np.abs(array-value)).argmin()
## Part 1 ##
plt.ion()
x = np.array(range(100))
y_low = 10*np.array(range(100))
y_high = 10*np.array(range(100))
font = {'family' : 'Calibri',
'weight' : 'normal',
'size' : 14}
mpl.rc('font', **font)
axcolor = 'lightgoldenrodyellow'
fig,(ax, sliderax) = plt.subplots(figsize=(12,8),
nrows=2,gridspec_kw={"height_ratios":[1,0.05]})
fig.subplots_adjust(hspace=0.5)
ln1 = ax.plot(x,y_low, color = 'dodgerblue', label = 'Low')
ax.set_xlabel('x', fontsize=24)
ax.xaxis.labelpad = 15
ax.set_ylabel('y', fontsize=24, color = 'dodgerblue')
ax.yaxis.labelpad = 15
for tl in ax.get_yticklabels():
tl.set_color('dodgerblue')
ax1 = ax.twinx()
ln2 = ax1.plot(x,y_high, color = 'darkred', label = 'High')
ax1.set_ylabel('y', fontsize=24, color = 'darkred')
for tl in ax1.get_yticklabels():
tl.set_color('darkred')
lns = ln1+ln2
labs = [l.get_label() for l in lns]
ax1.legend(lns, labs, loc=0)
slider = MinMaxSlider(sliderax,'x(min/max)',x.min(),x.max(),
valinit=x.min(),valinit2=x.max())
slider.on_changed(update)
update(x.min(),x.max())
resetax = plt.axes([0.8, 0.025, 0.1, 0.04])
button = Button(resetax, 'Edit', color=axcolor, hovercolor='0.975')
button.on_clicked(edit)
plt.show()
## Part 2 ##
idx_low = find_nearest(x, slider.val)
idx_high = find_nearest(x, slider.val2)
print(idx_low)
print(idx_high)
However the idx_low and idx_high are already calculated and printed while the figure is open, and thus based on the initial values of the slider. I want them to be calculated after clicking the Edit button and closing the figure so that they are based on the slider values set by the user. How can I achieve this? Do I need to edit the Edit event function? Thank you for your help.
I have a program which shows an image (fig 1). When the image is clicked it shows the colour in the image that was clicked in a separate Matplotlib window (fig 2). Fig 2 has some buttons that call different functions when they are clicked.
My problem is that the functions that are meant to be called in fig 2 are being called when fig 1 is clicked.
The code looks like this:
def show_fig1(img):
# Plot the image
plt.figure(1)
ax = plt.gca()
fig = plt.gcf()
implot = ax.imshow(img)
# Detect a click on the image
cid = fig.canvas.mpl_connect('button_press_event', on_pixel_click)
plt.show(block=True)
# Called when fig1 is clicked
def on_pixel_click(event):
if event.xdata != None and event.ydata != None:
# Do some computation here that gets the image for fig2
img = get_fig2_img()
show_fig2(img, event)
def show_fig2(img, event):
plt.figure(2)
plt.imshow(img)
# Specify coordinates of the button
ax = plt.axes([0.0, 0.0, 0.2, 0.1])
# Add the button
button = Button(ax, 'button')
# Detect a click on the button
button.on_clicked(test())
plt.show(block=True)
def test():
print "Button clicked"
So test() is called instantly when on_pixel_click() is called even though theoretically it should wait until the button is clicked because of the button.on_clicked() command.
Any help?
Thanks in advance :)
On this line:
button.on_clicked(test())
You are telling Python to execute your test function, rather than just passing a reference to it. Remove the brackets and it should sort it:
button.on_clicked(test)