4

我正在尝试做一个简单的按钮来将小部件“重置”为某些默认值。我@interact在 Jupyter Lab 环境中使用装饰器。问题是小部件标识符的值被复制到函数内用作浮点变量的相同标识符中,因此我无法再在这个新范围内访问它们。这是一个简短的示例(不起作用):

import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import interact, Button

@interact(starts_at=(0, np.pi*0.9, np.pi*0.1), ends_at=(np.pi, 2*np.pi, np.pi*0.1))
def plot_graph(starts_at=0, ends_at=2*np.pi):
    
    def on_button_clicked(_):
        # instructions when clicking the button (this cannot work)
        starts_at = 0
        ends_at = 2*np.pi
        
    button = Button(description="Reset")
    button.on_click(on_button_clicked)
    display(button)
    
    f = lambda x : sum(1/a*np.sin(a*x + np.pi/a) for a in range(1,6))
    x = np.linspace(0, 2*np.pi, 1000)
    plt.plot(x, f(x))
    plt.xlim([starts_at, ends_at])

有人知道如何将对原始小部件对象的引用发送到装饰函数的范围吗?我也将接受实现按钮以重置这些滑块的简单方法。

:-D

编辑:更正的文本流

4

3 回答 3

3

为此,您必须使用更多手动interactive_output功能。该功能允许您预先创建小部件,然后将它们传入:

import ipywidgets as widgets
import numpy as np
import matplotlib.pyplot as plt

start_slider = widgets.FloatSlider(
                            val = 0,
                            min = 0,
                            max = np.pi*0.9,
                            step = np.pi*0.1,
                            description = 'Starts at'
                            )
end_slider = widgets.FloatSlider(
                            val = np.pi,
                            min = np.pi,
                            max = 2*np.pi,
                            step = np.pi*0.1,
                            description = 'Ends at'
                            )
def on_button_clicked(_):
    start_slider.value = 0
    end_slider.value = 2*np.pi

button = Button(description="Reset")
button.on_click(on_button_clicked)
def plot_graph(starts_at=0, ends_at=2*np.pi):
    f = lambda x : sum(1/a*np.sin(a*x + np.pi/a) for a in range(1,6))
    x = np.linspace(0, 2*np.pi, 1000)
    plt.plot(x, f(x))
    plt.xlim([starts_at, ends_at])

display(widgets.VBox([start_slider, end_slider, button]))
widgets.interactive_output(plot_graph, {'starts_at': start_slider, 'ends_at':end_slider})

但是,这将在您每次更新时完全重新生成情节,这可能会导致不稳定的体验。因此,您也可以重新编写它以使用 matplotlib 方法,就像.set_data您在笔记本中使用交互式 matplotlib 后端一样。因此,如果您要使用ipympl,您可以按照本示例笔记本中的示例进行操作。

通过另一个图书馆

我编写了一个库mpl-interactions,以便更轻松地使用 ipywidgets 滑块控制 matplotlib 图。它提供了一个类似于ipywidgets.interact它为您处理创建小部件的功能,但它具有专注于 matplotlib 的优势,因此您需要提供的只是数据。更多关于 ipywidgets 的区别在这里

%matplotlib ipympl
import mpl_interactions.ipyplot as iplt
import matplotlib.pyplot as plt
import numpy as np
import ipywidgets as widgets

def plot_graph(starts_at=0, ends_at=2*np.pi):
    x = np.linspace(starts_at, ends_at, 1000)
    f = lambda x : sum(1/a*np.sin(a*x + np.pi/a) for a in range(1,6))
    return np.array([x, f(x)]).T


fig, ax = plt.subplots()
button = widgets.Button(description = 'reset')
display(button)
controls = iplt.plot(plot_graph, starts_at = (0, np.pi), ends_at = (np.pi, 2*np.pi), xlim='auto', parametric=True)
def on_click(event):
    for hbox in controls.controls.values():
        slider = hbox.children[0]
        slider.value = slider.min
button.on_click(on_click)
于 2020-09-18T17:32:54.827 回答
1

我可以在不使用装饰器的情况下弄清楚它@interact(现在它正在工作),但我对最终结果并不满意。所以,我仍然愿意为可以做出清晰/更简单的pythonic版本的人提供正确的答案状态。

无论如何,这是工作代码:

import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import interact, Button, FloatSlider

def plot_graph(starts_at, ends_at):
    f = lambda x : sum(1/a*np.sin(a*x + np.pi/a) for a in range(1,6))
    x = np.linspace(0, 2*np.pi, 1000)
    plt.plot(x, f(x))
    plt.xlim([starts_at, ends_at])
    
starts_at = FloatSlider(min=0, max=np.pi*0.9, value=0, step=np.pi*0.1)
ends_at = FloatSlider(min=np.pi, max=2*np.pi, value=2*np.pi, step=np.pi*0.1)

def on_button_clicked(_):
    starts_at.value = 0
    ends_at.value = 2*np.pi

button = Button(description="Reset")
button.on_click(on_button_clicked)
display(button)
_ = interact(plot_graph, starts_at=starts_at, ends_at=ends_at)

编辑:从这一点开始的新方法===============================

我选择@Ianhi 的答案是正确的,因为他指出了在我的问题背景下要考虑的问题。谢谢!

无论如何,我在这里发布了我正在使用的最终方案,该方案很简单,可以满足我的需求,并且我可以在所有交互中重复使用我的重置按钮:

# Preamble ----
import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import interact, Button, FloatSlider

def reset_button(defaults={}):
    def on_button_clicked(_):
        for k, v in defaults.items(): 
            k.value = v
    button = Button(description='Reset')
    button.on_click(on_button_clicked)
    display(button)

# Code ----
slider1 = FloatSlider(min=0, max=np.pi*0.9, value=0, step=np.pi*0.1)
slider2 = FloatSlider(min=np.pi, max=2*np.pi, value=2*np.pi, step=np.pi*0.1)
reset_button(defaults={slider1: 0, slider2: 2*np.pi})

@interact(starts_at=slider1, ends_at=slider2)
def plot_graph(starts_at, ends_at):
    f = lambda x : sum(1/a*np.sin(a*x + np.pi/a) for a in range(1,6))
    x = np.linspace(0, 2*np.pi, 1000)
    plt.plot(x, f(x))
    plt.xlim([starts_at, ends_at])
于 2020-09-18T17:12:54.390 回答
0

从@iperetta 的答案构建,您可以创建一个简单的装饰器,为每次使用添加一个重置按钮:

根据上述答案重置按钮:

def reset_button(defaults={}):
    def on_button_clicked(_):
        for k, v in defaults.items(): 
            k.value = v
    button = widgets.Button(description='Reset')
    button.on_click(on_button_clicked)
    display(button)

装饰师:

def interact_plus_reset(**_widgets):
  default_vals = {wid:wid._trait_values['value'] for k, wid in _widgets.items()}
  reset_button(defaults=default_vals)
  def wrap(func):
    @wraps(func)
    @widgets.interact(**_widgets)
    def inner(*args, **kwargs):
      return func(*args, **kwargs)
    return inner
  return wrap

然后如下使用它:

@interact_plus_reset(
  a = widgets.FloatSlider(min=1, max=2000, value=35, step=1), 
  b = widgets.FloatSlider(min=1, max=2000, value=1000, step=1), 
)
def run(a, b):
  print(a + b)
于 2021-05-17T22:32:13.147 回答