3d interactive graph won't update - python

I was using this code to create an interactive plot (2d), and it works.
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 = 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], axisbg=axcolor)
axamp = plt.axes([0.25, 0.15, 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)
rax = plt.axes([0.025, 0.5, 0.15, 0.15], axisbg=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()
I then tried to modify it to create an interactive 3d plot by simply changing the axes to axes3d. I added the import statement shown below and replaced the definition of "fig" and "ax" with those shown below to become 3d.
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
The plot no longer updates, and I can't figure out why. It seems that the function fig.canvas.draw_idle() doesn't work on 3d graphs, but I don't have another way of updating the graph.
Any help would be appreciated,
Thanks!

You can see that the function fig.canvas.draw_idle() does exist and works as expected by looking at the colors which become updated.
The problem lies in the set_ydata function, which works differently in 3d space.
Assuming that you want the y coordinate to update and the z-coordinate to be constant, set_data will be given the constant values, while an additional property set_3d_properties() needs to be set to control the y-coordinate.
Here is the working example code:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.widgets import Slider, Button, RadioButtons
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
plt.subplots_adjust(left=0.25, bottom=0.25)
t = np.arange(0.0, 1.0, 0.001)
### create constant z-coordinate
z = np.zeros_like(t) # <------------ here
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])
axfreq = plt.axes([0.25, 0.1, 0.65, 0.03], axisbg="w")
axamp = plt.axes([0.25, 0.15, 0.65, 0.03], axisbg="w")
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
#set constant z coordinate
l.set_data(t, z) # <------------ here
# set values to y-coordinate
l.set_3d_properties(amp*np.sin(2*np.pi*freq*t), zdir="y") #<------------ here
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], axisbg=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()

Related

Adjust matplotlib RadioButtons font size

I wonder if anyone has an advice on how to adjust the font size on the RadiouButtons side text. Taking the widget example from matplotlib
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import RadioButtons
t = np.arange(0.0, 2.0, 0.01)
s0 = np.sin(2*np.pi*t)
s1 = np.sin(4*np.pi*t)
s2 = np.sin(8*np.pi*t)
fig, ax = plt.subplots()
l, = ax.plot(t, s0, lw=2, color='red')
fig.subplots_adjust(left=0.3)
axcolor = 'lightgoldenrodyellow'
rax = fig.add_axes([0.05, 0.7, 0.15, 0.15], facecolor=axcolor)
radio = RadioButtons(rax, ('2 Hz', '4 Hz', '8 Hz'))
def hzfunc(label):
hzdict = {'2 Hz': s0, '4 Hz': s1, '8 Hz': s2}
ydata = hzdict[label]
l.set_ydata(ydata)
plt.draw()
radio.on_clicked(hzfunc)
rax = fig.add_axes([0.05, 0.4, 0.15, 0.15], facecolor=axcolor)
radio2 = RadioButtons(rax, ('red', 'blue', 'green'))
def colorfunc(label):
l.set_color(label)
plt.draw()
radio2.on_clicked(colorfunc)
rax = fig.add_axes([0.05, 0.1, 0.15, 0.15], facecolor=axcolor)
radio3 = RadioButtons(rax, ('-', '--', '-.', ':'))
def stylefunc(label):
l.set_linestyle(label)
plt.draw()
radio3.on_clicked(stylefunc)
plt.show()
How could I make "2Hz, 4Hz, 8Hz" larger?
It is possible to adjust the buttons size by accessing the Circle objects on the RadioButtons. Is there something similar to the font size? (the buttons widget have the .label.set_fontsize() attribute but I have not found a similar thing for this widget)
In order to change the font size of the RadioButton labels you need to access the properties of the individual label:
for r in radio.labels: r.set_fontsize(16)

Struggling to get widgets working in python

