Skip to content

Commit

Permalink
Added editable parameter to BokehServerWidget
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Apr 7, 2017
1 parent ef826b7 commit c4f51fc
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 32 deletions.
64 changes: 41 additions & 23 deletions holoviews/plotting/bokeh/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import numpy as np
from bokeh.io import _CommsHandle
from bokeh.util.notebook import get_comms
from bokeh.models.widgets import Select, Slider, AutocompleteInput, TextInput
from bokeh.models.widgets import Select, Slider, AutocompleteInput, TextInput, Div
from bokeh.layouts import layout, gridplot, widgetbox, row, column

from ...core import Store, NdMapping, OrderedDict
Expand All @@ -27,14 +27,8 @@ class BokehServerWidgets(param.Parameterized):
and dropdown widgets letting you select non-numeric values.
"""

basejs = param.String(default=None, precedence=-1, doc="""
Defines the local CSS file to be loaded for this widget.""")

extensionjs = param.String(default=None, precedence=-1, doc="""
Optional javascript extension file for a particular backend.""")

css = param.String(default=None, precedence=-1, doc="""
Defines the local CSS file to be loaded for this widget.""")
editable = param.Boolean(default=False, doc="""
Whether the slider text fields should be editable.""")

position = param.ObjectSelector(default='right',
objects=['right', 'left', 'above', 'below'])
Expand All @@ -43,9 +37,18 @@ class BokehServerWidgets(param.Parameterized):
objects=['fixed', 'stretch_both', 'scale_width',
'scale_height', 'scale_both'])

width = param.Integer(default=200, doc="""
width = param.Integer(default=250, doc="""
Width of the widget box in pixels""")

basejs = param.String(default=None, precedence=-1, doc="""
Defines the local CSS file to be loaded for this widget.""")

extensionjs = param.String(default=None, precedence=-1, doc="""
Optional javascript extension file for a particular backend.""")

css = param.String(default=None, precedence=-1, doc="""
Defines the local CSS file to be loaded for this widget.""")

def __init__(self, plot, renderer=None, **params):
super(BokehServerWidgets, self).__init__(**params)
self.plot = plot
Expand Down Expand Up @@ -75,7 +78,7 @@ def __init__(self, plot, renderer=None, **params):


