Skip to content

Commit d1997c5

Browse files
authored
Merge pull request #1928 from sebasguts/master
Added FloatLogSlider
2 parents bf1c6b2 + b4c47e6 commit d1997c5

File tree

4 files changed

+243
-2
lines changed

4 files changed

+243
-2
lines changed

ipywidgets/widgets/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from .widget_bool import Checkbox, ToggleButton, Valid
1212
from .widget_button import Button, ButtonStyle
1313
from .widget_box import Box, HBox, VBox
14-
from .widget_float import FloatText, BoundedFloatText, FloatSlider, FloatProgress, FloatRangeSlider
14+
from .widget_float import FloatText, BoundedFloatText, FloatSlider, FloatProgress, FloatRangeSlider, FloatLogSlider
1515
from .widget_image import Image
1616
from .widget_int import IntText, BoundedIntText, IntSlider, IntProgress, IntRangeSlider, Play, SliderStyle
1717
from .widget_color import ColorPicker

ipywidgets/widgets/widget_float.py

+76
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,40 @@ def _validate_max(self, proposal):
5858
self.value = max
5959
return max
6060

61+
class _BoundedLogFloat(_Float):
62+
max = CFloat(4.0, help="Max value for the exponent").tag(sync=True)
63+
min = CFloat(0.0, help="Min value for the exponent").tag(sync=True)
64+
base = CFloat(10.0, help="Base of value").tag(sync=True)
65+
value = CFloat(1.0, help="Float value").tag(sync=True)
66+
67+
@validate('value')
68+
def _validate_value(self, proposal):
69+
"""Cap and floor value"""
70+
value = proposal['value']
71+
if self.base ** self.min > value or self.base ** self.max < value:
72+
value = min(max(value, self.base ** self.min), self.base ** self.max)
73+
return value
74+
75+
@validate('min')
76+
def _validate_min(self, proposal):
77+
"""Enforce base ** min <= value <= base ** max"""
78+
min = proposal['value']
79+
if min > self.max:
80+
raise TraitError('Setting min > max')
81+
if self.base ** min > self.value:
82+
self.value = self.base ** min
83+
return min
84+
85+
@validate('max')
86+
def _validate_max(self, proposal):
87+
"""Enforce base ** min <= value <= base ** max"""
88+
max = proposal['value']
89+
if max < self.min:
90+
raise TraitError('setting max < min')
91+
if self.base ** max < self.value:
92+
self.value = self.base ** max
93+
return max
94+
6195

6296
@register
6397
class FloatText(_Float):
@@ -141,6 +175,48 @@ class FloatSlider(_BoundedFloat):
141175
continuous_update = Bool(True, help="Update the value of the widget as the user is holding the slider.").tag(sync=True)
142176
disabled = Bool(False, help="Enable or disable user changes").tag(sync=True)
143177

178+
style = InstanceDict(SliderStyle).tag(sync=True, **widget_serialization)
179+
180+
181+
@register
182+
class FloatLogSlider(_BoundedLogFloat):
183+
""" Slider/trackbar of logarithmic floating values with the specified range.
184+
185+
Parameters
186+
----------
187+
value : float
188+
position of the slider
189+
base : float
190+
base of the logarithmic scale. Default is 10
191+
min : float
192+
minimal position of the slider in log scale, i.e., actual minimum is base ** min
193+
max : float
194+
maximal position of the slider in log scale, i.e., actual maximum is base ** max
195+
step : float
196+
step of the trackbar, denotes steps for the exponent, not the actual value
197+
description : str
198+
name of the slider
199+
orientation : {'horizontal', 'vertical'}
200+
default is 'horizontal', orientation of the slider
201+
readout : {True, False}
202+
default is True, display the current value of the slider next to it
203+
readout_format : str
204+
default is '.2f', specifier for the format function used to represent
205+
slider value for human consumption, modeled after Python 3's format
206+
specification mini-language (PEP 3101).
207+
"""
208+
_view_name = Unicode('FloatLogSliderView').tag(sync=True)
209+
_model_name = Unicode('FloatLogSliderModel').tag(sync=True)
210+
step = CFloat(0.1, help="Minimum step in the exponent to increment the value").tag(sync=True)
211+
orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
212+
default_value='horizontal', help="Vertical or horizontal.").tag(sync=True)
213+
readout = Bool(True, help="Display the current value of the slider next to it.").tag(sync=True)
214+
readout_format = NumberFormat(
215+
'.2f', help="Format for the readout").tag(sync=True)
216+
continuous_update = Bool(True, help="Update the value of the widget as the user is holding the slider.").tag(sync=True)
217+
disabled = Bool(False, help="Enable or disable user changes").tag(sync=True)
218+
base = CFloat(10., help="Base for the logarithm").tag(sync=True)
219+
144220

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

