Skip to content

Add num and round parameters to Float sliders #2719

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 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
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
6 changes: 4 additions & 2 deletions docs/source/examples/Widget List.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"### IntSlider \n",
"### IntSlider\n",
"- The slider is displayed with a specified, initial `value`. Lower and upper bounds are defined by `min` and `max`, and the value can be incremented according to the `step` parameter.\n",
"- The slider's label is defined by `description` parameter \n",
"- The slider's `orientation` is either 'horizontal' (default) or 'vertical'\n",
Expand Down Expand Up @@ -84,7 +84,9 @@
}
},
"source": [
"### FloatSlider "
"### FloatSlider\n",
"- In addition to the `step` parameter, float sliders (`FloatSlider`, `FloatLogSlider` and `FloatRangeSlider`) also accept a `num` parameter, which specifies the number of values in the `[min, max]` interval. `num` takes precedence over `step`, and ensures that the `max` value is always included (which might not be the case due to floating point precision issues if the `step` parameter is used).\n",
"- A `round` parameter allows to round values that are set from the Python side to the closest value in the set of possible values. If `round` is `True`, values will be silently rounded (default value is `False`)."
]
},
{
Expand Down
9 changes: 8 additions & 1 deletion ipywidgets/widgets/tests/test_interaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import os
from collections import OrderedDict
import pytest
from math import isclose

import ipywidgets as widgets

Expand Down Expand Up @@ -44,7 +45,13 @@ def check_widget(w, **d):
assert w.__class__ is expected
else:
value = getattr(w, attr)
assert value == expected, "{}.{} = {!r} != {!r}".format(w.__class__.__name__, attr, value, expected)
def strict_isequal(a, b, rel_tol=None):
return a == b
if isinstance(value, float):
isequal = isclose
else:
isequal = strict_isequal
assert isequal(value, expected, rel_tol=1e-10), "{}.{} = {!r} != {!r}".format(w.__class__.__name__, attr, value, expected)

# For numeric values, the types should match too
if isinstance(value, (int, float)):
Expand Down
106 changes: 105 additions & 1 deletion ipywidgets/widgets/widget_float.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# Distributed under the terms of the Modified BSD License.

from traitlets import (
Instance, Unicode, CFloat, Bool, CaselessStrEnum, Tuple, TraitError, validate, default
Instance, Unicode, CFloat, CInt, Bool, CaselessStrEnum, Tuple, TraitError, validate, default
)
from .widget_description import DescriptionWidget
from .trait_types import InstanceDict, NumberFormat
Expand Down Expand Up @@ -153,6 +153,12 @@ class FloatSlider(_BoundedFloat):
maximal position of the slider
step : float
step of the trackbar
num : integer
number of values in the [min, max] interval, takes precedence over
the step parameter
round : bool
default is False, round the value set from Python to the closest value
in the interval
description : str
name of the slider
orientation : {'horizontal', 'vertical'}
Expand All @@ -167,6 +173,8 @@ class FloatSlider(_BoundedFloat):
_view_name = Unicode('FloatSliderView').tag(sync=True)
_model_name = Unicode('FloatSliderModel').tag(sync=True)
step = CFloat(0.1, allow_none=True, help="Minimum step to increment the value").tag(sync=True)
num = CInt(help="Number of values in the [min, max] interval").tag(sync=True)
round = Bool(False, help="Round the value set from Python to the closest value in the interval").tag(sync=True)
orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
default_value='horizontal', help="Vertical or horizontal.").tag(sync=True)
readout = Bool(True, help="Display the current value of the slider next to it.").tag(sync=True)
Expand All @@ -177,6 +185,33 @@ class FloatSlider(_BoundedFloat):

style = InstanceDict(SliderStyle).tag(sync=True, **widget_serialization)

@validate('value')
def _validate_value(self, proposal):
value = super()._validate_value(proposal)
if not self.round:
return value
# round value to closest one in interval
values = [self.min]
while values[-1] <= self.max:
values.append(values[-1] + self.step)
rounded_value = min(values, key=lambda x:abs(x-value))
return rounded_value

@validate('num')
def _validate_num(self, proposal):
# step value is computed from num
# num is not used in the front-end
num = proposal['value']
if self.min == self.max:
if num != 1:
raise TraitError('num must be 1 because min == max')
self.step = 0
elif num <= 1:
raise TraitError('num must be greater than 1')
else:
self.step = (self.max - self.min) / (num - 1)
return num


@register
class FloatLogSlider(_BoundedLogFloat):
Expand All @@ -194,6 +229,12 @@ class FloatLogSlider(_BoundedLogFloat):
maximal position of the slider in log scale, i.e., actual maximum is base ** max
step : float
step of the trackbar, denotes steps for the exponent, not the actual value
num : integer
number of values in the [min, max] interval, takes precedence over
the step parameter
round : bool
default is False, round the value set from Python to the closest value
in the interval
description : str
name of the slider
orientation : {'horizontal', 'vertical'}
Expand All @@ -208,6 +249,8 @@ class FloatLogSlider(_BoundedLogFloat):
_view_name = Unicode('FloatLogSliderView').tag(sync=True)
_model_name = Unicode('FloatLogSliderModel').tag(sync=True)
step = CFloat(0.1, allow_none=True, help="Minimum step in the exponent to increment the value").tag(sync=True)
num = CInt(help="Number of values in the [min, max] interval").tag(sync=True)
round = Bool(False, help="Round the value set from Python to the closest value in the interval").tag(sync=True)
orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
default_value='horizontal', help="Vertical or horizontal.").tag(sync=True)
readout = Bool(True, help="Display the current value of the slider next to it.").tag(sync=True)
Expand All @@ -219,6 +262,33 @@ class FloatLogSlider(_BoundedLogFloat):

