Pyplot滑块在Jupyter Notebook中未更新图表线条。

huangapple go评论57阅读模式
英文:

Pyplot slider not updating plot lines in Jupyter Notebook

问题

%matplotlib notebook

import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button
import math

y_inits = [95, 5, 0]
c = 1
d = 5
fStepSize = 0.01
iLower = 0
iUpper = 1

tDerivatives = [
    lambda t, y1, y2, y3: -c*y1*y2,
    lambda t, y1, y2, y3: (c*y1*y2) - (d*y2),
    lambda t, y1, y2, y3: d*y2
]
   
tColors = ["blue", "red", "yellow", "green", "black", "purple"]
    
def RK4(c, d, fStepSize):
    
    tY_est = [ [y_inits[i]] for i in range(len(tDerivatives)) ]
    tT = [iLower]
    
    iRange = iUpper - iLower
    n = math.ceil(iRange / fStepSize)    
    
    for i in range(n+1)[1:]:
        
        fT_last = tT[i-1]
        fT_new = fT_last + fStepSize
        
        tK = []
        
        for j in range(len(tDerivatives)):
            Derivative = tDerivatives[j]
            fK1 = fStepSize * Derivative(fT_last, *[tY_est[k][i-1] for k in range(len(tY_est))])
            tK.append(["y'"+str(j+1), fK1])
        for j in range(len(tDerivatives)):
            Derivative = tDerivatives[j]
            fK2 = fStepSize * Derivative(fT_last + (fStepSize/2), *[tY_est[k][i-1] + (tK[k][1]/2) for k in range(len(tY_est))])
            tK[j].append(fK2)
        for j in range(len(tDerivatives)):
            Derivative = tDerivatives[j]
            fK3 = fStepSize * Derivative(fT_last + (fStepSize/2), *[tY_est[k][i-1] + (tK[k][2]/2) for k in range(len(tY_est))])
            tK[j].append(fK3)
        for j in range(len(tDerivatives)):
            Derivative = tDerivatives[j]
            fK4 = fStepSize * Derivative(fT_new, *[tY_est[k][i-1] + tK[k][3] for k in range(len(tY_est))])
            tK[j].append(fK4)    
        for j in range(len(tY_est)):
            fY_est_new = tY_est[j][i-1] + (( tK[j][1] + (2*tK[j][2]) + (2*tK[j][3]) + tK[j][4] )/6)
            tY_est[j].append(fY_est_new)
            
        tT.append(fT_new)

    return tT, tY_est 

fig, ax = plt.subplots()
plt.subplots_adjust(left=0.25, bottom=0.25)

tT_init, tY_est_init = RK4(c, d, fStepSize)
tPlots = [ ax.plot(tT_init, tY_est_init[i], marker=".", color=tColors[i])[0] for i in range(len(tDerivatives)) ]
plt.xlabel('t')
plt.ylabel('y(t)')
plt.title('h = '+str(fStepSize))

axfreq = fig.add_axes([0.25, 0.1, 0.65, 0.03])
c_slider = Slider(
    ax=axfreq,
    label='c',
    valmin=0.1,
    valmax=30,
    valinit=c,
)

def Update(val):
    tT, tY_est = RK4(c_slider.val, d, fStepSize)
    for i in range(len(tPlots)):
        tPlots[i].set_data(tT, tY_est[i])
    fig.canvas.draw()

c_slider.on_changed(Update)
英文:

I am trying to use the 4th order Runge-Kutta method to approximate the solution to a system of 1st order ODEs. The RK4 implementation itself is correct I think, if kind of janky - the plots it produces look like the right shape anyway - but it relies on 3 constants c, d and h, and I want to see how the solution changes as I vary those constants. I could just manually change them, but I wanted to make it interactive using sliders.

I want there to eventually be a slider for each of the 3 constants; right now I can't get even a single slider, the one for c, to work correctly. It's definitely... present, I can slide it back and forth, but the graph doesn't update as the value of c changes - even though I have defined a Slider.on_changed for it and have remembered to re-call the RK4 function with the slider value and re-set the line data:

