Matplotlib scroll bar - python

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.

Related

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

matplotlib FuncAnimation won't start on button press widget event

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)

matplotlib.widgets Button doesn't work inside a class

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

Interactive matplotlib plots via textboxes

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:

Matplotlib widget - many radiobuttons issue

I need to set height of the matplotlib widget, change alignment or change font but i don't know how.
Screenshots:
5 radio buttons:
15 radio buttons:
And here is the critical code part:
def evolvecallback_(self, notification):
global i
stats={}
results=[]
fitnesses=[]
for code,fit in evolve(i):
results.append(code)
stats[str(fit)]=code
fitnesses.append(str(fit))
i+=1
fig = plt.figure()
done=""
if found():
done=" - Solution found!"
print "Done!"
fig.canvas.set_window_title("Generation "+str(i)+". - Best migrating individuals"+done)
ax = plt.subplot(111)
ax.set_title(stats[str(fitnesses[0])])
l, = ax.plot(x, s0, lw=2, color='blue')
plt.subplots_adjust(left=0.4)
axcolor = 'lightgoldenrodyellow'
rax = plt.axes([0.05, 0.7, 0.15, 0.15], axisbg=axcolor)
radio = RadioButtons(rax, tuple(fitnesses))
def change(label):
hzdict = {}
x = np.arange(-5.0, 5.0, 0.01)
j=0
for i in tuple(results):
hzdict[fitnesses[j]]=eval(i)
j+=1
ax.set_title(stats[str(label)])
ydata = hzdict[str(label)]
l.set_ydata(ydata)
plt.draw()
radio.on_clicked(change)
plt.show()
This line sets where and how big the axes the radio buttons are drawn onto is:
rax = plt.axes([0.05, 0.7, 0.15, 0.15], axisbg=axcolor)
Change it to something like
rax = plt.axes([0.05, 0.3, 0.15, 0.5], axisbg=axcolor)
(doc)
To be clear, rax is just an axes object to which you are adding the radio button widgets to.

Categories