Skip to content

Add debounce option to interactive #2204

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion ipywidgets/widgets/interaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,60 @@
from traitlets import HasTraits, Any, Unicode, observe
from numbers import Real, Integral
from warnings import warn
from collections import Iterable, Mapping
from collections import Iterable, Mapping, defaultdict
import functools
import time
import IPython
import zmq


empty = Parameter.empty


def _get_ioloop():
ipython = IPython.get_ipython()
if ipython and hasattr(ipython, 'kernel'):
return zmq.eventloop.ioloop.IOLoop.instance()


def debounced(delay_seconds=0.5, method=False):
"""Returns a decorator, that (in IPython) will create a debounced function or method.

A debounced function/method will only be executed once it has not been invoked for at least
the delay time.

Parameters
----------
delay_seconds : float
Delay in seconds before the function will be executed.
method : bool
If the argument of the returned decorator expects a method (True) of function (False).

"""
def wrapped(f):
counters = defaultdict(int)

@functools.wraps(f)
def execute(*args, **kwargs):
if method: # if it is a method, we want to have a counter per instance
key = args[0]
else:
key = None
counters[key] += 1

def debounced_execute(counter=counters[key]):
if counter == counters[key]: # only execute if the counter wasn't changed in the meantime
f(*args, **kwargs)
ioloop = _get_ioloop()

def thread_safe():
ioloop.add_timeout(time.time() + delay_seconds, debounced_execute)

ioloop.add_callback(thread_safe)
return execute
return wrapped


def show_inline_matplotlib_plots():
"""Show matplotlib plots immediately if using the inline backend.

Expand Down Expand Up @@ -179,6 +228,10 @@ def __init__(self, __interact_f, __options={}, **kwargs):
self.manual = __options.get("manual", False)
self.manual_name = __options.get("manual_name", "Run Interact")
self.auto_display = __options.get("auto_display", False)
self.debounce = kwargs.get("debounce", None)

if self.debounce is not None:
self.update = debounced(delay_seconds=self.debounce)(self.update)

new_kwargs = self.find_abbreviations(kwargs)
# Before we proceed, let's make sure that the user has passed a set of args+kwargs
Expand Down