From 23184e3723ff24f6b603e59e45e086014884435c Mon Sep 17 00:00:00 2001 From: xavArtley Date: Tue, 6 Oct 2020 15:21:11 +0200 Subject: [PATCH] Implementation of (Int|Float)[Input|Spinner] (New Bokeh widgets) (#1513) --- examples/reference/widgets/FloatInput.ipynb | 119 ++++++++++++++++++++ examples/reference/widgets/IntInput.ipynb | 119 ++++++++++++++++++++ examples/reference/widgets/Spinner.ipynb | 93 --------------- panel/param.py | 12 +- panel/widgets/__init__.py | 3 + panel/widgets/input.py | 95 ++++++++++++++-- 6 files changed, 333 insertions(+), 108 deletions(-) create mode 100644 examples/reference/widgets/FloatInput.ipynb create mode 100644 examples/reference/widgets/IntInput.ipynb delete mode 100644 examples/reference/widgets/Spinner.ipynb diff --git a/examples/reference/widgets/FloatInput.ipynb b/examples/reference/widgets/FloatInput.ipynb new file mode 100644 index 0000000000..e7f5ae285b --- /dev/null +++ b/examples/reference/widgets/FloatInput.ipynb @@ -0,0 +1,119 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import panel as pn\n", + "\n", + "pn.extension()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The ``FloatInput`` widget allows selecting a floating point value using a spinbox. It behaves like a slider except that lower and upper bounds are optional and a specific value can be entered. Value can be changed using keyboard (up, down, page up, page down), mouse wheel and arrow buttons.\n", + "\n", + "For more information about listening to widget events and laying out widgets refer to the [widgets user guide](../../user_guide/Widgets.ipynb). Alternatively you can learn how to build GUIs by declaring parameters independently of any specific widgets in the [param user guide](../../user_guide/Param.ipynb). To express interactivity entirely using Javascript without the need for a Python server take a look at the [links user guide](../../user_guide/Param.ipynb).\n", + "\n", + "#### Parameters:\n", + "\n", + "For layout and styling related parameters see the [customization user guide](../../user_guide/Customization.ipynb).\n", + "\n", + "##### Core\n", + "\n", + "* **``value``** (float): The initial value of the spinner\n", + "* **``value_throttled``** (float): The initial value of the spinner\n", + "* **``step``** (float): The step added or subtracted to the current value on each click\n", + "* **``start``** (float): Optional minimum allowable value\n", + "* **``end``** (float): Optional maximum allowable value\n", + "* **``format``** (str): Optional format to convert the float value in string, see : http://numbrojs.com/old-format.html\n", + "* **``page_step_multiplier``** (int): Defines the multiplication factor applied to step when the page up and page down keys are pressed.\n", + "\n", + "##### Display\n", + "\n", + "* **``disabled``** (boolean): Whether the widget is editable\n", + "* **``name``** (str): The title of the widget\n", + "* **``placeholder``** (str): A placeholder string displayed when no value is entered\n", + "\n", + "___" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "float_input = pn.widgets.FloatInput(name='FloatInput', value=5., step=1e-1, start=0, end=1000)\n", + "\n", + "float_input" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "``FloatInput.value`` returns a float value:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "float_input.value" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Controls\n", + "\n", + "The `FloatSpinner` widget exposes a number of options which can be changed from both Python and Javascript. Try out the effect of these parameters interactively:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pn.Row(float_input.controls(jslink=True), float_input)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.5" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": {}, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/reference/widgets/IntInput.ipynb b/examples/reference/widgets/IntInput.ipynb new file mode 100644 index 0000000000..4e96d8f5b3 --- /dev/null +++ b/examples/reference/widgets/IntInput.ipynb @@ -0,0 +1,119 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import panel as pn\n", + "\n", + "pn.extension()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The ``IntInput`` widget allows selecting an integer value using a spinbox. It behaves like a slider except that lower and upper bounds are optional and a specific value can be entered. Value can be changed using keyboard (up, down, page up, page down), mouse wheel and arrow buttons.\n", + "\n", + "For more information about listening to widget events and laying out widgets refer to the [widgets user guide](../../user_guide/Widgets.ipynb). Alternatively you can learn how to build GUIs by declaring parameters independently of any specific widgets in the [param user guide](../../user_guide/Param.ipynb). To express interactivity entirely using Javascript without the need for a Python server take a look at the [links user guide](../../user_guide/Param.ipynb).\n", + "\n", + "#### Parameters:\n", + "\n", + "For layout and styling related parameters see the [customization user guide](../../user_guide/Customization.ipynb).\n", + "\n", + "##### Core\n", + "\n", + "* **``value``** (int): The initial value of the spinner\n", + "* **``value_throttled``** (int): The initial value of the spinner\n", + "* **``step``** (int): The step added or subtracted to the current value on each click\n", + "* **``start``** (int): Optional minimum allowable value\n", + "* **``end``** (int): Optional maximum allowable value\n", + "* **``format``** (str): Optional format to convert the float value in string, see : http://numbrojs.com/old-format.html\n", + "* **``page_step_multiplier``** (int): Defines the multiplication factor applied to step when the page up and page down keys are pressed.\n", + "\n", + "##### Display\n", + "\n", + "* **``disabled``** (boolean): Whether the widget is editable\n", + "* **``name``** (str): The title of the widget\n", + "* **``placeholder``** (str): A placeholder string displayed when no value is entered\n", + "\n", + "___" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "int_input = pn.widgets.IntInput(name='IntInput', value=5, step=2, start=0, end=1000)\n", + "\n", + "int_input" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "``IntInput.value`` returns an integer value:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "int_input.value" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Controls\n", + "\n", + "The `IntInput` widget exposes a number of options which can be changed from both Python and Javascript. Try out the effect of these parameters interactively:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pn.Row(int_input.controls(jslink=True), int_input)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.5" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": {}, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/reference/widgets/Spinner.ipynb b/examples/reference/widgets/Spinner.ipynb deleted file mode 100644 index e948566974..0000000000 --- a/examples/reference/widgets/Spinner.ipynb +++ /dev/null @@ -1,93 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import panel as pn\n", - "\n", - "pn.extension()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The ``Spinner`` widget allows selecting a value using a spinbox. It behaves like a slider except that lower and upper bounds are optional and a specific value can be entered.\n", - "\n", - "For more information about listening to widget events and laying out widgets refer to the [widgets user guide](../../user_guide/Widgets.ipynb). Alternatively you can learn how to build GUIs by declaring parameters independently of any specific widgets in the [param user guide](../../user_guide/Param.ipynb). To express interactivity entirely using Javascript without the need for a Python server take a look at the [links user guide](../../user_guide/Param.ipynb).\n", - "\n", - "#### Parameters:\n", - "\n", - "For layout and styling related parameters see the [customization user guide](../../user_guide/Customization.ipynb).\n", - "\n", - "##### Core\n", - "\n", - "* **``value``** (float or int): The initial value of the spinner\n", - "* **``step``** (float or int): The step added or subtracted to the current value on each click\n", - "* **``start``** (float or int): Optional minimum allowable value\n", - "* **``end``** (float or int): Optional maximum allowable value\n", - "\n", - "##### Display\n", - "\n", - "* **``disabled``** (boolean): Whether the widget is editable\n", - "* **``name``** (str): The title of the widget" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "spinner = pn.widgets.Spinner(name='Spinner', value=5., step=1e-1, start=0, end=10)\n", - "\n", - "spinner" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "``Spinner.value`` returns a float or int value:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "spinner.value" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Controls\n", - "\n", - "The `Spinner` widget exposes a number of options which can be changed from both Python and Javascript. Try out the effect of these parameters interactively:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pn.Row(spinner.controls(jslink=True), spinner)" - ] - } - ], - "metadata": { - "language_info": { - "name": "python", - "pygments_lexer": "ipython3" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/panel/param.py b/panel/param.py index f7ced39470..e75d57fa83 100644 --- a/panel/param.py +++ b/panel/param.py @@ -28,10 +28,10 @@ ) from .viewable import Layoutable from .widgets import ( - Button, Checkbox, ColorPicker, DataFrame, DatePicker, DatetimeInput, - DateRangeSlider, FileSelector, FloatSlider, IntSlider, LiteralInput, - MultiSelect, RangeSlider, Select, Spinner, StaticText, TextInput, - Toggle, Widget + Button, Checkbox, ColorPicker, DataFrame, DatePicker, + DatetimeInput, DateRangeSlider, FileSelector, FloatSlider, + IntInput, IntSlider, LiteralInput, MultiSelect, RangeSlider, + Select, FloatInput, StaticText, TextInput, Toggle, Widget ) from .widgets.button import _ButtonBase @@ -382,9 +382,9 @@ def widget(self, p_name): if not widget_class_overridden: if (isinstance(p_obj, param.Number) and not isinstance(p_obj, (param.Date, param.CalendarDate))): - widget_class = Spinner + widget_class = FloatInput if isinstance(p_obj, param.Integer): - kw['step'] = 1 + widget_class = IntInput elif not issubclass(widget_class, LiteralInput): widget_class = LiteralInput if hasattr(widget_class, 'step') and getattr(p_obj, 'step', None): diff --git a/panel/widgets/__init__.py b/panel/widgets/__init__.py index 3e98c73fda..733c61c241 100644 --- a/panel/widgets/__init__.py +++ b/panel/widgets/__init__.py @@ -25,6 +25,9 @@ LiteralInput, StaticText, TextInput, + IntInput, + FloatInput, + NumberInput, Spinner, PasswordInput, TextAreaInput, diff --git a/panel/widgets/input.py b/panel/widgets/input.py index 3df2883f55..1495202f23 100644 --- a/panel/widgets/input.py +++ b/panel/widgets/input.py @@ -17,7 +17,8 @@ CheckboxGroup as _BkCheckboxGroup, ColorPicker as _BkColorPicker, DatePicker as _BkDatePicker, Div as _BkDiv, TextInput as _BkTextInput, PasswordInput as _BkPasswordInput, Spinner as _BkSpinner, - FileInput as _BkFileInput, TextAreaInput as _BkTextAreaInput) + FileInput as _BkFileInput, TextAreaInput as _BkTextAreaInput, + NumericInput as _BkNumericInput) from ..util import as_unicode from .base import Widget @@ -165,30 +166,106 @@ class ColorPicker(Widget): _rename = {'value': 'color', 'name': 'title'} -class Spinner(Widget): +class _NumericInputBase(Widget): - start = param.Number(default=None, doc=""" + value = param.Number(default=0, allow_None=True, doc=""" + The initial value of the spinner.""") + + placeholder = param.String(default='0', doc=""" + Placeholder for empty input field.""") + + format = param.String(default=None, allow_None=True, doc=""" + Number formating : http://numbrojs.com/old-format.html .""") + + _rename = {'name': 'title', 'start': 'low', 'end': 'high'} + + _widget_type = _BkNumericInput + + __abstract = True + + +class _IntInputBase(_NumericInputBase): + + value = param.Integer(default=0, allow_None=True, doc=""" + The initial value of the spinner.""") + + start = param.Integer(default=None, allow_None=True, doc=""" Optional minimum allowable value.""") - end = param.Number(default=None, doc=""" + end = param.Integer(default=None, allow_None=True, doc=""" Optional maximum allowable value.""") - value = param.Number(default=0, doc=""" + mode = param.String(default='int', constant=True, doc=""" + Define the type of number which can be enter in the input""") + + __abstract = True + + +class _FloatInputBase(_NumericInputBase): + + value = param.Number(default=0, allow_None=True, doc=""" The initial value of the spinner.""") - step = param.Number(default=1, doc=""" - The step added or subtracted to the current value.""") + start = param.Number(default=None, allow_None=True, doc=""" + Optional minimum allowable value.""") + + end = param.Number(default=None, allow_None=True, doc=""" + Optional maximum allowable value.""") + + mode = param.String(default='float', constant=True, doc=""" + Define the type of number which can be enter in the input""") + + __abstract = True + + +class _SpinnerBase(_NumericInputBase): + + page_step_multiplier = param.Integer(default=10, bounds=(0, None), doc=""" + Defines the multiplication factor applied to step when the page up + and page down keys are pressed.""") + + wheel_wait = param.Integer(default=100, doc=""" + Defines the debounce time in ms before updating `value_throttled` when + the mouse wheel is used to change the input.""") _widget_type = _BkSpinner - _rename = {'name': 'title', 'start': 'low', 'end': 'high'} + __abstract = True def __init__(self, **params): if params.get('value') is None: value = params.get('start', self.value) if value is not None: params['value'] = value - super(Spinner, self).__init__(**params) + super(_SpinnerBase, self).__init__(**params) + + +class IntInput(_SpinnerBase, _IntInputBase): + + step = param.Integer(default=1) + + value_throttled = param.Integer(default=None, allow_None=True) + + +class FloatInput(_SpinnerBase, _FloatInputBase): + + step = param.Number(default=0.1) + + value_throttled = param.Number(default=None, allow_None=True) + + +class NumberInput: + + def __new__(self, **params): + param_list = ["value", "start", "stop", "step"] + if all(isinstance(params.get(p, 0), int) for p in param_list): + return IntInput(**params) + else: + return FloatInput(**params) + + +# Backward compatibility +Spinner = NumberInput class LiteralInput(Widget):