##// END OF EJS Templates
Add initial implementation of 2-handle range sliders for integers.
Gordon Ball -
Show More
@@ -1,311 +1,327 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // IntWidget
9 // IntWidget
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 **/
15 **/
16
16
17 define(["widgets/js/widget"], function(WidgetManager){
17 define(["widgets/js/widget"], function(WidgetManager){
18
18
19 var IntSliderView = IPython.DOMWidgetView.extend({
19 var IntSliderView = IPython.DOMWidgetView.extend({
20 render : function(){
20 render : function(){
21 // Called when view is rendered.
21 // Called when view is rendered.
22 this.$el
22 this.$el
23 .addClass('widget-hbox-single');
23 .addClass('widget-hbox-single');
24 this.$label = $('<div />')
24 this.$label = $('<div />')
25 .appendTo(this.$el)
25 .appendTo(this.$el)
26 .addClass('widget-hlabel')
26 .addClass('widget-hlabel')
27 .hide();
27 .hide();
28
28
29 this.$slider = $('<div />')
29 this.$slider = $('<div />')
30 .slider({})
30 .slider({})
31 .addClass('slider');
31 .addClass('slider');
32 // Put the slider in a container
32 // Put the slider in a container
33 this.$slider_container = $('<div />')
33 this.$slider_container = $('<div />')
34 .addClass('widget-hslider')
34 .addClass('widget-hslider')
35 .append(this.$slider);
35 .append(this.$slider);
36 this.$el_to_style = this.$slider_container; // Set default element to style
36 this.$el_to_style = this.$slider_container; // Set default element to style
37 this.$el.append(this.$slider_container);
37 this.$el.append(this.$slider_container);
38
38
39 this.$readout = $('<div/>')
39 this.$readout = $('<div/>')
40 .appendTo(this.$el)
40 .appendTo(this.$el)
41 .addClass('widget-hreadout')
41 .addClass('widget-hreadout')
42 .hide();
42 .hide();
43
43
44 // Set defaults.
44 // Set defaults.
45 this.update();
45 this.update();
46 },
46 },
47
47
48 update : function(options){
48 update : function(options){
49 // Update the contents of this view
49 // Update the contents of this view
50 //
50 //
51 // Called when the model is changed. The model may have been
51 // Called when the model is changed. The model may have been
52 // changed by another view or by a state update from the back-end.
52 // changed by another view or by a state update from the back-end.
53 if (options === undefined || options.updated_view != this) {
53 if (options === undefined || options.updated_view != this) {
54 // JQuery slider option keys. These keys happen to have a
54 // JQuery slider option keys. These keys happen to have a
55 // one-to-one mapping with the corrosponding keys of the model.
55 // one-to-one mapping with the corrosponding keys of the model.
56 var jquery_slider_keys = ['step', 'max', 'min', 'disabled'];
56 var jquery_slider_keys = ['step', 'max', 'min', 'disabled', 'range'];
57 var that = this;
57 var that = this;
58 _.each(jquery_slider_keys, function(key, i) {
58 _.each(jquery_slider_keys, function(key, i) {
59 var model_value = that.model.get(key);
59 var model_value = that.model.get(key);
60 if (model_value !== undefined) {
60 if (model_value !== undefined) {
61 that.$slider.slider("option", key, model_value);
61 that.$slider.slider("option", key, model_value);
62 }
62 }
63 });
63 });
64
64
65 // WORKAROUND FOR JQUERY SLIDER BUG.
65 // WORKAROUND FOR JQUERY SLIDER BUG.
66 // The horizontal position of the slider handle
66 // The horizontal position of the slider handle
67 // depends on the value of the slider at the time
67 // depends on the value of the slider at the time
68 // of orientation change. Before applying the new
68 // of orientation change. Before applying the new
69 // workaround, we set the value to the minimum to
69 // workaround, we set the value to the minimum to
70 // make sure that the horizontal placement of the
70 // make sure that the horizontal placement of the
71 // handle in the vertical slider is always
71 // handle in the vertical slider is always
72 // consistent.
72 // consistent.
73 var orientation = this.model.get('orientation');
73 var orientation = this.model.get('orientation');
74 var value = this.model.get('min');
74 var value = this.model.get('min');
75 this.$slider.slider('option', 'value', value);
75 if (this.model.get('range')) {
76 this.$slider.slider('option', 'values', [value, value]);
77 } else {
78 this.$slider.slider('option', 'value', value);
79 }
76 this.$slider.slider('option', 'orientation', orientation);
80 this.$slider.slider('option', 'orientation', orientation);
77 value = this.model.get('value');
81 value = this.model.get('value');
78 this.$slider.slider('option', 'value', value);
82 if (this.model.get('range')) {
79 this.$readout.text(value);
83 this.$slider.slider('option', 'values', value);
84 this.$readout.text(value.join("-"));
85 } else {
86 this.$slider.slider('option', 'value', value);
87 this.$readout.text(value);
88 }
89
80
90
81 // Use the right CSS classes for vertical & horizontal sliders
91 // Use the right CSS classes for vertical & horizontal sliders
82 if (orientation=='vertical') {
92 if (orientation=='vertical') {
83 this.$slider_container
93 this.$slider_container
84 .removeClass('widget-hslider')
94 .removeClass('widget-hslider')
85 .addClass('widget-vslider');
95 .addClass('widget-vslider');
86 this.$el
96 this.$el
87 .removeClass('widget-hbox-single')
97 .removeClass('widget-hbox-single')
88 .addClass('widget-vbox-single');
98 .addClass('widget-vbox-single');
89 this.$label
99 this.$label
90 .removeClass('widget-hlabel')
100 .removeClass('widget-hlabel')
91 .addClass('widget-vlabel');
101 .addClass('widget-vlabel');
92 this.$readout
102 this.$readout
93 .removeClass('widget-hreadout')
103 .removeClass('widget-hreadout')
94 .addClass('widget-vreadout');
104 .addClass('widget-vreadout');
95
105
96 } else {
106 } else {
97 this.$slider_container
107 this.$slider_container
98 .removeClass('widget-vslider')
108 .removeClass('widget-vslider')
99 .addClass('widget-hslider');
109 .addClass('widget-hslider');
100 this.$el
110 this.$el
101 .removeClass('widget-vbox-single')
111 .removeClass('widget-vbox-single')
102 .addClass('widget-hbox-single');
112 .addClass('widget-hbox-single');
103 this.$label
113 this.$label
104 .removeClass('widget-vlabel')
114 .removeClass('widget-vlabel')
105 .addClass('widget-hlabel');
115 .addClass('widget-hlabel');
106 this.$readout
116 this.$readout
107 .removeClass('widget-vreadout')
117 .removeClass('widget-vreadout')
108 .addClass('widget-hreadout');
118 .addClass('widget-hreadout');
109 }
119 }
110
120
111 var description = this.model.get('description');
121 var description = this.model.get('description');
112 if (description.length === 0) {
122 if (description.length === 0) {
113 this.$label.hide();
123 this.$label.hide();
114 } else {
124 } else {
115 this.$label.text(description);
125 this.$label.text(description);
116 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
126 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
117 this.$label.show();
127 this.$label.show();
118 }
128 }
119
129
120 var readout = this.model.get('readout');
130 var readout = this.model.get('readout');
121 if (readout) {
131 if (readout) {
122 this.$readout.show();
132 this.$readout.show();
123 } else {
133 } else {
124 this.$readout.hide();
134 this.$readout.hide();
125 }
135 }
126 }
136 }
127 return IntSliderView.__super__.update.apply(this);
137 return IntSliderView.__super__.update.apply(this);
128 },
138 },
129
139
130 events: {
140 events: {
131 // Dictionary of events and their handlers.
141 // Dictionary of events and their handlers.
132 "slide" : "handleSliderChange"
142 "slide" : "handleSliderChange"
133 },
143 },
134
144
135 handleSliderChange: function(e, ui) {
145 handleSliderChange: function(e, ui) {
136 // Called when the slider value is changed.
146 // Called when the slider value is changed.
137
147
138 // Calling model.set will trigger all of the other views of the
148 // Calling model.set will trigger all of the other views of the
139 // model to update.
149 // model to update.
140 var actual_value = this._validate_slide_value(ui.value);
150 if (this.model.get("range")) {
151 var actual_value = ui.values.map(this._validate_slide_value);
152 this.$readout.text(actual_value.join("-"));
153 } else {
154 var actual_value = this._validate_slide_value(ui.value);
155 this.$readout.text(actual_value);
156 }
141 this.model.set('value', actual_value, {updated_view: this});
157 this.model.set('value', actual_value, {updated_view: this});
142 this.$readout.text(actual_value);
143 this.touch();
158 this.touch();
159
144 },
160 },
145
161
146 _validate_slide_value: function(x) {
162 _validate_slide_value: function(x) {
147 // Validate the value of the slider before sending it to the back-end
163 // Validate the value of the slider before sending it to the back-end
148 // and applying it to the other views on the page.
164 // and applying it to the other views on the page.
149
165
150 // Double bit-wise not truncates the decimel (int cast).
166 // Double bit-wise not truncates the decimel (int cast).
151 return ~~x;
167 return ~~x;
152 },
168 },
153 });
169 });
154 WidgetManager.register_widget_view('IntSliderView', IntSliderView);
170 WidgetManager.register_widget_view('IntSliderView', IntSliderView);
155
171
156
172
157 var IntTextView = IPython.DOMWidgetView.extend({
173 var IntTextView = IPython.DOMWidgetView.extend({
158 render : function(){
174 render : function(){
159 // Called when view is rendered.
175 // Called when view is rendered.
160 this.$el
176 this.$el
161 .addClass('widget-hbox-single');
177 .addClass('widget-hbox-single');
162 this.$label = $('<div />')
178 this.$label = $('<div />')
163 .appendTo(this.$el)
179 .appendTo(this.$el)
164 .addClass('widget-hlabel')
180 .addClass('widget-hlabel')
165 .hide();
181 .hide();
166 this.$textbox = $('<input type="text" />')
182 this.$textbox = $('<input type="text" />')
167 .addClass('form-control')
183 .addClass('form-control')
168 .addClass('widget-numeric-text')
184 .addClass('widget-numeric-text')
169 .appendTo(this.$el);
185 .appendTo(this.$el);
170 this.$el_to_style = this.$textbox; // Set default element to style
186 this.$el_to_style = this.$textbox; // Set default element to style
171 this.update(); // Set defaults.
187 this.update(); // Set defaults.
172 },
188 },
173
189
174 update : function(options){
190 update : function(options){
175 // Update the contents of this view
191 // Update the contents of this view
176 //
192 //
177 // Called when the model is changed. The model may have been
193 // Called when the model is changed. The model may have been
178 // changed by another view or by a state update from the back-end.
194 // changed by another view or by a state update from the back-end.
179 if (options === undefined || options.updated_view != this) {
195 if (options === undefined || options.updated_view != this) {
180 var value = this.model.get('value');
196 var value = this.model.get('value');
181 if (this._parse_value(this.$textbox.val()) != value) {
197 if (this._parse_value(this.$textbox.val()) != value) {
182 this.$textbox.val(value);
198 this.$textbox.val(value);
183 }
199 }
184
200
185 if (this.model.get('disabled')) {
201 if (this.model.get('disabled')) {
186 this.$textbox.attr('disabled','disabled');
202 this.$textbox.attr('disabled','disabled');
187 } else {
203 } else {
188 this.$textbox.removeAttr('disabled');
204 this.$textbox.removeAttr('disabled');
189 }
205 }
190
206
191 var description = this.model.get('description');
207 var description = this.model.get('description');
192 if (description.length === 0) {
208 if (description.length === 0) {
193 this.$label.hide();
209 this.$label.hide();
194 } else {
210 } else {
195 this.$label.text(description);
211 this.$label.text(description);
196 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
212 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
197 this.$label.show();
213 this.$label.show();
198 }
214 }
199 }
215 }
200 return IntTextView.__super__.update.apply(this);
216 return IntTextView.__super__.update.apply(this);
201 },
217 },
202
218
203 events: {
219 events: {
204 // Dictionary of events and their handlers.
220 // Dictionary of events and their handlers.
205 "keyup input" : "handleChanging",
221 "keyup input" : "handleChanging",
206 "paste input" : "handleChanging",
222 "paste input" : "handleChanging",
207 "cut input" : "handleChanging",
223 "cut input" : "handleChanging",
208
224
209 // Fires only when control is validated or looses focus.
225 // Fires only when control is validated or looses focus.
210 "change input" : "handleChanged"
226 "change input" : "handleChanged"
211 },
227 },
212
228
213 handleChanging: function(e) {
229 handleChanging: function(e) {
214 // Handles and validates user input.
230 // Handles and validates user input.
215
231
216 // Try to parse value as a int.
232 // Try to parse value as a int.
217 var numericalValue = 0;
233 var numericalValue = 0;
218 if (e.target.value !== '') {
234 if (e.target.value !== '') {
219 var trimmed = e.target.value.trim();
235 var trimmed = e.target.value.trim();
220 if (!(['-', '-.', '.', '+.', '+'].indexOf(trimmed) >= 0)) {
236 if (!(['-', '-.', '.', '+.', '+'].indexOf(trimmed) >= 0)) {
221 numericalValue = this._parse_value(e.target.value);
237 numericalValue = this._parse_value(e.target.value);
222 }
238 }
223 }
239 }
224
240
225 // If parse failed, reset value to value stored in model.
241 // If parse failed, reset value to value stored in model.
226 if (isNaN(numericalValue)) {
242 if (isNaN(numericalValue)) {
227 e.target.value = this.model.get('value');
243 e.target.value = this.model.get('value');
228 } else if (!isNaN(numericalValue)) {
244 } else if (!isNaN(numericalValue)) {
229 if (this.model.get('max') !== undefined) {
245 if (this.model.get('max') !== undefined) {
230 numericalValue = Math.min(this.model.get('max'), numericalValue);
246 numericalValue = Math.min(this.model.get('max'), numericalValue);
231 }
247 }
232 if (this.model.get('min') !== undefined) {
248 if (this.model.get('min') !== undefined) {
233 numericalValue = Math.max(this.model.get('min'), numericalValue);
249 numericalValue = Math.max(this.model.get('min'), numericalValue);
234 }
250 }
235
251
236 // Apply the value if it has changed.
252 // Apply the value if it has changed.
237 if (numericalValue != this.model.get('value')) {
253 if (numericalValue != this.model.get('value')) {
238
254
239 // Calling model.set will trigger all of the other views of the
255 // Calling model.set will trigger all of the other views of the
240 // model to update.
256 // model to update.
241 this.model.set('value', numericalValue, {updated_view: this});
257 this.model.set('value', numericalValue, {updated_view: this});
242 this.touch();
258 this.touch();
243 }
259 }
244 }
260 }
245 },
261 },
246
262
247 handleChanged: function(e) {
263 handleChanged: function(e) {
248 // Applies validated input.
264 // Applies validated input.
249 if (this.model.get('value') != e.target.value) {
265 if (this.model.get('value') != e.target.value) {
250 e.target.value = this.model.get('value');
266 e.target.value = this.model.get('value');
251 }
267 }
252 },
268 },
253
269
254 _parse_value: function(value) {
270 _parse_value: function(value) {
255 // Parse the value stored in a string.
271 // Parse the value stored in a string.
256 return parseInt(value);
272 return parseInt(value);
257 },
273 },
258 });
274 });
259 WidgetManager.register_widget_view('IntTextView', IntTextView);
275 WidgetManager.register_widget_view('IntTextView', IntTextView);
260
276
261
277
262 var ProgressView = IPython.DOMWidgetView.extend({
278 var ProgressView = IPython.DOMWidgetView.extend({
263 render : function(){
279 render : function(){
264 // Called when view is rendered.
280 // Called when view is rendered.
265 this.$el
281 this.$el
266 .addClass('widget-hbox-single');
282 .addClass('widget-hbox-single');
267 this.$label = $('<div />')
283 this.$label = $('<div />')
268 .appendTo(this.$el)
284 .appendTo(this.$el)
269 .addClass('widget-hlabel')
285 .addClass('widget-hlabel')
270 .hide();
286 .hide();
271 this.$progress = $('<div />')
287 this.$progress = $('<div />')
272 .addClass('progress')
288 .addClass('progress')
273 .addClass('widget-progress')
289 .addClass('widget-progress')
274 .appendTo(this.$el);
290 .appendTo(this.$el);
275 this.$el_to_style = this.$progress; // Set default element to style
291 this.$el_to_style = this.$progress; // Set default element to style
276 this.$bar = $('<div />')
292 this.$bar = $('<div />')
277 .addClass('progress-bar')
293 .addClass('progress-bar')
278 .css('width', '50%')
294 .css('width', '50%')
279 .appendTo(this.$progress);
295 .appendTo(this.$progress);
280 this.update(); // Set defaults.
296 this.update(); // Set defaults.
281 },
297 },
282
298
283 update : function(){
299 update : function(){
284 // Update the contents of this view
300 // Update the contents of this view
285 //
301 //
286 // Called when the model is changed. The model may have been
302 // Called when the model is changed. The model may have been
287 // changed by another view or by a state update from the back-end.
303 // changed by another view or by a state update from the back-end.
288 var value = this.model.get('value');
304 var value = this.model.get('value');
289 var max = this.model.get('max');
305 var max = this.model.get('max');
290 var min = this.model.get('min');
306 var min = this.model.get('min');
291 var percent = 100.0 * (value - min) / (max - min);
307 var percent = 100.0 * (value - min) / (max - min);
292 this.$bar.css('width', percent + '%');
308 this.$bar.css('width', percent + '%');
293
309
294 var description = this.model.get('description');
310 var description = this.model.get('description');
295 if (description.length === 0) {
311 if (description.length === 0) {
296 this.$label.hide();
312 this.$label.hide();
297 } else {
313 } else {
298 this.$label.text(description);
314 this.$label.text(description);
299 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
315 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
300 this.$label.show();
316 this.$label.show();
301 }
317 }
302 return ProgressView.__super__.update.apply(this);
318 return ProgressView.__super__.update.apply(this);
303 },
319 },
304 });
320 });
305 WidgetManager.register_widget_view('ProgressView', ProgressView);
321 WidgetManager.register_widget_view('ProgressView', ProgressView);
306
322
307
323
308 // Return the slider and text views so they can be inheritted to create the
324 // Return the slider and text views so they can be inheritted to create the
309 // float versions.
325 // float versions.
310 return [IntSliderView, IntTextView];
326 return [IntSliderView, IntTextView];
311 });
327 });
@@ -1,12 +1,12 b''
1 from .widget import Widget, DOMWidget, CallbackDispatcher
1 from .widget import Widget, DOMWidget, CallbackDispatcher
2
2
3 from .widget_bool import CheckboxWidget, ToggleButtonWidget
3 from .widget_bool import CheckboxWidget, ToggleButtonWidget
4 from .widget_button import ButtonWidget
4 from .widget_button import ButtonWidget
5 from .widget_container import ContainerWidget, PopupWidget
5 from .widget_container import ContainerWidget, PopupWidget
6 from .widget_float import FloatTextWidget, BoundedFloatTextWidget, FloatSliderWidget, FloatProgressWidget
6 from .widget_float import FloatTextWidget, BoundedFloatTextWidget, FloatSliderWidget, FloatProgressWidget
7 from .widget_image import ImageWidget
7 from .widget_image import ImageWidget
8 from .widget_int import IntTextWidget, BoundedIntTextWidget, IntSliderWidget, IntProgressWidget
8 from .widget_int import IntTextWidget, BoundedIntTextWidget, IntSliderWidget, IntProgressWidget, IntRangeSliderWidget
9 from .widget_selection import RadioButtonsWidget, ToggleButtonsWidget, DropdownWidget, SelectWidget
9 from .widget_selection import RadioButtonsWidget, ToggleButtonsWidget, DropdownWidget, SelectWidget
10 from .widget_selectioncontainer import TabWidget, AccordionWidget
10 from .widget_selectioncontainer import TabWidget, AccordionWidget
11 from .widget_string import HTMLWidget, LatexWidget, TextWidget, TextareaWidget
11 from .widget_string import HTMLWidget, LatexWidget, TextWidget, TextareaWidget
12 from .interaction import interact, interactive, fixed
12 from .interaction import interact, interactive, fixed
@@ -1,257 +1,266 b''
1 """Interact with functions using widgets."""
1 """Interact with functions using widgets."""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2013, the IPython Development Team.
4 # Copyright (c) 2013, the IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 from __future__ import print_function
15 from __future__ import print_function
16
16
17 try: # Python >= 3.3
17 try: # Python >= 3.3
18 from inspect import signature, Parameter
18 from inspect import signature, Parameter
19 except ImportError:
19 except ImportError:
20 from IPython.utils.signatures import signature, Parameter
20 from IPython.utils.signatures import signature, Parameter
21 from inspect import getcallargs
21 from inspect import getcallargs
22
22
23 from IPython.core.getipython import get_ipython
23 from IPython.core.getipython import get_ipython
24 from IPython.html.widgets import (Widget, TextWidget,
24 from IPython.html.widgets import (Widget, TextWidget,
25 FloatSliderWidget, IntSliderWidget, CheckboxWidget, DropdownWidget,
25 FloatSliderWidget, IntSliderWidget, CheckboxWidget, DropdownWidget,
26 ContainerWidget, DOMWidget)
26 ContainerWidget, DOMWidget, IntRangeSliderWidget)
27 from IPython.display import display, clear_output
27 from IPython.display import display, clear_output
28 from IPython.utils.py3compat import string_types, unicode_type
28 from IPython.utils.py3compat import string_types, unicode_type
29 from IPython.utils.traitlets import HasTraits, Any, Unicode
29 from IPython.utils.traitlets import HasTraits, Any, Unicode
30
30
31 empty = Parameter.empty
31 empty = Parameter.empty
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Classes and Functions
34 # Classes and Functions
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37
37
38 def _matches(o, pattern):
38 def _matches(o, pattern):
39 """Match a pattern of types in a sequence."""
39 """Match a pattern of types in a sequence."""
40 if not len(o) == len(pattern):
40 if not len(o) == len(pattern):
41 return False
41 return False
42 comps = zip(o,pattern)
42 comps = zip(o,pattern)
43 return all(isinstance(obj,kind) for obj,kind in comps)
43 return all(isinstance(obj,kind) for obj,kind in comps)
44
44
45
45
46 def _get_min_max_value(min, max, value=None, step=None):
46 def _get_min_max_value(min, max, value=None, step=None):
47 """Return min, max, value given input values with possible None."""
47 """Return min, max, value given input values with possible None."""
48 if value is None:
48 if value is None:
49 if not max > min:
49 if not max > min:
50 raise ValueError('max must be greater than min: (min={0}, max={1})'.format(min, max))
50 raise ValueError('max must be greater than min: (min={0}, max={1})'.format(min, max))
51 value = min + abs(min-max)/2
51 value = min + abs(min-max)/2
52 value = type(min)(value)
52 value = type(min)(value)
53 elif min is None and max is None:
53 elif min is None and max is None:
54 if value == 0.0:
54 if value == 0.0:
55 min, max, value = 0.0, 1.0, 0.5
55 min, max, value = 0.0, 1.0, 0.5
56 elif value == 0:
56 elif value == 0:
57 min, max, value = 0, 1, 0
57 min, max, value = 0, 1, 0
58 elif isinstance(value, (int, float)):
58 elif isinstance(value, (int, float)):
59 min, max = (-value, 3*value) if value > 0 else (3*value, -value)
59 min, max = (-value, 3*value) if value > 0 else (3*value, -value)
60 else:
60 else:
61 raise TypeError('expected a number, got: %r' % value)
61 raise TypeError('expected a number, got: %r' % value)
62 else:
62 else:
63 raise ValueError('unable to infer range, value from: ({0}, {1}, {2})'.format(min, max, value))
63 raise ValueError('unable to infer range, value from: ({0}, {1}, {2})'.format(min, max, value))
64 if step is not None:
64 if step is not None:
65 # ensure value is on a step
65 # ensure value is on a step
66 r = (value - min) % step
66 r = (value - min) % step
67 value = value - r
67 value = value - r
68 return min, max, value
68 return min, max, value
69
69
70 def _widget_abbrev_single_value(o):
70 def _widget_abbrev_single_value(o):
71 """Make widgets from single values, which can be used as parameter defaults."""
71 """Make widgets from single values, which can be used as parameter defaults."""
72 if isinstance(o, string_types):
72 if isinstance(o, string_types):
73 return TextWidget(value=unicode_type(o))
73 return TextWidget(value=unicode_type(o))
74 elif isinstance(o, dict):
74 elif isinstance(o, dict):
75 return DropdownWidget(values=o)
75 return DropdownWidget(values=o)
76 elif isinstance(o, bool):
76 elif isinstance(o, bool):
77 return CheckboxWidget(value=o)
77 return CheckboxWidget(value=o)
78 elif isinstance(o, float):
78 elif isinstance(o, float):
79 min, max, value = _get_min_max_value(None, None, o)
79 min, max, value = _get_min_max_value(None, None, o)
80 return FloatSliderWidget(value=o, min=min, max=max)
80 return FloatSliderWidget(value=o, min=min, max=max)
81 elif isinstance(o, int):
81 elif isinstance(o, int):
82 min, max, value = _get_min_max_value(None, None, o)
82 min, max, value = _get_min_max_value(None, None, o)
83 return IntSliderWidget(value=o, min=min, max=max)
83 return IntSliderWidget(value=o, min=min, max=max)
84 else:
84 else:
85 return None
85 return None
86
86
87 def _widget_abbrev(o):
87 def _widget_abbrev(o):
88 """Make widgets from abbreviations: single values, lists or tuples."""
88 """Make widgets from abbreviations: single values, lists or tuples."""
89 float_or_int = (float, int)
89 float_or_int = (float, int)
90 if isinstance(o, (list, tuple)):
90 if isinstance(o, (list, tuple)):
91 if o and all(isinstance(x, string_types) for x in o):
91 if o and all(isinstance(x, string_types) for x in o):
92 return DropdownWidget(values=[unicode_type(k) for k in o])
92 return DropdownWidget(values=[unicode_type(k) for k in o])
93 elif _matches(o, (float_or_int, float_or_int)):
93 elif _matches(o, (float_or_int, float_or_int)):
94 min, max, value = _get_min_max_value(o[0], o[1])
94 min, max, value = _get_min_max_value(o[0], o[1])
95 if all(isinstance(_, int) for _ in o):
95 if all(isinstance(_, int) for _ in o):
96 cls = IntSliderWidget
96 cls = IntSliderWidget
97 else:
97 else:
98 cls = FloatSliderWidget
98 cls = FloatSliderWidget
99 return cls(value=value, min=min, max=max)
99 return cls(value=value, min=min, max=max)
100 elif _matches(o, (float_or_int, float_or_int, float_or_int)):
100 elif _matches(o, (float_or_int, float_or_int, float_or_int)):
101 step = o[2]
101 step = o[2]
102 if step <= 0:
102 if step <= 0:
103 raise ValueError("step must be >= 0, not %r" % step)
103 raise ValueError("step must be >= 0, not %r" % step)
104 min, max, value = _get_min_max_value(o[0], o[1], step=step)
104 min, max, value = _get_min_max_value(o[0], o[1], step=step)
105 if all(isinstance(_, int) for _ in o):
105 if all(isinstance(_, int) for _ in o):
106 cls = IntSliderWidget
106 cls = IntSliderWidget
107 else:
107 else:
108 cls = FloatSliderWidget
108 cls = FloatSliderWidget
109 return cls(value=value, min=min, max=max, step=step)
109 return cls(value=value, min=min, max=max, step=step)
110 elif _matches(o, [float_or_int]*4):
111 min, low, high, max = o
112 if not min <= low <= high <= max:
113 raise ValueError("Range input expects min <= low <= high <= max, got %r" % o)
114 if all(isinstance(_, int) for _ in o):
115 cls = IntRangeSliderWidget
116 else:
117 cls = FloatRangeSliderWidget
118 return cls(value=(low, high), min=min, max=max)
110 else:
119 else:
111 return _widget_abbrev_single_value(o)
120 return _widget_abbrev_single_value(o)
112
121
113 def _widget_from_abbrev(abbrev, default=empty):
122 def _widget_from_abbrev(abbrev, default=empty):
114 """Build a Widget instance given an abbreviation or Widget."""
123 """Build a Widget instance given an abbreviation or Widget."""
115 if isinstance(abbrev, Widget) or isinstance(abbrev, fixed):
124 if isinstance(abbrev, Widget) or isinstance(abbrev, fixed):
116 return abbrev
125 return abbrev
117
126
118 widget = _widget_abbrev(abbrev)
127 widget = _widget_abbrev(abbrev)
119 if default is not empty and isinstance(abbrev, (list, tuple, dict)):
128 if default is not empty and isinstance(abbrev, (list, tuple, dict)):
120 # if it's not a single-value abbreviation,
129 # if it's not a single-value abbreviation,
121 # set the initial value from the default
130 # set the initial value from the default
122 try:
131 try:
123 widget.value = default
132 widget.value = default
124 except Exception:
133 except Exception:
125 # ignore failure to set default
134 # ignore failure to set default
126 pass
135 pass
127 if widget is None:
136 if widget is None:
128 raise ValueError("%r cannot be transformed to a Widget" % (abbrev,))
137 raise ValueError("%r cannot be transformed to a Widget" % (abbrev,))
129 return widget
138 return widget
130
139
131 def _yield_abbreviations_for_parameter(param, kwargs):
140 def _yield_abbreviations_for_parameter(param, kwargs):
132 """Get an abbreviation for a function parameter."""
141 """Get an abbreviation for a function parameter."""
133 name = param.name
142 name = param.name
134 kind = param.kind
143 kind = param.kind
135 ann = param.annotation
144 ann = param.annotation
136 default = param.default
145 default = param.default
137 not_found = (name, empty, empty)
146 not_found = (name, empty, empty)
138 if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY):
147 if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY):
139 if name in kwargs:
148 if name in kwargs:
140 value = kwargs.pop(name)
149 value = kwargs.pop(name)
141 elif ann is not empty:
150 elif ann is not empty:
142 value = ann
151 value = ann
143 elif default is not empty:
152 elif default is not empty:
144 value = default
153 value = default
145 else:
154 else:
146 yield not_found
155 yield not_found
147 yield (name, value, default)
156 yield (name, value, default)
148 elif kind == Parameter.VAR_KEYWORD:
157 elif kind == Parameter.VAR_KEYWORD:
149 # In this case name=kwargs and we yield the items in kwargs with their keys.
158 # In this case name=kwargs and we yield the items in kwargs with their keys.
150 for k, v in kwargs.copy().items():
159 for k, v in kwargs.copy().items():
151 kwargs.pop(k)
160 kwargs.pop(k)
152 yield k, v, empty
161 yield k, v, empty
153
162
154 def _find_abbreviations(f, kwargs):
163 def _find_abbreviations(f, kwargs):
155 """Find the abbreviations for a function and kwargs passed to interact."""
164 """Find the abbreviations for a function and kwargs passed to interact."""
156 new_kwargs = []
165 new_kwargs = []
157 for param in signature(f).parameters.values():
166 for param in signature(f).parameters.values():
158 for name, value, default in _yield_abbreviations_for_parameter(param, kwargs):
167 for name, value, default in _yield_abbreviations_for_parameter(param, kwargs):
159 if value is empty:
168 if value is empty:
160 raise ValueError('cannot find widget or abbreviation for argument: {!r}'.format(name))
169 raise ValueError('cannot find widget or abbreviation for argument: {!r}'.format(name))
161 new_kwargs.append((name, value, default))
170 new_kwargs.append((name, value, default))
162 return new_kwargs
171 return new_kwargs
163
172
164 def _widgets_from_abbreviations(seq):
173 def _widgets_from_abbreviations(seq):
165 """Given a sequence of (name, abbrev) tuples, return a sequence of Widgets."""
174 """Given a sequence of (name, abbrev) tuples, return a sequence of Widgets."""
166 result = []
175 result = []
167 for name, abbrev, default in seq:
176 for name, abbrev, default in seq:
168 widget = _widget_from_abbrev(abbrev, default)
177 widget = _widget_from_abbrev(abbrev, default)
169 if not widget.description:
178 if not widget.description:
170 widget.description = name
179 widget.description = name
171 result.append(widget)
180 result.append(widget)
172 return result
181 return result
173
182
174 def interactive(__interact_f, **kwargs):
183 def interactive(__interact_f, **kwargs):
175 """Build a group of widgets to interact with a function."""
184 """Build a group of widgets to interact with a function."""
176 f = __interact_f
185 f = __interact_f
177 co = kwargs.pop('clear_output', True)
186 co = kwargs.pop('clear_output', True)
178 kwargs_widgets = []
187 kwargs_widgets = []
179 container = ContainerWidget()
188 container = ContainerWidget()
180 container.result = None
189 container.result = None
181 container.args = []
190 container.args = []
182 container.kwargs = dict()
191 container.kwargs = dict()
183 kwargs = kwargs.copy()
192 kwargs = kwargs.copy()
184
193
185 new_kwargs = _find_abbreviations(f, kwargs)
194 new_kwargs = _find_abbreviations(f, kwargs)
186 # Before we proceed, let's make sure that the user has passed a set of args+kwargs
195 # Before we proceed, let's make sure that the user has passed a set of args+kwargs
187 # that will lead to a valid call of the function. This protects against unspecified
196 # that will lead to a valid call of the function. This protects against unspecified
188 # and doubly-specified arguments.
197 # and doubly-specified arguments.
189 getcallargs(f, **{n:v for n,v,_ in new_kwargs})
198 getcallargs(f, **{n:v for n,v,_ in new_kwargs})
190 # Now build the widgets from the abbreviations.
199 # Now build the widgets from the abbreviations.
191 kwargs_widgets.extend(_widgets_from_abbreviations(new_kwargs))
200 kwargs_widgets.extend(_widgets_from_abbreviations(new_kwargs))
192
201
193 # This has to be done as an assignment, not using container.children.append,
202 # This has to be done as an assignment, not using container.children.append,
194 # so that traitlets notices the update. We skip any objects (such as fixed) that
203 # so that traitlets notices the update. We skip any objects (such as fixed) that
195 # are not DOMWidgets.
204 # are not DOMWidgets.
196 c = [w for w in kwargs_widgets if isinstance(w, DOMWidget)]
205 c = [w for w in kwargs_widgets if isinstance(w, DOMWidget)]
197 container.children = c
206 container.children = c
198
207
199 # Build the callback
208 # Build the callback
200 def call_f(name, old, new):
209 def call_f(name, old, new):
201 container.kwargs = {}
210 container.kwargs = {}
202 for widget in kwargs_widgets:
211 for widget in kwargs_widgets:
203 value = widget.value
212 value = widget.value
204 container.kwargs[widget.description] = value
213 container.kwargs[widget.description] = value
205 if co:
214 if co:
206 clear_output(wait=True)
215 clear_output(wait=True)
207 try:
216 try:
208 container.result = f(**container.kwargs)
217 container.result = f(**container.kwargs)
209 except Exception as e:
218 except Exception as e:
210 ip = get_ipython()
219 ip = get_ipython()
211 if ip is None:
220 if ip is None:
212 container.log.warn("Exception in interact callback: %s", e, exc_info=True)
221 container.log.warn("Exception in interact callback: %s", e, exc_info=True)
213 else:
222 else:
214 ip.showtraceback()
223 ip.showtraceback()
215
224
216 # Wire up the widgets
225 # Wire up the widgets
217 for widget in kwargs_widgets:
226 for widget in kwargs_widgets:
218 widget.on_trait_change(call_f, 'value')
227 widget.on_trait_change(call_f, 'value')
219
228
220 container.on_displayed(lambda _: call_f(None, None, None))
229 container.on_displayed(lambda _: call_f(None, None, None))
221
230
222 return container
231 return container
223
232
224 def interact(__interact_f=None, **kwargs):
233 def interact(__interact_f=None, **kwargs):
225 """interact(f, **kwargs)
234 """interact(f, **kwargs)
226
235
227 Interact with a function using widgets."""
236 Interact with a function using widgets."""
228 # positional arg support in: https://gist.github.com/8851331
237 # positional arg support in: https://gist.github.com/8851331
229 if __interact_f is not None:
238 if __interact_f is not None:
230 # This branch handles the cases:
239 # This branch handles the cases:
231 # 1. interact(f, **kwargs)
240 # 1. interact(f, **kwargs)
232 # 2. @interact
241 # 2. @interact
233 # def f(*args, **kwargs):
242 # def f(*args, **kwargs):
234 # ...
243 # ...
235 f = __interact_f
244 f = __interact_f
236 w = interactive(f, **kwargs)
245 w = interactive(f, **kwargs)
237 f.widget = w
246 f.widget = w
238 display(w)
247 display(w)
239 return f
248 return f
240 else:
249 else:
241 # This branch handles the case:
250 # This branch handles the case:
242 # @interact(a=30, b=40)
251 # @interact(a=30, b=40)
243 # def f(*args, **kwargs):
252 # def f(*args, **kwargs):
244 # ...
253 # ...
245 def dec(f):
254 def dec(f):
246 w = interactive(f, **kwargs)
255 w = interactive(f, **kwargs)
247 f.widget = w
256 f.widget = w
248 display(w)
257 display(w)
249 return f
258 return f
250 return dec
259 return dec
251
260
252 class fixed(HasTraits):
261 class fixed(HasTraits):
253 """A pseudo-widget whose value is fixed and never synced to the client."""
262 """A pseudo-widget whose value is fixed and never synced to the client."""
254 value = Any(help="Any Python object")
263 value = Any(help="Any Python object")
255 description = Unicode('', help="Any Python object")
264 description = Unicode('', help="Any Python object")
256 def __init__(self, value, **kwargs):
265 def __init__(self, value, **kwargs):
257 super(fixed, self).__init__(value=value, **kwargs)
266 super(fixed, self).__init__(value=value, **kwargs)
@@ -1,60 +1,90 b''
1 """IntWidget class.
1 """IntWidget class.
2
2
3 Represents an unbounded int using a widget.
3 Represents an unbounded int using a widget.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from .widget import DOMWidget
16 from .widget import DOMWidget
17 from IPython.utils.traitlets import Unicode, CInt, Bool, Enum
17 from IPython.utils.traitlets import Unicode, CInt, Bool, Enum, Tuple
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Classes
20 # Classes
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 class _IntWidget(DOMWidget):
22 class _IntWidget(DOMWidget):
23 value = CInt(0, help="Int value", sync=True)
23 value = CInt(0, help="Int value", sync=True)
24 disabled = Bool(False, help="Enable or disable user changes", sync=True)
24 disabled = Bool(False, help="Enable or disable user changes", sync=True)
25 description = Unicode(help="Description of the value this widget represents", sync=True)
25 description = Unicode(help="Description of the value this widget represents", sync=True)
26
26
27
27
28 class _BoundedIntWidget(_IntWidget):
28 class _BoundedIntWidget(_IntWidget):
29 step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True)
29 step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True)
30 max = CInt(100, help="Max value", sync=True)
30 max = CInt(100, help="Max value", sync=True)
31 min = CInt(0, help="Min value", sync=True)
31 min = CInt(0, help="Min value", sync=True)
32
32
33 def __init__(self, *pargs, **kwargs):
33 def __init__(self, *pargs, **kwargs):
34 """Constructor"""
34 """Constructor"""
35 DOMWidget.__init__(self, *pargs, **kwargs)
35 DOMWidget.__init__(self, *pargs, **kwargs)
36 self.on_trait_change(self._validate, ['value', 'min', 'max'])
36 self.on_trait_change(self._validate, ['value', 'min', 'max'])
37
37
38 def _validate(self, name, old, new):
38 def _validate(self, name, old, new):
39 """Validate value, max, min."""
39 """Validate value, max, min."""
40 if self.min > new or new > self.max:
40 if self.min > new or new > self.max:
41 self.value = min(max(new, self.min), self.max)
41 self.value = min(max(new, self.min), self.max)
42
42
43
43
44 class IntTextWidget(_IntWidget):
44 class IntTextWidget(_IntWidget):
45 _view_name = Unicode('IntTextView', sync=True)
45 _view_name = Unicode('IntTextView', sync=True)
46
46
47
47
48 class BoundedIntTextWidget(_BoundedIntWidget):
48 class BoundedIntTextWidget(_BoundedIntWidget):
49 _view_name = Unicode('IntTextView', sync=True)
49 _view_name = Unicode('IntTextView', sync=True)
50
50
51
51
52 class IntSliderWidget(_BoundedIntWidget):
52 class IntSliderWidget(_BoundedIntWidget):
53 _view_name = Unicode('IntSliderView', sync=True)
53 _view_name = Unicode('IntSliderView', sync=True)
54 orientation = Enum([u'horizontal', u'vertical'], u'horizontal',
54 orientation = Enum([u'horizontal', u'vertical'], u'horizontal',
55 help="Vertical or horizontal.", sync=True)
55 help="Vertical or horizontal.", sync=True)
56 range = Bool(False, help="Display a range selector", sync=True)
56 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
57 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
57
58
58
59
59 class IntProgressWidget(_BoundedIntWidget):
60 class IntProgressWidget(_BoundedIntWidget):
60 _view_name = Unicode('ProgressView', sync=True)
61 _view_name = Unicode('ProgressView', sync=True)
62
63 class _IntRangeWidget(_IntWidget):
64 value = Tuple(CInt, CInt, default_value=(0, 1), help="Low and high int values", sync=True)
65
66 class _BoundedIntRangeWidget(_IntRangeWidget):
67 step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True)
68 max = CInt(100, help="Max value", sync=True)
69 min = CInt(0, help="Min value", sync=True)
70
71 def __init__(self, *pargs, **kwargs):
72 """Constructor"""
73 DOMWidget.__init__(self, *pargs, **kwargs)
74 self.on_trait_change(self._validate, ['value', 'min', 'max'])
75
76 def _validate(self, name, old, new):
77 """Validate min <= low <= high <= max"""
78 if name == "value":
79 low, high = new
80 low = max(low, self.min)
81 high = min(high, self.max)
82 self.value = (min(low, high), max(low, high))
83
84
85 class IntRangeSliderWidget(_BoundedIntRangeWidget):
86 _view_name = Unicode('IntSliderView', sync=True)
87 orientation = Enum([u'horizontal', u'vertical'], u'horizontal',
88 help="Vertical or horizontal.", sync=True)
89 range = Bool(True, help="Display a range selector", sync=True)
90 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
General Comments 0
You need to be logged in to leave comments. Login now