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

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()

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.

Pyplot grid is not shown if I add Button widget

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()

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.

Changing the image of a matplotlib button widget

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()

Python Matplotlib callback function with parameters

In a callback function of button presses, is there anyway to pass more parameters other than 'event'? For example, in the call back function, I want to know the text of the button ('Next' in this case). How can I do that?
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
fig = plt.figure()
def next(event):
# I want to print the text label of the button here, which is 'Next'
pass
axnext = plt.axes([0.81, 0.05, 0.1, 0.075])
bnext = Button(axnext, 'Next')
bnext.on_clicked(next)
plt.show()
Another possibly quicker solution is to use a lambda function:
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
fig = plt.figure()
def next(event, text):
print(text)
pass
axnext = plt.axes([0.81, 0.05, 0.1, 0.075])
bnext = Button(axnext, 'Next')
bnext.on_clicked(lambda x: next(x, bnext.label.get_text()))
plt.show()
To obtain that, you might need to encapsulate event processing in a class, as in official tutorial:
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
class ButtonClickProcessor(object):
def __init__(self, axes, label):
self.button = Button(axes, label)
self.button.on_clicked(self.process)
def process(self, event):
print self.button.label
fig = plt.figure()
axnext = plt.axes([0.81, 0.05, 0.1, 0.075])
bnext = ButtonClickProcessor(axnext, "Next")
plt.show()

Categories