I'm trying to create an interactive matplotlib plot of a multidimensional function with three parameters to vary. The problem is that the parameters can vary over a very large range, so I'd rather not use sliders but directly type the value I'd like. Basically, I'd like to recreate the canonical example below where instead of sliders I'd like text boxes in which I can input parameters
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.35)
t = np.arange(0.0, 1.0, 0.001)
a0 = 5
f0 = 3
s = a0*np.sin(2*np.pi*f0*t)
l, = plt.plot(t,s, lw=2, color='red')
plt.axis([0, 1, -10, 10])
ax.plot(t,t)
axcolor = 'lightgoldenrodyellow'
axamp = plt.axes([0.25, 0.25, 0.65, 0.03], axisbg=axcolor)
axfreq = plt.axes([0.25, 0.2, 0.65, 0.03], axisbg=axcolor)
sfreq = Slider(axfreq, 'Freq', 0.1, 30.0, valinit=f0)
samp = Slider(axamp, 'Amp', 0.1, 10.0, valinit=a0)
def update(val):
amp = samp.val
freq = sfreq.val
l.set_ydata(amp*np.sin(2*np.pi*freq*t))
fig.canvas.draw_idle()
sfreq.on_changed(update)
samp.on_changed(update)
resetax = plt.axes([0.8, 0.025, 0.1, 0.04])
button = Button(resetax, 'Reset', color=axcolor, hovercolor='0.975')
def reset(event):
sfreq.reset()
samp.reset()
button.on_clicked(reset)
plt.show()
You can add a GUI panel to the figure window. Here is an example to use the Qt4Agg backend, and add a QDockWidget to the main figure window, and then you can add QWidgets to the dock window.
import numpy as np
import matplotlib
matplotlib.use("Qt4Agg") # This program works with Qt only
import pylab as pl
fig, ax1 = pl.subplots()
t = np.linspace(0, 10, 200)
line, = ax1.plot(t, np.sin(t))
### control panel ###
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import Qt
def update():
freq = float(textbox.text())
y = np.sin(2*np.pi*freq*t)
line.set_data(t, y)
fig.canvas.draw_idle()
root = fig.canvas.manager.window
panel = QtGui.QWidget()
hbox = QtGui.QHBoxLayout(panel)
textbox = QtGui.QLineEdit(parent = panel)
textbox.textChanged.connect(update)
hbox.addWidget(textbox)
panel.setLayout(hbox)
dock = QtGui.QDockWidget("control", root)
root.addDockWidget(Qt.BottomDockWidgetArea, dock)
dock.setWidget(panel)
######################
pl.show()
Here is the screen:
Related
I was trying to create interactive topoplot with python MNE and got stuck. Topomap (or topoplot) is drawn, but it doesn't update. Do you have any idea how to make it interactive?
data:
import mne
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets import Slider, Button
%matplotlib widget
biosemi_montage = mne.channels.make_standard_montage('biosemi64')
n_channels = len(biosemi_montage.ch_names)
fake_info = mne.create_info(ch_names=biosemi_montage.ch_names, sfreq=250.,
ch_types='eeg')
rng = np.random.RandomState(0)
data = rng.normal(size=(n_channels, 100)) * 1e-6
fake_evoked = mne.EvokedArray(data, fake_info)
fake_evoked.set_montage(biosemi_montage)
topoplot:
fig, ax = plt.subplots()
voltage = fake_evoked.data
timing = [round(i, ndigits=3) for i in list(np.linspace(0, 1, 100))]
init_time = 0
mne.viz.plot_topomap(voltage[:, timing.index(init_time)], fake_evoked.info, axes=ax,
#image_interp='cubic',
show=False)
ax.set_title('MNE', fontweight='bold')
axtime = fig.add_axes([0.25, 0.01, 0.65, 0.04])
time_slider = Slider(
ax=axtime,
label='Time [ms]',
valmin=0, valmax=1, valstep=100,
valinit=init_time,
)
# The function to be called anytime a slider's value changes
def update(val):
init_time = round(time_slider.val, ndigits=3)
mne.viz.plot_topomap(voltage[:, timing.index(init_time)], fake_evoked.info, axes=ax,
show=False)
fig.canvas.draw_idle()
# register the update function with each slider
time_slider.on_changed(update)
plt.show()
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()
matplotlib noob here.
I am trying to create an animation (which starts on a button click) of a normal distribution being populated, where the distribution parameters (mean and std dev) are selected using two slider widgets.
Please help. I have pasted my code below
%matplotlib notebook
from pdb import set_trace as bp
import matplotlib.animation as animation
import numpy as np
from matplotlib.widgets import Slider, Button, RadioButtons
fig = plt.figure()
n = 1000
x = np.array([])
bins = np.arange(-4, 4, 0.5)
plt.hist(x, bins=bins)
plt.subplots_adjust(bottom=0.25)
def update(curr):
if curr == n:
a.event_source.stop()
plt.cla()
plt.hist(x[:curr], bins=bins)
plt.axis([-4,4,0,500])
plt.gca().set_title('Sampling the Normal Distribution')
plt.gca().set_ylabel('Frequency')
plt.gca().set_xlabel('Value')
plt.annotate('n = {}'.format(curr), [3,480])
axcolor = 'lightgoldenrodyellow'
axmu = plt.axes([0.15, 0.1, 0.65, 0.03], facecolor=axcolor)
axstdev = plt.axes([0.15, 0.15, 0.65, 0.03], facecolor=axcolor)
muslider = Slider(axmu, 'mean', 0.1, 30.0, valinit=0)
stdevslider = Slider(axstdev, 'stdev', 0.1, 10.0, valinit=1.0)
startax = plt.axes([0.4, 0.025, 0.1, 0.04])
startbutton = Button(startax, 'Start', color=axcolor, hovercolor='0.975')
newmean = 0
newstdev = 1.0
def getnewparams(val):
global newmean
global newstdev
newmean = muslider.val
newstdev = stdevslider.val
def startanimation(event):
print(f"params now is {newmean} {newstdev}")
global x
x = np.random.normal(loc=newmean, scale=newstdev, size=n)
a = animation.FuncAnimation(fig, update, interval=100)
a.event_source.start()
muslider.on_changed(getnewparams)
stdevslider.on_changed(getnewparams)
startbutton.on_clicked(startanimation)
How my plot looks now
Using #jasonharper's suggestion, I was able to resolve the issue on my own. I am pasting the working code below
%matplotlib notebook
import matplotlib.pyplot as plt
from pdb import set_trace as bp
import matplotlib.animation as animation
import numpy as np
from matplotlib.widgets import Slider, Button, RadioButtons
fig = plt.figure()
n = 1000
a = None
x = np.array([])
# bins = np.arange(-4, 4, 0.5)
plt.hist(x, bins=bins)
plt.subplots_adjust(bottom=0.25)
histaxis = plt.gca()
def update(curr):
if curr == n:
a.event_source.stop()
histaxis.cla()
histaxis.hist(x[:curr], bins=100)
# histaxis.axis([-4,4,0,500])
histaxis.set_title('Sampling the Normal Distribution')
histaxis.set_ylabel('Frequency')
histaxis.set_xlabel('Value')
histaxis.annotate('n = {}'.format(curr), [3,480])
axcolor = 'lightgoldenrodyellow'
axmu = plt.axes([0.15, 0.1, 0.65, 0.03], facecolor=axcolor)
axstdev = plt.axes([0.15, 0.15, 0.65, 0.03], facecolor=axcolor)
muslider = Slider(axmu, 'mean', 0.1, 30.0, valinit=0)
stdevslider = Slider(axstdev, 'stdev', 0.1, 10.0, valinit=1.0)
startax = plt.axes([0.4, 0.025, 0.1, 0.04])
startbutton = Button(startax, 'Start', color=axcolor, hovercolor='0.975')
newmean = 0
newstdev = 1.0
def getnewparams(val):
global newmean
global newstdev
newmean = muslider.val
newstdev = stdevslider.val
def startanimation(event):
print(f"params now is {newmean} {newstdev}")
global x
x = np.random.normal(loc=newmean, scale=newstdev, size=n)
global a
a = animation.FuncAnimation(fig, update, interval=100)
a.event_source.start()
muslider.on_changed(getnewparams)
stdevslider.on_changed(getnewparams)
startbutton.on_clicked(startanimation)
I took the example for a slider widget , and tried to plug it into a class. The sliders work properly, but for some reason the Button and RadioButtons does not react when the method is inside a class:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons
class test(object):
def __init__(self):
fig, ax = plt.subplots()
plt.subplots_adjust(left=0.25, bottom=0.25)
t = np.arange(0.0, 1.0, 0.001)
a0 = 5
f0 = 3
s = a0*np.sin(2*np.pi*f0*t)
l, = plt.plot(t, s, lw=2, color='red')
plt.axis([0, 1, -10, 10])
axcolor = 'lightgoldenrodyellow'
axfreq = plt.axes([0.25, 0.1, 0.65, 0.03], facecolor=axcolor)
axamp = plt.axes([0.25, 0.15, 0.65, 0.03], facecolor=axcolor)
sfreq = Slider(axfreq, 'Freq', 0.1, 30.0, valinit=f0)
samp = Slider(axamp, 'Amp', 0.1, 10.0, valinit=a0)
def update(val):
amp = samp.val
freq = sfreq.val
l.set_ydata(amp*np.sin(2*np.pi*freq*t))
fig.canvas.draw_idle()
sfreq.on_changed(update)
samp.on_changed(update)
resetax = plt.axes([0.8, 0.025, 0.1, 0.04])
button = Button(resetax, 'Reset', color=axcolor, hovercolor='0.975')
def reset(event):
sfreq.reset()
samp.reset()
button.on_clicked(reset)
rax = plt.axes([0.025, 0.5, 0.15, 0.15], facecolor=axcolor)
radio = RadioButtons(rax, ('red', 'blue', 'green'), active=0)
def colorfunc(label):
l.set_color(label)
fig.canvas.draw_idle()
radio.on_clicked(colorfunc)
plt.show()
If I try now to use this class the Button and RadioButton are freezed..anyone encountered this?
Your loosing the reference to the widgets and methods statically defined inside the class constructor. The documentation says "To guarantee that the widget remains responsive and not garbage-collected, a reference to the object should be maintained by the user."
The usual way to do this is to use class attributes (self.). That means that you need to change your code as follows:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons
class test(object):
def __init__(self):
self.fig, ax = plt.subplots()
plt.subplots_adjust(left=0.25, bottom=0.25)
self.t = np.arange(0.0, 1.0, 0.001)
a0 = 5
f0 = 3
s = a0*np.sin(2*np.pi*f0*self.t)
self.l, = plt.plot(self.t, s, lw=2, color='red')
plt.axis([0, 1, -10, 10])
axcolor = 'lightgoldenrodyellow'
axfreq = plt.axes([0.25, 0.1, 0.65, 0.03], facecolor=axcolor)
axamp = plt.axes([0.25, 0.15, 0.65, 0.03], facecolor=axcolor)
self.sfreq = Slider(axfreq, 'Freq', 0.1, 30.0, valinit=f0)
self.samp = Slider(axamp, 'Amp', 0.1, 10.0, valinit=a0)
self.sfreq.on_changed(self.update)
self.samp.on_changed(self.update)
resetax = plt.axes([0.8, 0.025, 0.1, 0.04])
self.button = Button(resetax, 'Reset', color=axcolor, hovercolor='0.975')
self.button.on_clicked(self.reset)
rax = plt.axes([0.025, 0.5, 0.15, 0.15], facecolor=axcolor)
self.radio = RadioButtons(rax, ('red', 'blue', 'green'), active=0)
self.radio.on_clicked(self.colorfunc)
plt.show()
def update(self,val):
amp = self.samp.val
freq = self.sfreq.val
self.l.set_ydata(amp*np.sin(2*np.pi*freq*self.t))
self.fig.canvas.draw_idle()
def reset(self, event):
self.sfreq.reset()
self.samp.reset()
def colorfunc(self, label):
self.l.set_color(label)
self.fig.canvas.draw_idle()
test()
I have a line generalisation algorithm and want to add a scroll bar to the plot that will increase the tolerance (i,e make the line more and more generalised). Using matplotlib how would this be possible?
So to sum up, I want to be able to click and drag a slider that will display the increase in the tolerances effect on the line.
Still really struggling with this. I only want one slider on a simple scale from 1-10.
yeah the demo helps, i'm just struggerling to get one slider to work, this is what I have so far,
fig = mp.figure()
ax = fig.add_subplot(111)
fig.subplots_adjust(left=0.25, bottom=0.25)
min0=1
max0=10
tolerance = 0
chain1 = ChainLoader('Wiggle1.txt')
chain = chain1[0]
chain2 = chain.generalise(tolerance)
axcolor = 'lightgoldenrodyellow'
axmin = fig.add_axes([0.25, 0.1, 0.65, 0.03], axisbg=axcolor)
axmax = fig.add_axes([0.25, 0.15, 0.65, 0.03], axisbg=axcolor)
tolerance = Slider(axmin, 'Min', 1, 10, valinit=min0)
#smax = Slider(axmax, 'Max', 0, 30000, valinit=max0)
def update(val):
tolerance = tolerance.val
#pp.show()
tolerance.on_changed(update)
#smax.on_changed(update)
chain2 = chain.generalise(tolerance)
pp.plotPolylines(chain2)
pp.show()
My problems are how to write the def update section. Any help?
from PointPlotter import PointPlotter
from ChainHandler import ChainLoader
pp=PointPlotter()
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
ax = plt.subplot(111)
plt.subplots_adjust(left=0.25, bottom=0.25)
tolerance = 0
f0 = 0
chain2 = ChainLoader('Wiggle1.txt')
for chain in chain2:
chain2 = chain.generalise(tolerance)
pp.plotPolylines(chain2)
axcolor = 'lightgoldenrodyellow'
axtol = plt.axes([0.25, 0.1, 0.65, 0.03], axisbg=axcolor)
tolerance = Slider(axtol, 'tol', 0.1, 30.0, valinit=f0)
def update(val):
tolerance = tolerance.val
for chain in chain2:
chain2 = chain.generalise(tolerance)
pp.plotPolylines(chain2)
pp.plotPolylines(chain2)
tolerance.on_changed(update)
plt.show()
So close! Its now plotting, but returns "UnboundLocalError: local variable 'tolerance' referenced before assignment" when the scroll bar is used. #tcaswell any help?
You want the slider widget (doc).
Here is the demo from the examples:
http://matplotlib.org/examples/widgets/slider_demo.html
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons
ax = plt.subplot(111)
plt.subplots_adjust(left=0.25, bottom=0.25)
t = np.arange(0.0, 1.0, 0.001)
a0 = 5
f0 = 3
s = a0*np.sin(2*np.pi*f0*t)
l, = plt.plot(t,s, lw=2, color='red')
plt.axis([0, 1, -10, 10])
axcolor = 'lightgoldenrodyellow'
axfreq = plt.axes([0.25, 0.1, 0.65, 0.03], facecolor=axcolor)
axamp = plt.axes([0.25, 0.15, 0.65, 0.03], facecolor=axcolor)
sfreq = Slider(axfreq, 'Freq', 0.1, 30.0, valinit=f0)
samp = Slider(axamp, 'Amp', 0.1, 10.0, valinit=a0)
def update(val):
amp = samp.val
freq = sfreq.val
l.set_ydata(amp*np.sin(2*np.pi*freq*t))
plt.draw()
sfreq.on_changed(update)
samp.on_changed(update)
resetax = plt.axes([0.8, 0.025, 0.1, 0.04])
button = Button(resetax, 'Reset', color=axcolor, hovercolor='0.975')
def reset(event):
sfreq.reset()
samp.reset()
button.on_clicked(reset)
rax = plt.axes([0.025, 0.5, 0.15, 0.15], facecolor=axcolor)
radio = RadioButtons(rax, ('red', 'blue', 'green'), active=0)
def colorfunc(label):
l.set_color(label)
plt.draw()
radio.on_clicked(colorfunc)
plt.show()
To adapt this to your case:
#smax.on_changed(update)
chain2 = chain.generalise(tol)
pp.plotPolylines(chain2)
def update(val):
tol = tolerance.val # get the value from the slider
chain2 = chain.generalise(tol) # shove that value into your code
ax.cla() # clear the axes
pp.plotPolylines(chain2) # re-plot your new results
# register the call back
tolerance.on_changed(update)
Be careful about re-using variable names (you use tolerance twice, once for a float and once for the Slider and python will happily clobber your old variables with new ones of an entirely different type).
In update I went with the most brute-force approach, clearing the axes and then re-drawing it, in general you want to grab the artists that are returned by plotPolylines and update those with your new data. (If you need help with that step, open a new question with details about your data structure).
The way to understand .on_changed is that when the slider notices it has been changed, it will call the function you passed in (update) with a single argument (val) which is the current value of the slider. Inside that function you can do what ever you want, and it will be executed in full every time the slider is changed.