Skip to content

Commit fd221a4

Browse files
committed
Add numpy.linspace-like support to Float sliders
1 parent 9036526 commit fd221a4

File tree

4 files changed

+70
-4
lines changed

4 files changed

+70
-4
lines changed

docs/source/examples/Widget List.ipynb

+4-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
"cell_type": "markdown",
4848
"metadata": {},
4949
"source": [
50-
"### IntSlider \n",
50+
"### IntSlider\n",
5151
"- 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",
5252
"- The slider's label is defined by `description` parameter \n",
5353
"- The slider's `orientation` is either 'horizontal' (default) or 'vertical'\n",
@@ -84,7 +84,9 @@
8484
}
8585
},
8686
"source": [
87-
"### FloatSlider "
87+
"### FloatSlider\n",
88+
"- In addition to the `step` parameter, a `FloatSlider` also accepts 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",
89+
"- 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`)."
8890
]
8991
},
9092
{

ipywidgets/widgets/tests/test_interaction.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import os
99
from collections import OrderedDict
1010
import pytest
11+
from math import isclose
1112

1213
import ipywidgets as widgets
1314

@@ -44,7 +45,13 @@ def check_widget(w, **d):
4445
assert w.__class__ is expected
4546
else:
4647
value = getattr(w, attr)
47-
assert value == expected, "{}.{} = {!r} != {!r}".format(w.__class__.__name__, attr, value, expected)
48+
def strict_isequal(a, b, rel_tol=None):
49+
return a == b
50+
if isinstance(value, float):
51+
isequal = isclose
52+
else:
53+
isequal = strict_isequal
54+
assert isequal(value, expected, rel_tol=1e-10), "{}.{} = {!r} != {!r}".format(w.__class__.__name__, attr, value, expected)
4855

4956
# For numeric values, the types should match too
5057
if isinstance(value, (int, float)):

ipywidgets/widgets/widget_float.py

+55-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# Distributed under the terms of the Modified BSD License.
88

99
from traitlets import (
10-
Instance, Unicode, CFloat, Bool, CaselessStrEnum, Tuple, TraitError, validate, default
10+
Instance, Unicode, CFloat, CInt, Bool, CaselessStrEnum, Tuple, TraitError, validate, default
1111
)
1212
from .widget_description import DescriptionWidget
1313
from .trait_types import InstanceDict, NumberFormat
@@ -153,6 +153,12 @@ class FloatSlider(_BoundedFloat):
153153
maximal position of the slider
154154
step : float
155155
step of the trackbar
156+
num : integer
157+
number of values in the [min, max] interval, takes precedence over
158+
the step parameter.
159+
round : bool
160+
default is False, round the value set from Python to the closest value
161+
in the interval
156162
description : str
157163
name of the slider
158164
orientation : {'horizontal', 'vertical'}
@@ -167,6 +173,8 @@ class FloatSlider(_BoundedFloat):
167173
_view_name = Unicode('FloatSliderView').tag(sync=True)
168174
_model_name = Unicode('FloatSliderModel').tag(sync=True)
169175
step = CFloat(0.1, allow_none=True, help="Minimum step to increment the value").tag(sync=True)
176+
num = CInt(help="Number of values in the [min, max] interval").tag(sync=True)
177+
round = Bool(False, help="Round the value set from Python to the closest value in the interval").tag(sync=True)
170178
orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
171179
default_value='horizontal', help="Vertical or horizontal.").tag(sync=True)
172180
readout = Bool(True, help="Display the current value of the slider next to it.").tag(sync=True)
@@ -177,6 +185,33 @@ class FloatSlider(_BoundedFloat):
177185

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