style = InstanceDict(SliderStyle).tag(sync=True, **widget_serialization)

@validate('value')
def _validate_value(self, proposal):
value = super()._validate_value(proposal)
if not self.round:
return value
# round value to closest one in interval
values = [self.min]
while values[-1] <= self.max:
values.append(values[-1] + self.step)
rounded_value = min(values, key=lambda x:abs(x-value))
return rounded_value

@validate('num')
def _validate_num(self, proposal):
# step value is computed from num
# num is not used in the front-end
num = proposal['value']
if self.min == self.max:
if num != 1:
raise TraitError('num must be 1 because min == max')
self.step = 0
elif num <= 1:
raise TraitError('num must be greater than 1')
else:
self.step = (self.max - self.min) / (num - 1)
return num


@register
class FloatProgress(_BoundedFloat):
Expand Down Expand Up @@ -337,6 +407,11 @@ class FloatRangeSlider(_BoundedFloatRange):
maximal position of the slider
step : float
step of the trackbar
num : integer
number of values in the [min, max] interval, takes precedence over
the step parameter
round : bool
default is False, round the value set from Python to the closest value
description : str
name of the slider
orientation : {'horizontal', 'vertical'}
Expand All @@ -351,6 +426,8 @@ class FloatRangeSlider(_BoundedFloatRange):
_view_name = Unicode('FloatRangeSliderView').tag(sync=True)
_model_name = Unicode('FloatRangeSliderModel').tag(sync=True)
step = CFloat(0.1, allow_none=True, help="Minimum step to increment the value").tag(sync=True)
num = CInt(help="Number of values in the [min, max] interval").tag(sync=True)
round = Bool(False, help="Round the value set from Python to the closest value in the interval").tag(sync=True)
orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
default_value='horizontal', help="Vertical or horizontal.").tag(sync=True)
readout = Bool(True, help="Display the current value of the slider next to it.").tag(sync=True)
Expand All @@ -360,3 +437,30 @@ class FloatRangeSlider(_BoundedFloatRange):
disabled = Bool(False, help="Enable or disable user changes").tag(sync=True)

style = InstanceDict(SliderStyle).tag(sync=True, **widget_serialization)

@validate('value')
def _validate_value(self, proposal):
value = super()._validate_value(proposal)
if not self.round:
return value
# round value to closest one in interval
values = [self.min]
while values[-1] <= self.max:
values.append(values[-1] + self.step)
rounded_value = min(values, key=lambda x:abs(x-value))
return rounded_value

@validate('num')
def _validate_num(self, proposal):
# step value is computed from num
# num is not used in the front-end
num = proposal['value']
if self.min == self.max:
if num != 1:
raise TraitError('num must be 1 because min == max')
self.step = 0
elif num <= 1:
raise TraitError('num must be greater than 1')
else:
self.step = (self.max - self.min) / (num - 1)
return num
6 changes: 6 additions & 0 deletions packages/schema/jupyterwidgetmodels.latest.md
Original file line number Diff line number Diff line change
Expand Up @@ -461,9 +461,11 @@ Attribute | Type | Default | Help
`layout` | reference to Layout widget | reference to new instance |
`max` | number (float) | `4.0` | Max value for the exponent
`min` | number (float) | `0.0` | Min value for the exponent
`num` | number (integer) | `0` | Number of values in the [min, max] interval
`orientation` | string (one of `'horizontal'`, `'vertical'`) | `'horizontal'` | Vertical or horizontal.
`readout` | boolean | `true` | Display the current value of the slider next to it.
`readout_format` | string | `'.3g'` | Format for the readout
`round` | boolean | `false` | Round the value set from Python to the closest value in the interval
`step` | `null` or number (float) | `0.1` | Minimum step in the exponent to increment the value
`style` | reference to SliderStyle widget | reference to new instance |
`tabbable` | `null` or boolean | `null` | Is widget tabbable?
Expand Down Expand Up @@ -509,9 +511,11 @@ Attribute | Type | Default | Help
`layout` | reference to Layout widget | reference to new instance |
`max` | number (float) | `100.0` | Max value
`min` | number (float) | `0.0` | Min value
`num` | number (integer) | `0` | Number of values in the [min, max] interval
`orientation` | string (one of `'horizontal'`, `'vertical'`) | `'horizontal'` | Vertical or horizontal.
`readout` | boolean | `true` | Display the current value of the slider next to it.
`readout_format` | string | `'.2f'` | Format for the readout
`round` | boolean | `false` | Round the value set from Python to the closest value in the interval
`step` | `null` or number (float) | `0.1` | Minimum step to increment the value
`style` | reference to SliderStyle widget | reference to new instance |
`tabbable` | `null` or boolean | `null` | Is widget tabbable?
Expand All @@ -535,9 +539,11 @@ Attribute | Type | Default | Help
`layout` | reference to Layout widget | reference to new instance |
`max` | number (float) | `100.0` | Max value
`min` | number (float) | `0.0` | Min value
`num` | number (integer) | `0` | Number of values in the [min, max] interval
`orientation` | string (one of `'horizontal'`, `'vertical'`) | `'horizontal'` | Vertical or horizontal.
`readout` | boolean | `true` | Display the current value of the slider next to it.
`readout_format` | string | `'.2f'` | Format for the readout
`round` | boolean | `false` | Round the value set from Python to the closest value in the interval
`step` | `null` or number (float) | `0.1` | Minimum step to increment the value
`style` | reference to SliderStyle widget | reference to new instance |
`tabbable` | `null` or boolean | `null` | Is widget tabbable?
Expand Down