Changing the image of a matplotlib button widget - python

I have a matplotlib button widget with an image. After clicking the button, I want to change the image to something else. I tried this:
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
FIGURE = plt.figure()
ICON_PLAY = mpimg.imread('./ui/images/play.png')
ICON_PAUSE = mpimg.imread('./ui/images/pause.png')
def play(event):
print "Starting"
start_button.image = ICON_PAUSE
start_button = Button(plt.axes([0.1, 0.1, 0.8, 0.8]), '', image=ICON_PLAY)
start_button.on_clicked(play)
plt.show()
The event handler is called, but the image is not changed. Does someone know how to change the image of a matplotlib button widget after construction?

The image of a matplotlib.widget.Button is an imshow plot in the axes of the button. Hence you may get the existing image from the axes via button_axes.images[0] and set new data on it, button_axes.images[0].set_data(ICON_PAUSE).
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
fig = plt.figure()
ICON_PLAY = plt.imread('https://i.stack.imgur.com/ySW6o.png')
ICON_PAUSE = plt.imread("https://i.stack.imgur.com/tTa3H.png")
def play(event):
button_axes.images[0].set_data(ICON_PAUSE)
fig.canvas.draw_idle()
button_axes = plt.axes([0.3, 0.3, 0.4, 0.4])
start_button = Button(button_axes, '', image=ICON_PLAY)
start_button.on_clicked(play)
plt.show()

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.

Matplotlib mplcursors not working when figure size is Increased in tkinter canvas

I am a newbie on tkinter and python. I am trying to use mplcursors in a matplotlib figure embedded in a tkinter canvas. The figure size in the tkinter canvas can be changed as discussed on SO here. I want to add mplcursors to hover the data on the plot.
When I do not change the figure size, mplcursors works.
However, the problem occurs when the figure size is increased (to say 30 inches) mplcursors hover does not show up.
I suspect the problem is using tkinter canvas scrollbar with mplcursors. And I am not sure how to fix it.
Any suggestions or ideas, if I am missing something ?
import tkinter as tk
from tkinter import ttk
from tkinter.simpledialog import askfloat
from matplotlib.figure import Figure
from matplotlib.axes import Axes
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import mplcursors
from matplotlib.colors import to_rgb
class InteractivePlot(tk.Frame):
def __init__(self,master,**kwargs):
super().__init__(master,**kwargs)
self._figure = Figure(dpi=150)
self._canvas = FigureCanvasTkAgg(self._figure, master=self)
buttonframe = tk.Frame(self)
self._sizebutton = tk.Button(buttonframe,text="Size (in.)", command=self._change_size)
self._dpibutton = tk.Button(buttonframe,text="DPI", command=self._change_dpi)
self._axis = self._figure.add_subplot(111)
# Plot some data just to have something to look at.
self._axis.plot([0,1,2,3,4,5],[1,1,3,3,5,5],label='Dummy Data')
self._cwidg = self._canvas.get_tk_widget()
self._scrx = ttk.Scrollbar(self,orient="horizontal", command=self._cwidg.xview)
self._scry = ttk.Scrollbar(self,orient="vertical", command=self._cwidg.yview)
self._cwidg.configure(yscrollcommand=self._scry.set, xscrollcommand=self._scrx.set)
self._cwidg.bind("<Configure>",self._refresh)
self._sizebutton.grid(row=0,column=0,sticky='w')
self._dpibutton.grid(row=0,column=1,sticky='w')
buttonframe.grid(row=0,column=0,columnspan=2,sticky='W')
self._cwidg. grid(row=1,column=0,sticky='news')
self._scrx. grid(row=2,column=0,sticky='ew')
self._scry. grid(row=1,column=1,sticky='ns')
self.rowconfigure(1,weight=1)
self.columnconfigure(0,weight=1)
## ADDED LINE
self.curs = mplcursors.cursor(self._axis, hover=mplcursors.HoverMode.Transient) # hover=True
# Refresh the canvas to show the new plot
self._canvas.draw()
# Figure size change button callback
def _change_size(self):
newsize = askfloat('Size','Input new size in inches')
if newsize is None:
return
w = newsize
h = newsize/1.8
self._figure.set_figwidth(w)
self._figure.set_figheight(h)
self._refresh()
# Figure DPI change button callback
def _change_dpi(self):
newdpi = askfloat('DPI', 'Input a new DPI for the figure')
if newdpi is None:
return
self._figure.set_dpi(newdpi)
self._refresh()
# Refresh function to make the figure canvas widget display the entire figure
def _refresh(self,event=None):
# Get the width and height of the *figure* in pixels
w = self._figure.get_figwidth()*self._figure.get_dpi()
h = self._figure.get_figheight()*self._figure.get_dpi()
# Generate a blank tkinter Event object
evnt = tk.Event()
# Set the "width" and "height" values of the event
evnt.width = w
evnt.height = h
# Set the width and height of the canvas widget
self._cwidg.configure(width=w,height=h)
self._cwidg.update_idletasks()
# Pass the generated event object to the FigureCanvasTk.resize() function
self._canvas.resize(evnt)
# Set the scroll region to *only* the area of the last canvas item created.
# Otherwise, the scrollregion will always be the size of the largest iteration
# of the figure.
self._cwidg.configure(scrollregion=self._cwidg.bbox(self._cwidg.find_all()[-1]))
root = tk.Tk()
plt = InteractivePlot(root,width=400,height=400)
plt.pack(fill=tk.BOTH,expand=True)
root.mainloop()