%matplotlib notebook

import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button
import math

y_inits = [95, 5, 0]
c = 1
d = 5
fStepSize = 0.01
iLower = 0
iUpper = 1

tDerivatives = [
    lambda t, y1, y2, y3: -c*y1*y2,
    lambda t, y1, y2, y3: (c*y1*y2) - (d*y2),
    lambda t, y1, y2, y3: d*y2
]
   
tColors = ["blue", "red", "yellow", "green", "black", "purple"]
    
def RK4(c, d, fStepSize):
    
    tY_est = [ [y_inits[i]] for i in range(len(tDerivatives)) ]
    tT = [iLower]
    
    iRange = iUpper - iLower
    n = math.ceil(iRange / fStepSize)    
    
    for i in range(n+1)[1:]:
        
        fT_last = tT[i-1]
        fT_new = fT_last + fStepSize
        
        tK = []
        
        for j in range(len(tDerivatives)):
            Derivative = tDerivatives[j]
            fK1 = fStepSize * Derivative(fT_last, *[tY_est[k][i-1] for k in range(len(tY_est))])
            tK.append(["y'"+str(j+1), fK1])
        for j in range(len(tDerivatives)):
            Derivative = tDerivatives[j]
            fK2 = fStepSize * Derivative(fT_last + (fStepSize/2), *[tY_est[k][i-1] + (tK[k][1]/2) for k in range(len(tY_est))])
            tK[j].append(fK2)
        for j in range(len(tDerivatives)):
            Derivative = tDerivatives[j]
            fK3 = fStepSize * Derivative(fT_last + (fStepSize/2), *[tY_est[k][i-1] + (tK[k][2]/2) for k in range(len(tY_est))])
            tK[j].append(fK3)
        for j in range(len(tDerivatives)):
            Derivative = tDerivatives[j]
            fK4 = fStepSize * Derivative(fT_new, *[tY_est[k][i-1] + tK[k][3] for k in range(len(tY_est))])
            tK[j].append(fK4)    
        for j in range(len(tY_est)):
            fY_est_new = tY_est[j][i-1] + (( tK[j][1] + (2*tK[j][2]) + (2*tK[j][3]) + tK[j][4] )/6)
            tY_est[j].append(fY_est_new)
            
        tT.append(fT_new)

    return tT, tY_est 

fig, ax = plt.subplots()
plt.subplots_adjust(left=0.25, bottom=0.25)

tT_init, tY_est_init = RK4(c, d, fStepSize)
tPlots = [ ax.plot(tT_init, tY_est_init[i], marker=".", color=tColors[i]) for i in range(len(tDerivatives)) ]
plt.xlabel('t')
plt.ylabel('y(t)')
plt.title('h = '+str(fStepSize))

axfreq = fig.add_axes([0.25, 0.1, 0.65, 0.03])
c_slider = Slider(
    ax=axfreq,
    label='c',
    valmin=0.1,
    valmax=30,
    valinit=c,
)

def Update(val):
    tT, tY_est = RK4(c_slider.val, d, fStepSize)
    for i in range(len(tPlots)):
        tPlots[i].set_data(tT, tY_est[i])
    fig.canvas.draw()

c_slider.on_changed(Update)

No error messages are thrown.

I should note that I have used this slider demo from the matplotlib site itself as a base for my slider implementation. And their code works just fine, in that the resultant plot lines are responsive to the slider change, even in my environment - so it's not just a matter of "matplotlib isn't interactive in Jupyter Notebook".

I've also tried fiddling with the fig.canvas.draw() line in Update - maybe it's supposed to be draw_idle, or maybe I should use plt.draw(), or some other things, but none of them seem to have any effect.

What do I need to change to make the slider responsive?

