Good evening
My bokeh dashboard would be a lot punchier if users could hide subsets of the data on demand. I adapted SlickGrid example 5: Collapsing to bokeh (see below or this fiddle). I would like to confirm that I haven’t missed an easier approach…
Thanks,
Jan
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Collapsing DataTable</title>
<**link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/bokeh/0.12.16/bokeh.min.css" **/>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/bokeh/0.12.16/bokeh-tables.min.css" />
<style>
.toggle {
height: 9px;
width: 9px;
display: inline-block;
}
.toggle.expand {
background: url(https://cdnjs.cloudflare.com/ajax/libs/6pac-slickgrid/2.4.0/images/expand.gif) no-repeat center center;
}
.toggle.collapse {
background: url(https://cdnjs.cloudflare.com/ajax/libs/6pac-slickgrid/2.4.0/images/collapse.gif) no-repeat center center;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bokeh/0.12.16/bokeh.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bokeh/0.12.16/bokeh-api.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bokeh/0.12.16/bokeh-tables.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<div class="bk-root" id="bokeh-root"></div>
<script>
**const ****DataTable = Bokeh.require(‘models/widgets/tables/data_table’);
**class **TreeFormatter **extends **Bokeh.Tables.**CellFormatter **{
constructor(provider) {
super();
this.**data **= provider;
}
doFormat(row, cell, value, columnDef, dataContext) {
const
spacer = <span style='display:inline-block;height:1px;width:**${15 * dataContext.**indent**}**px'></span>
,
esc = {’&’: ‘&’, ‘<’: ‘<’, ‘"’: ‘"’, “’”: ‘'’},
safe = (value + ‘’).replace(/&<@’/g, c => esc[c]),
next = this.data.getItem(dataContext[DataTable.DTINDEX_NAME] + 1),
toggle = next[DataTable.DTINDEX_NAME] && next.**indent **> dataContext.**indent
? (dataContext._collapsed **? **‘toggle expand’ **: ‘toggle collapse’) : ‘toggle’;
**return ******${spacer}** <span class='**${toggle}**'></span> **${value}**
**}
}
**function **filter_cb(source) {
const collapsed = source.data._collapsed;
**return **source.data.parent.map((q, i, a) => {
**for **(**let **p = q; p !== null; p = a[p]) {
**if **(collapsed[p])
**return false
**}
**return true
**})
}
**const
*****source *= new **Bokeh.ColumnDataSource({
data: {
parent: [null, null, 1, 1, null],
indent: [0, 0, 1, 1, 0],
name: [‘Alice’, ‘Bob’, ‘Cheryl’, ‘Diana’, ‘Eric’],
_collapsed: [false, false, false, false, false],
age: [45, 31, 61, 37, 13]
}
}),
***filter ***= **new **Bokeh.CustomJSFilter({code: return (**${*filter_cb*.toString()}**)(source)
}),
***view ***= **new **Bokeh.CDSView({source, filters: [filter]}),
***provider ***= **new **DataTable.DataProvider(source, view),
***columns ***=[
**new **Bokeh.Tables.TableColumn({field: ‘name’, title: ‘Name’, formatter: **new **TreeFormatter(provider)}),
**new **Bokeh.Tables.TableColumn({field: ‘age’, title: ‘Age’}),
],
***table ***= **new **Bokeh.Tables.DataTable({source, view, columns, reorderable: false, index_position: null});
**const
*****views ***= Bokeh.Plotting.show(table, ‘#bokeh-root’),
[{grid}] = Object.values(views).filter(view => ‘grid’ ****in view);
grid.onClick.subscribe((e, args) => {
if ($(e.target).hasClass(‘toggle’)) {
**const **collapsed = provider.getField(args.row, ‘_collapsed’);
**if **(collapsed !== undefined) {
provider.setField(args.row, ‘_collapsed’, !collapsed)
}
e.stopImmediatePropagation()
}
})
</script>
</body>
</html>