How to make dependent sliders in matplotlib - matplotlib

Similar to Matplotlib dependent sliders, I want to make two sliders whose sum make 10. To do that, i want that when i move one slider, the other one moves to compensate. At the moment, the code is the following :
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons
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 = 5
delta_f = 5.0
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, 10.0, valinit=f0)
samp = Slider(axamp, 'Amp', 0.1, 10.0, valinit=a0)
def update_sfreq(val):
samp.val = 10-sfreq.val
l.set_ydata(samp.val*np.sin(2*np.pi*sfreq.val*t))
fig.canvas.draw_idle()
sfreq.on_changed(update_sfreq)
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()
This is one of matplotlib examples that I modified to suit my needs. At the moment, I only implemented s_freq.on_changed(). I want that when I move the freq slider, the graph changes (This part is working), and at the same time, the amp slider moves too (This part is not).
Any thoughts on how to modify my function update_sfreq to correctly update samp?
Note : I do realize that if both my sliders update each other, I might end up in an infinite loop. I have already thought of this and of a solution. The part that is not working is really the part where moving one slider makes the other slider move.

Well, after some digging in matplotlib source code, I managed to find an answer to my question. You need to change
samp.val = ...
to
samp.set_val(...)
This will update the bar correctly.

Related

Utilise a slider to update the position of legend in Matplotlib

I am trying to make a slider that can adjust the x and y coordinates of the legend anchor, but this does not seem to be updating on the plot. I keep getting the message in console "No artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument", each time the slider value is updated.
Here is the code, taken from this example in the matplotlib docs
from cProfile import label
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button
# The parametrized function to be plotted
def f(t, amplitude, frequency):
return amplitude * np.sin(2 * np.pi * frequency * t)
t = np.linspace(0, 1, 1000)
# Define initial parameters
init_amplitude = 5
init_frequency = 3
# Create the figure and the line that we will manipulate
fig, ax = plt.subplots()
line, = ax.plot(t, f(t, init_amplitude, init_frequency), lw=2, label = "wave")
ax.set_xlabel('Time [s]')
# adjust the main plot to make room for the sliders
fig.subplots_adjust(left=0.25, bottom=0.25)
initx = 0.4
inity = 0.2
def l(x,y):
return (x,y)
legend = fig.legend(title = 'title', prop={'size': 8}, bbox_to_anchor = l(initx,inity))
legend.remove( )
# Make a horizontal slider to control the frequency.
axfreq = fig.add_axes([0.25, 0.1, 0.3, 0.3])
freq_slider = Slider(
ax=axfreq,
label='Frequency [Hz]',
valmin=0.1,
valmax=30,
valinit=init_frequency,
)
# Make a vertically oriented slider to control the amplitude
axamp = fig.add_axes([0.1, 0.25, 0.0225, 0.63])
amp_slider = Slider(
ax=axamp,
label="Amplitude",
valmin=0,
valmax=10,
valinit=init_amplitude,
orientation="vertical"
)
# The function to be called anytime a slider's value changes
def update(val):
legend = plt.legend(title = '$J_{xx}$', prop={'size': 8}, bbox_to_anchor= l(amp_slider.val, freq_slider.val))
legend.remove()
#line.set_ydata(f(t, amp_slider.val, freq_slider.val))
fig.canvas.draw_idle()
# register the update function with each slider
freq_slider.on_changed(update)
amp_slider.on_changed(update)
# Create a `matplotlib.widgets.Button` to reset the sliders to initial values.
resetax = fig.add_axes([0.8, 0.025, 0.1, 0.04])
button = Button(resetax, 'Reset', hovercolor='0.975')
def reset(event):
freq_slider.reset()
amp_slider.reset()
button.on_clicked(reset)
plt.show()
Is it even possible to update other matplotlib plot parameters like xticks/yticks or xlim/ylim with a slider, rather than the actual plotted data? I am asking so that I can speed up the graphing process, as I tend to lose a lot of time just getting the right plot parameters whilst making plots presentable, and would like to automate this in some way.

How to set the updated value of Slider always in the centre?