EDIT:
There turned out to be two problems, as discussed in the comments of Yacine's answer. Besides how the lines are being instantiated (see Yacine's answer), the other problem is that the above code defines RK4 to not actually pass the c_slider.val to the RK4 subroutines that needed them. So the graph was updating... but using the exact same global c variable every time.

The full solution, with all 3 sliders I wanted, is as follows:

%matplotlib notebook

import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button
import math

y_inits = [95, 5, 0]
c_init = 1
d_init = 5
fStepSize = 0.01
iLower = 0
iUpper = 1

tDerivatives = [
    lambda c, d, t, y1, y2, y3: -c*y1*y2,
    lambda c, d, t, y1, y2, y3: (c*y1*y2) - (d*y2),
    lambda c, d, t, y1, y2, y3: d*y2
]
   
tColors = ["blue", "red", "yellow", "green", "black", "purple"]
    
def RK4(c, d, fStepSize):
    
    tY_est = [ [y_inits[i]] for i in range(len(tDerivatives)) ]
    tT = [iLower]
    
    iRange = iUpper - iLower
    n = math.ceil(iRange / fStepSize)    
    
    for i in range(n+1)[1:]:
        
        fT_last = tT[i-1]
        fT_new = fT_last + fStepSize
        
        tK = []
        
        for j in range(len(tDerivatives)):
            Derivative = tDerivatives[j]
            fK1 = fStepSize * Derivative(c, d, fT_last, *[tY_est[k][i-1] for k in range(len(tY_est))])
            tK.append(["y'"+str(j+1), fK1])
        for j in range(len(tDerivatives)):
            Derivative = tDerivatives[j]
            fK2 = fStepSize * Derivative(c, d, fT_last + (fStepSize/2), *[tY_est[k][i-1] + (tK[k][1]/2) for k in range(len(tY_est))])
            tK[j].append(fK2)
        for j in range(len(tDerivatives)):
            Derivative = tDerivatives[j]
            fK3 = fStepSize * Derivative(c, d, fT_last + (fStepSize/2), *[tY_est[k][i-1] + (tK[k][2]/2) for k in range(len(tY_est))])
            tK[j].append(fK3)
        for j in range(len(tDerivatives)):
            Derivative = tDerivatives[j]
            fK4 = fStepSize * Derivative(c, d, fT_new, *[tY_est[k][i-1] + tK[k][3] for k in range(len(tY_est))])
            tK[j].append(fK4)    
        for j in range(len(tY_est)):
            fY_est_new = tY_est[j][i-1] + (( tK[j][1] + (2*tK[j][2]) + (2*tK[j][3]) + tK[j][4] )/6)
            tY_est[j].append(fY_est_new)
            
        tT.append(fT_new)

    return tT, tY_est 

fig, ax = plt.subplots()
plt.subplots_adjust(left=0.25, bottom=0.25)

plt.xlabel('t')
plt.ylabel('y(t)')
plt.title('h = '+str(fStepSize))

c_slider = Slider(
    ax=fig.add_axes([0.25, 0.1, 0.65, 0.03]),
    label='c',
    valmin=0.1,
    valmax=5,
    valinit=c_init,
)
d_slider = Slider(
    ax=fig.add_axes([0.25, 0.06, 0.65, 0.03]),
    label='d',
    valmin=0.1,
    valmax=10,
    valinit=d_init,
)
h_slider = Slider(
    ax=fig.add_axes([0.25, 0.02, 0.65, 0.03]),
    label='h',
    valmin=0.001,
    valmax=0.1,
    valinit=fStepSize,
)

tPlots = [ax.plot([], [], marker=".", color=tColors[i])[0] for i in range(len(tDerivatives))]


def Update(val):
    fStepSize = h_slider.val
    tT, tY_est = RK4(c_slider.val, d_slider.val, fStepSize)
    for i in range(len(tPlots)):
        tPlots[i].set_data(tT, tY_est[i])
    fig.canvas.draw_idle()
    ax.relim()
    ax.autoscale_view()