packages/controls/src/widget_float.ts

+141-1
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ import {
1212
import * as _ from 'underscore';
1313

1414
import {
15-
IntSliderView, IntRangeSliderView, IntTextView
15+
IntSliderView, IntRangeSliderView, IntTextView, BaseIntSliderView
1616
} from './widget_int';
1717

1818
import {
1919
format
2020
} from 'd3-format';
2121

22+
2223
export
2324
class FloatModel extends CoreDescriptionModel {
2425
defaults() {
@@ -69,6 +70,39 @@ class FloatSliderModel extends BoundedFloatModel {
6970
readout_formatter: any;
7071
}
7172

73+
export
74+
class FloatLogSliderModel extends BoundedFloatModel {
75+
defaults() {
76+
return _.extend(super.defaults(), {
77+
_model_name: 'FloatLogSliderModel',
78+
_view_name: 'FloatLogSliderView',
79+
step: 0.1,
80+
orientation: 'horizontal',
81+
_range: false,
82+
readout: true,
83+
readout_format: '.2f',
84+
slider_color: null,
85+
continuous_update: true,
86+
disabled: false,
87+
base: 10.,
88+
value: 1.0,
89+
min: 0,
90+
max: 4
91+
});
92+
}
93+
initialize(attributes, options) {
94+
super.initialize(attributes, options);
95+
this.on('change:readout_format', this.update_readout_format, this);
96+
this.update_readout_format();
97+
}
98+
99+
update_readout_format() {
100+
this.readout_formatter = format(this.get('readout_format'));
101+
}
102+
103+
readout_formatter: any;
104+
}
105+
72106
export
73107
class FloatRangeSliderModel extends FloatSliderModel {}
74108

@@ -85,6 +119,112 @@ class FloatSliderView extends IntSliderView {
85119
_parse_value = parseFloat;
86120
}
87121

122+
123+
export
124+
class FloatLogSliderView extends BaseIntSliderView {
125+
126+
update(options?) {
127+
super.update(options);
128+
let min = this.model.get('min');
129+
let max = this.model.get('max');
130+
let value = this.model.get('value');
131+
let base = this.model.get('base');
132+
133+
let log_value = Math.log( value ) / Math.log( base );
134+
135+
if(log_value > max) {
136+
log_value = max;
137+
} else if(log_value < min) {
138+
log_value = min;
139+
}
140+
this.$slider.slider('option', 'value', log_value);
141+
this.readout.textContent = this.valueToString(value);
142+
if(this.model.get('value') !== value) {
143+
this.model.set('value', value, {updated_view: this});
144+
this.touch();
145+
}
146+
}
147+
148+
/**
149+
* Write value to a string
150+
*/
151+
valueToString(value: number): string {
152+
let format = this.model.readout_formatter;
153+
return format(value);
154+
}
155+
156+
/**
157+
* Parse value from a string
158+
*/
159+
stringToValue(text: string): number {
160+
return this._parse_value(text);
161+
}
162+
163+
/**
164+
* this handles the entry of text into the contentEditable label first, the
165+
* value is checked if it contains a parseable value then it is clamped
166+
* within the min-max range of the slider finally, the model is updated if
167+
* the value is to be changed
168+
*
169+
* if any of these conditions are not met, the text is reset
170+
*/
171+
handleTextChange() {
172+
let value = this.stringToValue(this.readout.textContent);
173+
let vmin = this.model.get('min');
174+
let vmax = this.model.get('max');
175+
let base = this.model.get('base');
176+
177+
if (isNaN(value)) {
178+
this.readout.textContent = this.valueToString(this.model.get('value'));
179+
} else {
180+
value = Math.max(Math.min(value, Math.pow(base,vmax)), Math.pow(base,vmin));
181+
182+
if (value !== this.model.get('value')) {
183+
this.readout.textContent = this.valueToString(value);
184+
this.model.set('value', value, {updated_view: this});
185+
this.touch();
186+
} else {
187+
this.readout.textContent = this.valueToString(this.model.get('value'));
188+
}
189+
}
190+
}
191+
/**
192+
* Called when the slider value is changing.
193+
*/
194+
handleSliderChange(e, ui) {
195+
let base = this.model.get('base');
196+
let actual_value = Math.pow(base,this._validate_slide_value(ui.value));
197+
this.readout.textContent = this.valueToString(actual_value);
198+
199+
// Only persist the value while sliding if the continuous_update
200+
// trait is set to true.
201+
if (this.model.get('continuous_update')) {
202+
this.handleSliderChanged(e, ui);
203+
}
204+
}
205+
206+
/**
207+
* Called when the slider value has changed.
208+
*
209+
* Calling model.set will trigger all of the other views of the
210+
* model to update.
211+
*/
212+
handleSliderChanged(e, ui) {
213+
let base = this.model.get('base');
214+
let actual_value = Math.pow(base,this._validate_slide_value(ui.value));
215+
this.model.set('value', actual_value, {updated_view: this});
216+
this.touch();
217+
}
218+
219+
_validate_slide_value(x) {
220+
return x;
221+
}
222+
223+
_parse_value = parseFloat;
224+
225+
}
226+
227+
88228
export
89229
class FloatRangeSliderView extends IntRangeSliderView {
90230
/**

packages/schema/jupyterwidgetmodels.latest.md

+25
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,31 @@ Attribute | Type | Default | Help
296296
`layout` | reference to Layout widget | reference to new instance |
297297
`style` | reference to DescriptionStyle widget | reference to new instance | Styling customizations
298298

299+
### FloatLogSliderModel (@jupyter-widgets/controls, 1.1.0); FloatLogSliderView (@jupyter-widgets/controls, 1.1.0)
300+
301+
Attribute | Type | Default | Help
302+
-----------------|------------------|------------------|----
303+
`_dom_classes` | array | `[]` | CSS classes applied to widget DOM element
304+
`_model_module` | string | `'@jupyter-widgets/controls'` |
305+
`_model_module_version` | string | `'1.1.0'` |
306+
`_model_name` | string | `'FloatLogSliderModel'` |
307+
`_view_module` | string | `'@jupyter-widgets/controls'` |
308+
`_view_module_version` | string | `'1.1.0'` |
309+
`_view_name` | string | `'FloatLogSliderView'` |
310+
`base` | number (float) | `10.0` | Base for the logarithm
311+
`continuous_update` | boolean | `true` | Update the value of the widget as the user is holding the slider.
312+
`description` | string | `''` | Description of the control.
313+
`disabled` | boolean | `false` | Enable or disable user changes
314+
`layout` | reference to Layout widget | reference to new instance |
315+
`max` | number (float) | `4.0` | Max value for the exponent
316+
`min` | number (float) | `0.0` | Min value for the exponent
317+
`orientation` | string (one of `'horizontal'`, `'vertical'`) | `'horizontal'` | Vertical or horizontal.
318+
`readout` | boolean | `true` | Display the current value of the slider next to it.
319+
`readout_format` | string | `'.2f'` | Format for the readout
320+
`step` | number (float) | `0.1` | Minimum step in the exponent to increment the value
321+
`style` | reference to SliderStyle widget | reference to new instance |
322+
`value` | number (float) | `1.0` | Float value
323+
299324
### FloatProgressModel (@jupyter-widgets/controls, 1.1.0); ProgressView (@jupyter-widgets/controls, 1.1.0)
300325

301326
Attribute | Type | Default | Help

0 commit comments

Comments
 (0)