Following the Slider Demo of Matplotlib https://matplotlib.org/gallery/widgets/slider_demo.html, I would like to update the Slider ranges, so that every time I change the slider values, those are re-centred in the Slider.
I have tried to define the Sliders as
sfreq = Slider(axfreq, 'Freq', freq-10, freq+10, valinit=freq)
samp = Slider(axamp, 'Amp', amp-5, amp+5, valinit=amp)
but since the update() function does not return anything, that does not work. I also tried making these variables global inside the function, which also did not work. I finally tried defining the Sliders inside the update function,
def update(val):
amp = samp.val
freq = sfreq.val
l.set_ydata(amp*np.sin(2*np.pi*freq*t))
fig.canvas.draw_idle()
Slider(axfreq, 'Freq', freq-10, freq+10, valinit=freq)
Slider(axamp, 'Amp', amp-5, amp+5, valinit=amp)
but that overlays more and more Sliders as I change the values. Any suggestions?
So I just decided to make the range of the slider cover several orders of magnitude of the parameter, and display the values in a logarithmic scale. In case anyone wonders, and following the matplotlib demo:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons
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 = 10
delta_f = 5.0
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', np.log(1), np.log10(1000), valinit=np.log10(f0), valfmt='%4.2E')
samp = Slider(axamp, 'Amp', a0-5, a0+5, valinit=a0)
def update(val):
amp = samp.val
freq = sfreq.val
sfreq.valtext.set_text('{:4.2E}'.format(10**freq))
l.set_ydata(amp*np.sin(2*np.pi*10**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()

create different sliders by clicking different buttons (matplotlib widgets)

Okay, here we go: This code first defines 3 different sliders (f010,f015,f022) that are set invisible and 3 different buttons (WR10,WR15,WR22). Tje sliders are all at the same xy position. Then each button should call a specific slider, e.g. Button WR10 --> slider f010. The problem is, that if one button is clicked, all 3 sliders are called and do overwrite each other. Can someone help me?
from __future__ import print_function
from numpy import pi, sin
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons
from matplotlib.patches import Ellipse
from scipy.optimize import fsolve
#Plot
axis_color = 'lightgoldenrodyellow'
fig = plt.figure(figsize=(20,10))
ax = fig.add_subplot(111)
fig.subplots_adjust(left=0.25, bottom=0.35)
#Define Sliders and set all invisible
f010_slider_ax = fig.add_axes([0.25, 0.25, 0.65, 0.03], axisbg=axis_color)
f010_slider = Slider(f010_slider_ax, 'f0_10 [GHz]', 750.0, 1100.0, valinit=750.0)
f010_slider_ax.set_visible(False)
f015_slider_ax = fig.add_axes([0.25, 0.25, 0.65, 0.03], axisbg=axis_color)
f015_slider = Slider(f015_slider_ax, 'f0_15 [GHz]', 500.0, 750.0, valinit=500.0)
f015_slider_ax.set_visible(False)
f022_slider_ax = fig.add_axes([0.25, 0.25, 0.65, 0.03], axisbg=axis_color)
f022_slider = Slider(f022_slider_ax, 'f0_22 [GHz]', 340.0, 500.0, valinit=340.0)
f022_slider_ax.set_visible(False)
#Define WR buttons
WR10_button_ax = fig.add_axes([0.025, 0.9, 0.05, 0.05])
WR10_button = Button(WR10_button_ax, 'WR1.0:', color=axis_color, hovercolor='0.975')
WR15_button_ax = fig.add_axes([0.025, 0.8, 0.05, 0.05])
WR15_button = Button(WR15_button_ax, 'WR1.5:', color=axis_color, hovercolor='0.975')
WR22_button_ax = fig.add_axes([0.025, 0.7, 0.05, 0.05])
WR22_button = Button(WR22_button_ax, 'WR2.2:', color=axis_color, hovercolor='0.975')
#Define Event on Buttons
def WR10_button_on_clicked(mouse_event):
f010_slider_ax.set_visible(True)
WR10_button.on_clicked(WR10_button_on_clicked)
def WR15_button_on_clicked(mouse_event):
f015_slider_ax.set_visible(True)
WR15_button.on_clicked(WR15_button_on_clicked)
def WR22_button_on_clicked(mouse_event):
f022_slider_ax.set_visible(True)
WR22_button.on_clicked(WR22_button_on_clicked)
plt.show()

How do I extend the margin at the bottom of a figure in Matplotlib?

The following screenshot shows my x-axis.
I added some labels and rotated them by 90 degrees in order to better read them. However, pyplot truncates the bottom such that I'm not able to completely read the labels.
How do I extend the bottom margin in order to see the complete labels?
Two retroactive ways:
fig, ax = plt.subplots()
# ...
fig.tight_layout()
Or
fig.subplots_adjust(bottom=0.2) # or whatever
Here's a subplots_adjust example: http://matplotlib.org/examples/pylab_examples/subplots_adjust.html
(but I prefer tight_layout)
A quick one-line solution that has worked for me is to use pyplot's auto tight_layout method directly, available in Matplotlib v1.1 onwards:
plt.tight_layout()
This can be invoked immediately before you show the plot (plt.show()), but after your manipulations on the axes (e.g. ticklabel rotations, etc).
This convenience method avoids manipulating individual figures of subplots.
Where plt is the standard pyplot from:
import matplotlib.pyplot as plt
fig.savefig('name.png', bbox_inches='tight')
works best for me, since it doesn't reduce the plot size compared to
fig.tight_layout()
Subplot-adjust did not work for me, since the whole figure would just resize with the labels still out of bounds.
A workaround I found was to keep the y-axis always a certain margin over the highest or minimum y-values:
x1,x2,y1,y2 = plt.axis()
plt.axis((x1,x2,y1 - 100 ,y2 + 100))
fig, ax = plt.subplots(tight_layout=True)
This is rather complicated, but it gives a general and neat solution.
import numpy as np
value1 = 3
xvalues = [0, 1, 2, 3, 4]
line1 = [2.0, 3.0, 2.0, 5.0, 4.0]
stdev1 = [0.1, 0.2, 0.1, 0.4, 0.3]
line2 = [1.7, 3.1, 2.5, 4.8, 4.2]
stdev2 = [0.12, 0.18, 0.12, 0.3, 0.35]
max_times = [max(line1+stdev1),max(line2+stdev2)]
min_times = [min(line1+stdev1),min(line2+stdev2)]
font_size = 25
max_total = max(max_times)
min_total = min(min_times)
max_minus_min = max_total - min_total
step_size = max_minus_min/10
head_space = (step_size*3)
plt.figure(figsize=(15, 15))
plt.errorbar(xvalues, line1, yerr=stdev1, fmt='', color='b')
plt.errorbar(xvalues, line2, yerr=stdev2, fmt='', color='r')
plt.xlabel("xvalues", fontsize=font_size)
plt.ylabel("lines 1 and 2 Test "+str(value1), fontsize=font_size)
plt.title("Let's leave space for the legend Experiment"+ str(value1), fontsize=font_size)
plt.legend(("Line1", "Line2"), loc="upper left", fontsize=font_size)
plt.tick_params(labelsize=font_size)
plt.yticks(np.arange(min_total, max_total+head_space, step=step_size) )
plt.grid()
plt.tight_layout()
Result:

How to format slider

I have a slider:
time_ax = fig.add_axes([0.1, 0.05, 0.8, 0.03])
var_time = Slider(time_ax, 'Time', 0, 100, valinit=10, valfmt='%0.0f')
var_time.on_changed(update)
and I want to customize the appearance of this slider:
I can add axisbg parameter to add_axes function, which will change default white background to assigned color, but that's all I see possible for now.
So, how to change other slider components:
silder border (default: black)
default value indicator (default: red)
slider progress (default: blue)
The slider border is just the spines of the Axes instance. The progress bar can be directly accessed for basic customization in the constructor, and the initial status indicator is an attribute of the slider. I was able to change all of those things:
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
fig = plt.figure()
time_ax = fig.add_axes([0.1, 0.05, 0.8, 0.03])
# Facecolor and edgecolor control the slider itself
var_time = Slider(time_ax, 'Time', 0, 100, valinit=10, valfmt='%0.0f',
facecolor='c', edgecolor='r')
# The vline attribute controls the initial value line
var_time.vline.set_color('blue')
# The spines of the axis control the borders
time_ax.spines['left'].set_color('magenta')
time_ax.spines['right'].set_color('magenta')
time_ax.spines['bottom'].set_color('magenta')
time_ax.spines['top'].set_color('magenta')
The color of the box you can change when you define the axis of the "ax" box:
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.03, 0.25, 0.03, 0.65], axisbg=axcolor)
axamp = plt.axes([0.08, 0.25, 0.03, 0.65], axisbg=axcolor)
sfreq = Slider(axfreq, 'Freq', 0.1, 30.0, valinit=f0)
samp = Slider(axamp, 'Amp', 0.1, 10.0, valinit=a0)
# The vline attribute controls the initial value line
samp.vline.set_color('green')