@classmethod
def create_widget(self, dim, holomap=None):
def create_widget(self, dim, holomap=None, editable=False):
""""
Given a Dimension creates bokeh widgets to select along that
dimension. For numeric data a slider widget is created which
Expand All @@ -92,8 +95,11 @@ def create_widget(self, dim, holomap=None):
if all(isnumeric(v) for v in dim.values):
values = dim.values
labels = [unicode(dim.pprint_value(v)) for v in dim.values]
label = AutocompleteInput(value=labels[0], completions=labels,
title=dim.pprint_label)
if editable:
label = AutocompleteInput(value=labels[0], completions=labels,
title=dim.pprint_label)
else:
label = Div(text='<b>%s</b>' % dim.pprint_value_string(labels[0]))
widget = Slider(value=0, end=len(dim.values)-1, title=None, step=1)
mapping = list(zip(values, labels))
else:
Expand All @@ -109,16 +115,22 @@ def create_widget(self, dim, holomap=None):
step = 1
else:
step = 10**((round(math.log10(dim_range))-3))
label = TextInput(value=str(start), title=dim.pprint_label)
if editable:
label = TextInput(value=str(start), title=dim.pprint_label)
else:
label = Div(text='<b>%s</b>' % dim.pprint_value_string(start))
widget = Slider(value=start, start=start,
end=end, step=step, title=None)
else:
values = (dim.values if dim.values else
list(unique_array(holomap.dimension_values(dim.name))))
labels = [dim.pprint_value(v) for v in values]
if isinstance(values[0], np.datetime64) or isnumeric(values[0]):
label = AutocompleteInput(value=labels[0], completions=labels,
title=dim.pprint_label)
if editable:
label = AutocompleteInput(value=labels[0], completions=labels,
title=dim.pprint_label)
else:
label = Div(text='<b>%s</b>' % (dim.pprint_value_string(labels[0])))
widget = Slider(value=0, end=len(values)-1, title=None, step=1)
else:
widget = Select(title=dim.pprint_label, value=values[0],
Expand All @@ -136,8 +148,8 @@ def get_widgets(self):
mappings = {}
for dim in self.mock_obj.kdims:
holomap = None if self.plot.dynamic else self.mock_obj
widget, label, mapping = self.create_widget(dim, holomap)
if label is not None:
widget, label, mapping = self.create_widget(dim, holomap, self.editable)
if label is not None and not isinstance(label, Div):
label.on_change('value', partial(self.on_change, dim, 'label'))
widget.on_change('value', partial(self.on_change, dim, 'widget'))
widgets[dim.pprint_label] = (label, widget)
Expand Down Expand Up @@ -184,21 +196,27 @@ def update(self):
label, widget = self.widgets[dim_label]
if widget_type == 'label':
if isinstance(label, AutocompleteInput):
value = self.reverse_lookups[dim_label][new]
value = [new]
widget.value = value
else:
widget.value = float(new)
elif label:
if isinstance(label, AutocompleteInput):
text = self.lookups[dim_label][new]
lookups = self.lookups.get(dim_label)
if not self.editable:
if lookups:
new = list(lookups.keys())[widget.value]
label.text = '<b>%s</b>' % dim.pprint_value_string(new)
elif isinstance(label, AutocompleteInput):
text = lookups[new]
label.value = text
else:
label.value = dim.pprint_value(new)

key = []
for dim, (label, widget) in self.widgets.items():
if label and isinstance(label, AutocompleteInput):
val = list(self.lookups[dim].keys())[widget.value]
lookups = self.lookups.get(dim)
if label and lookups:
val = list(lookups.keys())[widget.value]
else:
val = widget.value
key.append(val)
Expand Down
43 changes: 34 additions & 9 deletions tests/testbokehwidgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from holoviews.plotting.bokeh.widgets import BokehServerWidgets
from holoviews.plotting.bokeh.util import bokeh_version

from bokeh.models.widgets import Select, Slider, AutocompleteInput, TextInput
from bokeh.models.widgets import Select, Slider, AutocompleteInput, TextInput, Div
except:
BokehServerWidgets = None

Expand All @@ -23,7 +23,7 @@ def setUp(self):

def test_bokeh_server_dynamic_range_int(self):
dim = Dimension('x', range=(3, 11))
widget, label, mapping = BokehServerWidgets.create_widget(dim)
widget, label, mapping = BokehServerWidgets.create_widget(dim, editable=True)
self.assertIsInstance(widget, Slider)
self.assertEqual(widget.value, 3)
self.assertEqual(widget.start, 3)
Expand All @@ -36,7 +36,7 @@ def test_bokeh_server_dynamic_range_int(self):

def test_bokeh_server_dynamic_range_float(self):
dim = Dimension('x', range=(3.1, 11.2))
widget, label, mapping = BokehServerWidgets.create_widget(dim)
widget, label, mapping = BokehServerWidgets.create_widget(dim, editable=True)
self.assertIsInstance(widget, Slider)
self.assertEqual(widget.value, 3.1)
self.assertEqual(widget.start, 3.1)
Expand All @@ -47,10 +47,22 @@ def test_bokeh_server_dynamic_range_float(self):
self.assertEqual(label.value, '3.1')
self.assertIs(mapping, None)

def test_bokeh_server_dynamic_range_not_editable(self):
dim = Dimension('x', range=(3.1, 11.2))
widget, label, mapping = BokehServerWidgets.create_widget(dim, editable=False)
self.assertIsInstance(widget, Slider)
self.assertEqual(widget.value, 3.1)
self.assertEqual(widget.start, 3.1)
self.assertEqual(widget.end, 11.2)
self.assertEqual(widget.step, 0.01)
self.assertIsInstance(label, Div)
self.assertEqual(label.text, '<b>%s</b>' % dim.pprint_value_string(3.1))
self.assertIs(mapping, None)

def test_bokeh_server_dynamic_values_int(self):
values = list(range(3, 11))
dim = Dimension('x', values=values)
widget, label, mapping = BokehServerWidgets.create_widget(dim)
widget, label, mapping = BokehServerWidgets.create_widget(dim, editable=True)
self.assertIsInstance(widget, Slider)
self.assertEqual(widget.value, 0)
self.assertEqual(widget.start, 0)
Expand All @@ -61,10 +73,23 @@ def test_bokeh_server_dynamic_values_int(self):
self.assertEqual(label.value, '3')
self.assertEqual(mapping, [(v, dim.pprint_value(v)) for v in values])

def test_bokeh_server_dynamic_values_float(self):
def test_bokeh_server_dynamic_values_float_not_editable(self):
values = list(np.linspace(3.1, 11.2, 7))
dim = Dimension('x', values=values)
widget, label, mapping = BokehServerWidgets.create_widget(dim, editable=False)
self.assertIsInstance(widget, Slider)
self.assertEqual(widget.value, 0)
self.assertEqual(widget.start, 0)
self.assertEqual(widget.end, 6)
self.assertEqual(widget.step, 1)
self.assertIsInstance(label, Div)
self.assertEqual(label.text, '<b>%s</b>' % dim.pprint_value_string(3.1))
self.assertEqual(mapping, [(v, dim.pprint_value(v)) for v in values])

def test_bokeh_server_dynamic_values_float_editable(self):
values = list(np.linspace(3.1, 11.2, 7))
dim = Dimension('x', values=values)
widget, label, mapping = BokehServerWidgets.create_widget(dim)
widget, label, mapping = BokehServerWidgets.create_widget(dim, editable=True)
self.assertIsInstance(widget, Slider)
self.assertEqual(widget.value, 0)
self.assertEqual(widget.start, 0)
Expand All @@ -78,7 +103,7 @@ def test_bokeh_server_dynamic_values_float(self):
def test_bokeh_server_dynamic_values_str(self):
values = [chr(65+i) for i in range(10)]
dim = Dimension('x', values=values)
widget, label, mapping = BokehServerWidgets.create_widget(dim)
widget, label, mapping = BokehServerWidgets.create_widget(dim, editable=True)
self.assertIsInstance(widget, Select)
self.assertEqual(widget.value, 'A')
self.assertEqual(widget.options, list(zip(values, values)))
Expand All @@ -89,7 +114,7 @@ def test_bokeh_server_dynamic_values_str(self):
def test_bokeh_server_static_numeric_values(self):
dim = Dimension('x')
ndmap = NdMapping({i: None for i in range(3, 12)}, kdims=['x'])
widget, label, mapping = BokehServerWidgets.create_widget(dim, ndmap)
widget, label, mapping = BokehServerWidgets.create_widget(dim, ndmap, editable=True)
self.assertIsInstance(widget, Slider)
self.assertEqual(widget.value, 0)
self.assertEqual(widget.start, 0)
Expand All @@ -104,7 +129,7 @@ def test_bokeh_server_dynamic_values_str(self):
keys = [chr(65+i) for i in range(10)]
ndmap = NdMapping({i: None for i in keys}, kdims=['x'])
dim = Dimension('x')
widget, label, mapping = BokehServerWidgets.create_widget(dim, ndmap)
widget, label, mapping = BokehServerWidgets.create_widget(dim, ndmap, editable=True)
self.assertIsInstance(widget, Select)
self.assertEqual(widget.value, 'A')
self.assertEqual(widget.options, list(zip(keys, keys)))
Expand Down

0 comments on commit c4f51fc

Please sign in to comment.