import logging
from bokeh.models import (CustomJS, ColumnDataSource)
from bokeh.models.widgets import (RadioButtonGroup, CheckboxButtonGroup, Button, DataTable, TableColumn)
[docs]def get_checkbox(glyph_renderers, labels, max_checkbox_length=None):
"""
Parameters
----------
glyph_renderers: List[List[Renderer]]
list of glyph-renderers
labels: List[str]
list with strings to be put in checkbox
max_checkbox_length: None or int
maximum number of checkboxes in a single CheckboxButtonGroup, splits up the checkboxes over multiple
CheckboxButtonGroups (e.g. to simulate "line-breaks" in the visual layout)
NOTE: if this is not None, will return List[CheckboxButtonGroup]
Returns
-------
checkbox: CheckboxButtonGroup or List[CheckboxButtonGroup]
checkbox object (or list of checkbox-objects)
select_all: Button
button related to checkbox
select_none: Button
button related to checkbox
"""
code, args_checkbox = _prepare_nested_glyphs(glyph_renderers)
# Toggle all renderers in a subgroup, if their domain is set to active
code += """
for (i = 0; i < len_labels; i++) {
if (cb_obj.active.includes(i)) {
// console.log('Setting to true: ' + i + '(' + glyph_renderers[i].length + ')')
for (j = 0; j < glyph_renderers[i].length; j++) {
glyph_renderers[i][j].visible = true;
// console.log('Setting to true: ' + i + ' : ' + j)
}
} else {
// console.log('Setting to false: ' + i + '(' + glyph_renderers[i].length + ')')
for (j = 0; j < glyph_renderers[i].length; j++) {
glyph_renderers[i][j].visible = false;
// console.log('Setting to false: ' + i + ' : ' + j)
}
}
}
"""
# Create the actual checkbox-widget
callback = CustomJS(args=args_checkbox, code=code)
checkbox = CheckboxButtonGroup(labels=labels, active=list(range(len(labels))), callback=callback)
# Select all/none:
handle_list_as_string = str(list(range(len(glyph_renderers))))
code_button_tail = "checkbox.active = labels;" + code.replace('cb_obj', 'checkbox')
select_all = Button(label="All", callback=CustomJS(args=dict({'checkbox': checkbox}, **args_checkbox),
code="var labels = {}; {}".format(
handle_list_as_string, code_button_tail)))
select_none = Button(label="None", callback=CustomJS(args=dict({'checkbox': checkbox}, **args_checkbox),
code="var labels = {}; {}".format('[]', code_button_tail)))
if max_checkbox_length:
# Keep all and none buttons, but create new checkboxes and return a list
slices = list(range(0, len(glyph_renderers), max_checkbox_length)) + [len(glyph_renderers)]
checkboxes = [get_checkbox(glyph_renderers[s:e], labels[s:e])[0] for s, e in zip(slices[:-1], slices[1:])]
return checkboxes, select_all, select_none
return checkbox, select_all, select_none
[docs]def _prepare_nested_glyphs(glyph_renderers):
# First create a consecutive list of strings named glyph_renderer_i for i in len(all_renderers)
num_total_lines = sum([len(group) for group in glyph_renderers])
aliases_flattened = ['glyph_renderer' + str(i) for i in range(num_total_lines)]
# Make that list nested to sum up multiple renderers in one checkbox
aliases = []
start = 0
for group in glyph_renderers:
aliases.append(aliases_flattened[start: start+len(group)])
start += len(group)
# Flatten renderers-list to pass it to the CustomJS properly
glyph_renderers_flattened = [a for b in glyph_renderers for a in b]
args = {name: glyph for name, glyph in zip(aliases_flattened, glyph_renderers_flattened)}
# Create javascript-code
code = "len_labels = " + str(len(aliases)) + ";"
# Create nested list of glyph renderers to be toggled by a button
code += "glyph_renderers = [{}];".format(
','.join(['[' + ','.join([str(idx) for idx in group]) + ']' for group in aliases]))
return code, args
[docs]def array_to_bokeh_table(df, sortable=None, width=None, logger=None):
"""
Create bokeh-table from array.
Parameters
----------
df: pandas.DataFrame
dataframe with columns and index set
sortable: dict(str : boolean)
columns that should be sortable, default none
width: dict(str : int)
width of columns, default 100 for all
logger: logging.Logger
logger to use, if not set use default
Returns
-------
bokeh_table: bokeh.models.widgets.DataTable
bokeh object
"""
if logger is None:
logger = logging.getLogger('cave.utils.bokeh_routines.array_to_bokeh_table')
if sortable is None:
sortable = {}
if width is None:
width = {}
columns = list(df.columns.values)
data = dict(df[columns])
# Sanity checks
for attr, d in {'width': width, 'sortable': sortable}.items():
diff = set(d.keys()).difference(set(columns))
if len(diff) > 0:
logger.debug("For attr %s with value %s and columns %s there is a diff %s", attr, d, columns, diff)
raise ValueError("Illegal table description! Trying to specify '%s' for the following columns, but they "
"are not present in DataFrame: %s!" % (attr, diff))
source = ColumnDataSource(data)
columns = [TableColumn(field=header, title=header,
sortable=sortable.get(header, False),
default_sort='descending',
width=width.get(header, 100)) for header in columns
]
data_table = DataTable(source=source,
columns=columns,
height=20 + 30 * len(list(data.values())[0]),
index_position=None, # Disable index-column
)
return data_table