I don't know why but I am really struggling to get widgets working well in python. I try to look at examples about how to use them but I don't know how to extrapolate that to get it to work with my code. I am trying to get a figure to display widgets such that the type, frequency, phase, and other variables adjust the graph itself.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.widgets as widgets
from scipy import signal
from matplotlib.widgets import RadioButtons
A = 1
ang_f = 5
t = np.linspace(0, 4*np.pi, 1000)
phase = 0
s0 = A*np.sin(ang_f*t + phase)
s2 = A*signal.sawtooth(ang_f*t + phase)
s1 = A*signal.square(ang_f*t + phase)
fig, ax = plt.subplots()
l, = ax.plot(t, s1, lw=2, color='red')
plt.subplots_adjust(left=0.4)
def sinf(x, omega):
return np.sin(omega*x)
def sliderCallback(val):
# """ 'val' is the current value selected by the slider
# Recalculate sine values with val as the frequency """
axesHandle.set_ydata(sinf(x, val))
plt.draw() # Redraw the axes
def clickcallback(val):
# 'val' is the current value selected by the slider
# Recalculate sine values with val as the frequency
axesHandle.set_ydata(sinf(x, val))
plt.draw() # Redraw the axes
def closeCallback(event):
plt.close('all') # Close all open figure windows
fig = plt.figure(figsize=(7, 5))
ax = plt.axes([0.1, 0.2, 0.6, 0.7])
axesHandle, = plt.plot(x, sinf(x, 1), lw=2, color='red')
# Add axis to contain the slider
fax = plt.axes([0.1, 0.04, 0.35, 0.03]) # Frequency
tax = plt.axes([0.1, 0.12, 0.35, 0.03]) # Time
sax_3 = plt.axes([0.60, 0.1, 0.35, 0.03]) # Number of points
pax = plt.axes([0.60, 0.05, 0.35, 0.03]) # Phase
rax = plt.axes([0.85, 0.65, 0.12, 0.15]) # Type
bax = plt.axes([0.85, 0.85, 0.1, 0.1]) # Close
pointshandle = widgets.Slider(sax_3, 'Number of points', 1, 200,
valfmt='%0.0f')
pointshandle.on_changed(sliderCallback)
graphchoice = widgets.RadioButtons(rax, ('Sine', 'Squarewave', 'Sawtooth'))
graphchoice.on_clicked(clickcallback)
freqhandle = widgets.Slider(fax, 'Frequancy (Hz)', 0, 5, valinit=1)
freqhandle.on_changed(sliderCallback)
phasehandle = widgets.Slider(pax, 'Phase', 0, 0*np.pi, valinit=0)
phasehandle.on_changed(sliderCallback)
timehandle = widgets.Slider(tax, 'Time (s)', 1, 10, valinit=1)
timehandle.on_changed(sliderCallback)
buttonHandle = widgets.Button(bax, 'Close')
buttonHandle.on_clicked(closeCallback)
def hzfunc(label):
hzdict = {'Sine': s0, 'Squarewave': s1, 'Sawtooth': s2}
ydata = hzdict[label]
l.set_ydata(ydata)
plt.draw()
graphchoice.on_clicked(hzfunc)
I'm really lost so any tips to put me on the right path would be much appreciated, im just so confused atm.

Matplotlib could not generate an accurate slider plot

I have the following equation:
y = ((b-6(x**k))/c)**(1/k)
k = 10/(6+c)
I know that when k > 1 then y is concave and when 0 < k < 1 then y is convex. However, the problem is that in the generated plot it does not matter whatever the value of k is, it always generates a concave y. I was wondering if anybody can help me to figure out what is the problem.
Codes to generate the dynamic plot:
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)
x = np.arange(0.0, 1.0, 0.001)
b_init = 1
c_init = 0
k = 10/(6+c_init)
delta_f = 1.0
y = ((b_init-6*(x**k))/c_init)**(1/k)
l, = plt.plot(x, y, lw=2)
ax.margins(x=0)
axcolor = 'lightgoldenrodyellow'
ax_b = plt.axes([0.25, 0.1, 0.65, 0.03], facecolor=axcolor)
ax_c = plt.axes([0.25, 0.15, 0.65, 0.03], facecolor=axcolor)
s_b = Slider(ax_b, 'b', 0.1, 18.0, valinit=b_init, valstep=delta_f)
s_c = Slider(ax_c, 'c', 0.1, 12.0, valinit=c_init)
def update(val):
b = s_b.val
c = s_c.val
l.set_ydata(((b-6*(x**k))/c)**(1/k))
fig.canvas.draw_idle()
s_b.on_changed(update)
s_c.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):
s_b.reset()
s_c.reset()
button.on_clicked(reset)
def colorfunc(label):
l.set_color(label)
fig.canvas.draw_idle()
plt.show()
In case you are working with juypter notebooks you can use the widgets from ipywidgets as shown below.
Also, to get an intuition, it might help if you print out the b,c and k values.
%matplotlib inline
import numpy as np
import matplotlib.pyplot as p
from ipywidgets import *
def y(x,b,c):
k = 10/(6+c)
print(f' b={b:.3f},c={c:.3f},k={k:.3f}')
y = ((b-6*(x**k))/c)**(1/k)
return y
def inter(b0,c0):
y1=y(x,b0,c0)
p.figure(figsize=(20,6))
p.plot(x,y1)
dx=0.001
x = np.arange(0, 1.0+dx, dx) # assuming you want to go to 1 inclusively
b0=widgets.FloatSlider(value=10,min=-1,max=18.0,step=0.01,
description='b0',
continuous_update=False,
readout_format='.3f',
layout=Layout(width='90%', height='20px'))
c0=widgets.FloatSlider(value=0.1,min=-1,max=12.0,step=0.01,
description='c0',
continuous_update=False,
readout_format='.3f',
layout=Layout(width='90%', height='20px'))
interact(inter, b0=b0,c0=c0);

Adding additional sliders in matplotlib