c_slider.on_changed(Update)
d_slider.on_changed(Update)
h_slider.on_changed(Update)

tT_init, tY_est_init = RK4(c_init, d_init, fStepSize)
for i in range(len(tPlots)):
    tPlots[i].set_data(tT_init, tY_est_init[i])
ax.relim()
ax.autoscale_view()
plt.show()

答案1

得分: 1

我认为问题在于您需要在Update函数中为曲线设置数据。您在RK4函数中为初始绘图设置了数据,但在滑块值更改时不会更新绘图。

fig, ax = plt.subplots()
plt.subplots_adjust(left=0.25, bottom=0.25)

# 为线条数据创建空列表
tPlots = [ax.plot([], [], marker=".", color=tColors[i])[0] for i in range(len(tDerivatives))]

def Update(val):
    tT, tY_est = RK4(c_slider.val, d, fStepSize)
    for i in range(len(tPlots)):
        # 为每条线设置数据
        tPlots[i].set_data(tT, tY_est[i])
    ax.relim()
    ax.autoscale_view()
    fig.canvas.draw()

# 为线条设置初始数据
tT_init, tY_est_init = RK4(c, d, fStepSize)
for i in range(len(tPlots)):
    tPlots[i].set_data(tT_init, tY_est_init[i])

# 添加滑块
axfreq = fig.add_axes([0.25, 0.1, 0.65, 0.03])
c_slider = Slider(
    ax=axfreq,
    label='c',
    valmin=0.1,
    valmax=30,
    valinit=c,
)
c_slider.on_changed(Update)

plt.xlabel('t')
plt.ylabel('y(t)')
plt.title('h = '+str(fStepSize))
plt.show()

以上是您提供的代码的翻译部分。

英文:

I think the issue is that you need to set the data for the lines in the Update function. You're setting the data for the initial plot in the RK4 function, but that won't update the plot when the slider value changes.

 fig, ax = plt.subplots()
plt.subplots_adjust(left=0.25, bottom=0.25)
# create empty lists for the line data
tPlots = [ax.plot([], [], marker=".", color=tColors[i])[0] for i in range(len(tDerivatives))]
def Update(val):
tT, tY_est = RK4(c_slider.val, d, fStepSize)
for i in range(len(tPlots)):
# set the data for each line
tPlots[i].set_data(tT, tY_est[i])
ax.relim()
ax.autoscale_view()
fig.canvas.draw()
# set the initial data for the lines
tT_init, tY_est_init = RK4(c, d, fStepSize)
for i in range(len(tPlots)):
tPlots[i].set_data(tT_init, tY_est_init[i])
# add the slider
axfreq = fig.add_axes([0.25, 0.1, 0.65, 0.03])
c_slider = Slider(
ax=axfreq,
label='c',
valmin=0.1,
valmax=30,
valinit=c,
)
c_slider.on_changed(Update)
plt.xlabel('t')
plt.ylabel('y(t)')
plt.title('h = '+str(fStepSize))
plt.show()

答案2

得分: 0

以下是您要翻译的代码部分:

There's also the option of using ipywidgets' [interactive](https://ipywidgets.readthedocs.io/en/stable/examples/Using%20Interact.html#interactive) and letting it handle making the connections to the sliders and being responsive. It will actually make the sliders itself when supplied by tuples of the values desired like in the examples I'll point to below; however, as you wanted the initial settings I defined the sliders here.  
It should be something like this; however, I'm having a little problem putting it all together so that it makes the three lines correctly. **And so for now, this just demonstrates the slider handling and isn't plotting correctly (?!?!).** It does at least respond to changes in sliders:

# PLOT ISN'T WORKING RIGHT BUT SLIDERS SEEM RESPONSIVE.
from ipywidgets import interactive
import ipywidgets as widgets
import matplotlib.pyplot as plt
import numpy as np

from matplotlib.widgets import Slider, Button
import math

y_inits = [95, 5, 0]
c = 1
d = 5
fStepSize = 0.01
iLower = 0
iUpper = 1

tDerivatives = [
    lambda t, y1, y2, y3: -c*y1*y2,
    lambda t, y1, y2, y3: (c*y1*y2) - (d*y2),
    lambda t, y1, y2, y3: d*y2
]
   
tColors = ["blue", "red", "yellow", "green", "black", "purple"]
    
def RK4(c, d, fStepSize):
    
    tY_est = [ [y_inits[i]] for i in range(len(tDerivatives)) ]
    tT = [iLower]
    
    iRange = iUpper - iLower
    n = math.ceil(iRange / fStepSize)    
    
    for i in range(n+1)[1:]:
        
        fT_last = tT[i-1]
        fT_new = fT_last + fStepSize
        
        tK = []
        
        for j in range(len(tDerivatives)):
            Derivative = tDerivatives[j]
            fK1 = fStepSize * Derivative(fT_last, *[tY_est[k][i-1] for k in range(len(tY_est)])
            tK.append(["y"+str(j+1), fK1])
        for j in range(len(tDerivatives)):
            Derivative = tDerivatives[j]
            fK2 = fStepSize * Derivative(fT_last + (fStepSize/2), *[tY_est[k][i-1] + (tK[k][1]/2) for k in range(len(tY_est)])
            tK[j].append(fK2)
        for j in range(len(tDerivatives)):
            Derivative = tDerivatives[j]
            fK3 = fStepSize * Derivative(fT_last + (fStepSize/2), *[tY_est[k][i-1] + (tK[k][2]/2) for k in range(len(tY_est)])
            tK[j].append(fK3)
        for j in range(len(tDerivatives)):
            Derivative = tDerivatives[j]
            fK4 = fStepSize * Derivative(fT_new, *[tY_est[k][i-1] + tK[k][3] for k in range(len(tY_est)])
            tK[j].append(fK4)    
        for j in range(len(tY_est)):
            fY_est_new = tY_est[j][i-1] + (( tK[j][1] + (2*tK[j][2]) + (2*tK[j][3]) + tK[j][4] )/6)
            tY_est[j].append(fY_est_new)
            
        tT.append(fT_new)

    return tT, tY_est 

def f(c, d, fStepSize):
    #[l.remove() for l in ax.lines]
    fig, ax = plt.subplots()
    plt.subplots_adjust(left=0.25, bottom=0.25)
    plt.xlabel('t')
    plt.ylabel('y(t)')
    plt.title('h = '+str(fStepSize))
    tPlots = [ax.plot([], [], marker=".", color=tColors[i])[0] for i in range(len(tDerivatives))]

    tT, tY_est = RK4(c, d, fStepSize)
    for i in range(len(tPlots)):
        tPlots[i].set_data(tT, tY_est[i])
    plt.grid(True) #optional grid
    plt.show()

cc=widgets.FloatSlider(min=0.1,max=5,value=1) #Create an FloatSlider such that the range is [0.1,5] and default is 1
dc=widgets.FloatSlider(min=0.1,max=10,value=5) #Create an FloatSlider such that the range is [0.1,10] and default is 5
hc=widgets.FloatSlider(min=0.01,max=1,value=0.1) #Create an FloatSlider such that the range is [0.01,1] and default is 0.1
    
interactive_plot = interactive(f, c=cc, d=dc, fStepSize=hc)
interactive_plot

这是您提供的代码的翻译。

英文:

There's also the option of using ipywidgets' interactive and letting it handle making the connections to the sliders and being responsive. It will actually make the sliders itself when supplied by tuples of the values desired like in the examples I'll point to below; however, as you wanted the initial settings I defined the sliders here.
It should be something like this; however, I'm having a little problem putting it all together so that it makes the three lines correctly. And so for now, this just demonstrates the slider handling and isn't plotting correctly (?!?!). It does at least respond to changes in sliders:

# PLOT ISN'T WORKING RIGHT BUT SLIDERS SEEM RESPONSIVE.
from ipywidgets import interactive
import ipywidgets as widgets
import matplotlib.pyplot as plt
import numpy as np

from matplotlib.widgets import Slider, Button
import math

y_inits = [95, 5, 0]
c = 1
d = 5
fStepSize = 0.01
iLower = 0
iUpper = 1

tDerivatives = [
    lambda t, y1, y2, y3: -c*y1*y2,
    lambda t, y1, y2, y3: (c*y1*y2) - (d*y2),
    lambda t, y1, y2, y3: d*y2
]
   
tColors = ["blue", "red", "yellow", "green", "black", "purple"]
    
def RK4(c, d, fStepSize):
    
    tY_est = [ [y_inits[i]] for i in range(len(tDerivatives)) ]
    tT = [iLower]
    
    iRange = iUpper - iLower
    n = math.ceil(iRange / fStepSize)    
    
    for i in range(n+1)[1:]:
        
        fT_last = tT[i-1]
        fT_new = fT_last + fStepSize
        
        tK = []
        
        for j in range(len(tDerivatives)):
            Derivative = tDerivatives[j]
            fK1 = fStepSize * Derivative(fT_last, *[tY_est[k][i-1] for k in range(len(tY_est))])
            tK.append(["y'"+str(j+1), fK1])
        for j in range(len(tDerivatives)):
            Derivative = tDerivatives[j]
            fK2 = fStepSize * Derivative(fT_last + (fStepSize/2), *[tY_est[k][i-1] + (tK[k][1]/2) for k in range(len(tY_est))])
            tK[j].append(fK2)
        for j in range(len(tDerivatives)):
            Derivative = tDerivatives[j]
            fK3 = fStepSize * Derivative(fT_last + (fStepSize/2), *[tY_est[k][i-1] + (tK[k][2]/2) for k in range(len(tY_est))])
            tK[j].append(fK3)
        for j in range(len(tDerivatives)):
            Derivative = tDerivatives[j]
            fK4 = fStepSize * Derivative(fT_new, *[tY_est[k][i-1] + tK[k][3] for k in range(len(tY_est))])
            tK[j].append(fK4)    
        for j in range(len(tY_est)):
            fY_est_new = tY_est[j][i-1] + (( tK[j][1] + (2*tK[j][2]) + (2*tK[j][3]) + tK[j][4] )/6)
            tY_est[j].append(fY_est_new)
            
        tT.append(fT_new)

    return tT, tY_est 



def f(c, d, fStepSize):
    #[l.remove() for l in ax.lines]
    fig, ax = plt.subplots()
    plt.subplots_adjust(left=0.25, bottom=0.25)
    plt.xlabel('t')
    plt.ylabel('y(t)')
    plt.title('h = '+str(fStepSize))
    tPlots = [ax.plot([], [], marker=".", color=tColors[i])[0] for i in range(len(tDerivatives))]

    tT, tY_est = RK4(c, d, fStepSize)
    for i in range(len(tPlots)):
        tPlots[i].set_data(tT, tY_est[i])
    plt.grid(True) #optional grid
    plt.show()

cc=widgets.FloatSlider(min=0.1,max=5,value=1) #Create an FloatSlider such that the range is [0.1,5] and default is 1
dc=widgets.FloatSlider(min=0.1,max=10,value=5) #Create an FloatSlider such that the range is [0.1,10] and default is 5
hc=widgets.FloatSlider(min=0.01,max=1,value=0.1) #Create an FloatSlider such that the range is [0.01,1] and default is 0.1
    
interactive_plot = interactive(f, c=cc, d=dc, fStepSize=hc)
interactive_plot

This is based on here and here.

It'd be much better at selling ipywidgets' interactive if it actually made the same plot, but maybe eventually I'll see the issue and fix it.

huangapple
  • 本文由 发表于 2023年2月16日 03:06:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/75464401.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定