From a8afcfad951d455efa6e21628d4e8fde6754b110 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Fri, 7 Apr 2017 11:34:05 +0100
Subject: [PATCH] Added editable parameter to BokehServerWidget
---
holoviews/plotting/bokeh/widgets.py | 65 +++++++++++++++++++----------
tests/testbokehwidgets.py | 43 +++++++++++++++----
2 files changed, 76 insertions(+), 32 deletions(-)
diff --git a/holoviews/plotting/bokeh/widgets.py b/holoviews/plotting/bokeh/widgets.py
index 9a7a00e39a..39b51658d1 100644
--- a/holoviews/plotting/bokeh/widgets.py
+++ b/holoviews/plotting/bokeh/widgets.py
@@ -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
@@ -27,14 +27,9 @@ 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. Disabled
+ by default for a more compact widget layout.""")
position = param.ObjectSelector(default='right',
objects=['right', 'left', 'above', 'below'])
@@ -43,9 +38,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
@@ -75,7 +79,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
@@ -92,8 +96,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='%s' % 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:
@@ -109,7 +116,10 @@ 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='%s' % dim.pprint_value_string(start))
widget = Slider(value=start, start=start,
end=end, step=step, title=None)
else:
@@ -117,8 +127,11 @@ def create_widget(self, dim, holomap=None):
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='%s' % (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],
@@ -136,8 +149,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)
@@ -184,21 +197,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 = '%s' % 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)
diff --git a/tests/testbokehwidgets.py b/tests/testbokehwidgets.py
index fc7afdc616..391c83185b 100644
--- a/tests/testbokehwidgets.py
+++ b/tests/testbokehwidgets.py
@@ -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
@@ -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)
@@ -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)
@@ -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, '%s' % 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)
@@ -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, '%s' % 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)
@@ -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)))
@@ -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)
@@ -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)))