I am trying to create a third slider to control my plot.
fig, ax = plt.subplots()
plt.subplots_adjust(left=0.25, bottom=0.25)
l, = plt.plot(u,v, lw=1, color='red')
plt.axis([-20, 20, -20,20])
amp_slider_ax = fig.add_axes([0.25, 0.15, 0.65, 0.03], axisbg=axis_color)
samp = Slider(amp_slider_ax, 'Ey', 1, 10.0, valinit=a0)
freq_slider_ax = fig.add_axes([0.25, 0.1, 0.65, 0.03], axisbg=axis_color)
sfreq = Slider(freq_slider_ax, 'gamma (Ex/Ey)', 0.01, 1.3, valinit=f0)
#new slider
fbz_slider_ax = fig.add_axes([3, 7, 0.65, 0.03], axisbg=axis_color)
sbz = Slider(fbz_slider_ax, 'Bz', 0.01, 1.3, valinit=b0)
I don't see why my third slider is not being initialized. Can someone provide an example with 3 sliders, please. When I call the slider object, I do not get any errors either.
In the line fig.add_axes([3, 7, 0.65, 0.03]) you are adding an axes at coordinates (3,7). The point (3,7) does not lie inside the figure, as the figure goes from 0 to 1 in both directions.
The solution is of course to add the axes somewhere inside the figure.

set_ydata for subplots OR why is figure containing two subplots significantly slower than figure without subpots

I've extended following example to use multiple subplots. Here is my code:
#!/usr/bin/python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
########################################
t = np.arange(0.0, 1.0, 0.001)
a0 = 5
f0 = 3
s = a0*np.sin(2*np.pi*f0*t)
########################################
plt.close('all')
fig, ax = plt.subplots(nrows=2, ncols=1)
plt.subplots_adjust(bottom=0.30)
########################################
ax[0].plot(t,s, lw=2, color='red', label="red")
ax[1].plot(t,s, lw=2, color='green', label="green")
########################################
# plt.axis([0, 1, -10, 10])
ax[0].set_xlim([0, 1])
ax[0].set_ylim([-10, 10])
ax[1].set_xlim([0, 1])
ax[1].set_ylim([-10, 10])
########################################
axcolor = 'lightgoldenrodyellow'
f1 = plt.axes([0.25, 0.20, 0.65, 0.03], axisbg=axcolor)
a1 = plt.axes([0.25, 0.15, 0.65, 0.03], axisbg=axcolor)
f2 = plt.axes([0.25, 0.1, 0.65, 0.03], axisbg=axcolor)
a2 = plt.axes([0.25, 0.05, 0.65, 0.03], axisbg=axcolor)
sf1 = Slider(f1, 'Freq1', 0.1, 30.0, valinit=f0)
sa1 = Slider(a1, 'Amp1', 0.1, 10.0, valinit=a0)
sf2 = Slider(f2, 'Freq2', 0.1, 30.0, valinit=f0)
sa2 = Slider(a2, 'Amp2', 0.1, 10.0, valinit=a0)
########################################
def update1(val):
amp = sa1.val
freq = sf1.val
# DOES NOT WORKS - set_ydata DOES NOT EXISTS
# ax[1].set_ydata(amp*np.sin(2*np.pi*freq*t))
# ax[2].set_ydata(amp*np.sin(2*np.pi*freq*t))
# WORKS BUT IT SEEMS SLOW
s = amp*np.sin(2*np.pi*freq*t)
ax[0].clear()
ax[0].plot(t,s, lw=2, color='red', label="red")
ax[0].set_xlim([0, 1])
ax[0].set_ylim([-10, 10])
# THIS HAS NO EFFECT ON SPEED
fig.canvas.draw_idle()
sf1.on_changed(update1)
sa1.on_changed(update1)
def update2(val):
amp = sa2.val
freq = sf2.val
# DOES NOT WORKS - set_ydata DOES NOT EXISTS
# ax[1].set_ydata(amp*np.sin(2*np.pi*freq*t))
# ax[2].set_ydata(amp*np.sin(2*np.pi*freq*t))
# WORKS BUT IT SEEMS SLOW
s = amp*np.sin(2*np.pi*freq*t)
ax[1].clear()
ax[1].plot(t,s, lw=2, color='green', label="green")
ax[1].set_xlim([0, 1])
ax[1].set_ylim([-10, 10])
# THIS HAS NO EFFECT ON SPEED
fig.canvas.draw_idle()
sf2.on_changed(update2)
sa2.on_changed(update2)
plt.show()
The only problem is that it is slower (when the slider is clicked) than the original. I suspect this is caused by bunch of code that is used in update1 and update2 functions. I do not know how to rewrite it more effectively. The original example is using the set_ydata function but seems that subplots does not have this function. I've been also thinking about one update function for all four sliders, but I do not know if is it possible to distinguish the object on which the update was triggered and handle it inside the function. Thanks
You try to call set_ydata on the ax[0] (or ax[1], respectively). This obviously does not work, as these are axes instances. set_ydata is a method of Line2D artists. In the example that you have linked a line l is created in the beginning, which receives the new y data in the update function. You forgot that in your code.
So you need to create the lines:
line0, = ax[0].plot(t,s, lw=2, color='red')
line1, = ax[1].plot(t,s, lw=2, color='green')
in the beginning and then in your update functions you can call:
line0.set_ydata(amp*np.sin(2*np.pi*freq*t))
or
line1.set_ydata(amp*np.sin(2*np.pi*freq*t))
Note: The idea here is to only change the y coordinates of your lines. With your workaround, you deleted the entire graph and recreated it. This is more work and therefore it makes things slower.

Categories