Python figureCanvasTkAgg animation

I'm using Python 3.6 on Ubuntu 17.10. This is an MVC that I tried to do for my actually long code.
I have to make an animated graph of the convolution of two signals, but when I graph, the screen turns gray and the graph just appears.
from tkinter import *
from tkinter import ttk
import matplotlib
matplotlib.use("TkAgg")
from matplotlib import style
style.use("ggplot")
from matplotlib import pyplot as plt
import matplotlib.animation as animation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import numpy as np
from scipy import signal as sn
import time
tim = np.arange(0,10)
tim1 = np.arange(0,10)
root = Tk()
root.geometry('1280x700')
root.resizable(width = False, height = False)
root.configure(bg = fondo)
root.title('Convolusión')
f2 = Figure(figsize=(3,2), dpi=100)
k2 = f2.add_subplot(1, 1, 1)
plot = FigureCanvasTkAgg(f2, root)
plot.show()
plot.get_tk_widget().place(x=50,y=470)
k3.clear()
ycc = sn.convolve(s1,s2,mode="full")
t = np.arange(min(tim)+min(tim1),max(tim)+max(tim1)+1)
i=0
tim = [0]*len(t)
y = [0]*len(t)
for x in t:
tim[i] = t[i]
y[i] = ycc[i]
k3.clear()
k3.plot(tim,y)
plot = FigureCanvasTkAgg(self.f3, self.root)
plot.show()
plot.get_tk_widget().place(x=450,y=470)
i=i+1
plt.pause(0.1)

Continue code after closing matplotlib figure

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.

matplotlib, plt.show() in a different method = no on_clicked

When I put plt.show() in a different method, it's impossible to click the button :
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
class ButtonTest:
def __init__(self):
ax = plt.axes([0.81, 0.05, 0.1, 0.075])
bnext = Button(ax, 'Next')
bnext.on_clicked(self._next)
# plt.show()
def show(self):
print("when i put plt.show() in a different method, it's impossible to click the button")
plt.show()
def _next(self, event):
print("next !")
b = ButtonTest()
b.show()
The button is not even highlighted when the mouse moves over it. Would someone know why and how to solve the problem ?
What's happening is that the button object is being garbage collected before the plot is displayed. You'll need to keep a reference to it around.
For example, if you change
bnext = Button(...)
to
self.bnext = Button(...)
Everything should work.
As a complete example:
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
class ButtonTest:
def __init__(self):
ax = plt.axes([0.81, 0.05, 0.1, 0.075])
self.bnext = Button(ax, 'Next')
self.bnext.on_clicked(self._next)
def show(self):
plt.show()
def _next(self, event):
print("next !")
ButtonTest().show()

Categories