188+
@validate('value')
189+
def _validate_value(self, proposal):
190+
value = proposal['value']
191+
if not self.round:
192+
return value
193+
# round value to closest one in interval
194+
values = [self.min]
195+
while values[-1] <= self.max:
196+
values.append(values[-1] + self.step)
197+
rounded_value = min(values, key=lambda x:abs(x-value))
198+
return rounded_value
199+
200+
@validate('num')
201+
def _validate_num(self, proposal):
202+
# step value is computed from num
203+
# num is not used in the front-end
204+
num = proposal['value']
205+
if self.min == self.max:
206+
if num != 1:
207+
raise TraitError('num must be 1 because min == max')
208+
self.step = 0
209+
elif num <= 1:
210+
raise TraitError('num must be greater than 1')
211+
else:
212+
self.step = (self.max - self.min) / (num - 1)
213+
return num
214+
180215

181216
@register
182217
class FloatLogSlider(_BoundedLogFloat):
@@ -337,6 +372,9 @@ class FloatRangeSlider(_BoundedFloatRange):
337372
maximal position of the slider
338373
step : float
339374
step of the trackbar
375+
num : integer
376+
number of values in the [min, max] interval, takes precedence over
377+
the step parameter.
340378
description : str
341379
name of the slider
342380
orientation : {'horizontal', 'vertical'}
@@ -351,6 +389,7 @@ class FloatRangeSlider(_BoundedFloatRange):
351389
_view_name = Unicode('FloatRangeSliderView').tag(sync=True)
352390
_model_name = Unicode('FloatRangeSliderModel').tag(sync=True)
353391
step = CFloat(0.1, allow_none=True, help="Minimum step to increment the value").tag(sync=True)
392+
num = CInt(help="Number of values in the [min, max] interval").tag(sync=True)
354393
orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
355394
default_value='horizontal', help="Vertical or horizontal.").tag(sync=True)
356395
readout = Bool(True, help="Display the current value of the slider next to it.").tag(sync=True)
@@ -360,3 +399,18 @@ class FloatRangeSlider(_BoundedFloatRange):
360399
disabled = Bool(False, help="Enable or disable user changes").tag(sync=True)
361400

362401
style = InstanceDict(SliderStyle).tag(sync=True, **widget_serialization)
402+
403+
@validate('num')
404+
def _validate_num(self, proposal):
405+
# step value is computed from num
406+
# num is not used in the front-end
407+
num = proposal['value']
408+
if self.min == self.max:
409+
if num != 1:
410+
raise TraitError('num must be 1 because min == max')
411+
self.step = 0
412+
elif num <= 1:
413+
raise TraitError('num must be greater than 1')
414+
else:
415+
self.step = (self.max - self.min) / (num - 1)
416+
return num

packages/schema/jupyterwidgetmodels.latest.md

+3
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ Attribute | Type | Default | Help
509509
`layout` | reference to Layout widget | reference to new instance |
510510
`max` | number (float) | `100.0` | Max value
511511
`min` | number (float) | `0.0` | Min value
512+
`num` | number (integer) | `0` | Number of values in the [min, max] interval
512513
`orientation` | string (one of `'horizontal'`, `'vertical'`) | `'horizontal'` | Vertical or horizontal.
513514
`readout` | boolean | `true` | Display the current value of the slider next to it.
514515
`readout_format` | string | `'.2f'` | Format for the readout
@@ -535,9 +536,11 @@ Attribute | Type | Default | Help
535536
`layout` | reference to Layout widget | reference to new instance |
536537
`max` | number (float) | `100.0` | Max value
537538
`min` | number (float) | `0.0` | Min value
539+
`num` | number (integer) | `0` | Number of values in the [min, max] interval
538540
`orientation` | string (one of `'horizontal'`, `'vertical'`) | `'horizontal'` | Vertical or horizontal.
539541
`readout` | boolean | `true` | Display the current value of the slider next to it.
540542
`readout_format` | string | `'.2f'` | Format for the readout
543+
`round` | boolean | `false` | Round the value set from Python to the closest value in the interval
541544
`step` | `null` or number (float) | `0.1` | Minimum step to increment the value
542545
`style` | reference to SliderStyle widget | reference to new instance |
543546
`tabbable` | `null` or boolean | `null` | Is widget tabbable?

0 commit comments

Comments
 (0)