【问题标题】:Implement a "reset" button when using @interact decorator in Jupyter在 Jupyter 中使用 @interact 装饰器时实现“重置”按钮
【发布时间】:2020-09-18 15:33:49
【问题描述】:

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

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

编辑:更正文本流

【问题讨论】:

    标签: python jupyter ipywidgets


    【解决方案1】:

    要完成此操作,您必须使用更手动的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,您可以按照this example notebook 中的示例进行操作。

    通过另一个库

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

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

    【讨论】:

      【解决方案2】:

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

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

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

      【讨论】:

      • 哎呀错过了你在发布我的答案之前发布了这个,因为我吃午饭时分心了。我的手动解决方案有点不同,因为它使用interactive_output。但我认为您提出的基本上是使用ipywidgets 执行此操作的正确方法。
      • mpl-interactions 解决方案起作用的原因是因为与 interact 不同,它会返回它生成的小部件,以便您可以用它们做更多的事情
      【解决方案3】:

      根据@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)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-02-29
        • 1970-01-01
        • 2015-10-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-08
        相关资源
        最近更新 更多