flexxui / flexx

Write desktop and web apps in pure Python
http://flexx.readthedocs.io
BSD 2-Clause "Simplified" License
3.25k stars 258 forks source link

Bokeh DataTable #713

Open amitdeliwala opened 2 years ago

amitdeliwala commented 2 years ago

I am attempting to add a Bokeh DataTable object via Flexx. Because it uses a ColumnDataSource, I assume that we can make it work very similarly to the BokehWidget for plots. Just as we create a figure, we can create a DataTable from a ColumnDataSource:

x_table = [randint(0, 100) for i in range(10)]
y_table = [randint(0, 100) for i in range(10)]
total = np.sum([x_table, y_table], axis = 0)

self.datatable_source = ColumnDataSource(dict(x=x_table, y=y_table, total = total))
datatable_columns = [TableColumn(field="x", title="x"),
                    TableColumn(field="y", title="y"),
                    TableColumn(field="total", title="total")]
self.data_table= DataTable(source=self.datatable_source,
                            # columns=datatable_columns, 
                            width=600, height=600, editable = True)

I have created a modified BokehWidget object which works very similarly to the BokehWidget for plots:

def make_bokeh_widget_from_data_table(data_table, **kwargs):
    ns = {}
    exec("from bokeh.models.widgets import DataTable", ns, ns)  # noqa - dont trigger e.g. PyInstaller
    exec("from bokeh.embed import components", ns, ns)  # noqa - dont trigger e.g. PyInstaller
    DataTableClone, components = ns["DataTable"], ns["components"]
    # Set plot prop
    if not isinstance(data_table, DataTableClone):
        raise ValueError('plot must be a Bokeh plot object.')
    # The sizing_mode is fixed by default, but that's silly in this context

    # if plot.sizing_mode == 'fixed':
    #     plot.sizing_mode = 'stretch_both'

    # Get components and apply to widget
    script, div = components(data_table)
    script = '\n'.join(script.strip().split('\n')[1:-1])
    widget = BokehWidgetAdapted(**kwargs)
    widget.set_table_components(
        dict(script=script, div=div, id=data_table.ref['id']))
    return widget

class BokehWidgetAdapted(BokehWidget):
    @classmethod
    def from_data_table(cls, data_table, **kwargs):
        """ Create a BokehWidget using a Bokeh plot.
        """
        return make_bokeh_widget_from_data_table(data_table, **kwargs)

    table = event.Attribute(doc="""The JS-side of the Bokeh plot object.""")

    def _render_dom(self):
        return None

    @event.action
    def set_table_components(self, d):
        """ Set the plot using its script/html components.
        """
        global window
        # Embed div
        self.node.innerHTML = d.div  # We put trust in d.div
        # "exec" code
        el = window.document.createElement('script')
        el.innerHTML = d.script
        self.node.appendChild(el)
        # Get plot from id in next event-loop iter
        def gettable():
            self._table = window.Bokeh.index[d.id]
            self.__resize_table()
        window.setTimeout(gettable, 10)

    @event.reaction('size')
    def __resize_table(self, *events):
        if self.table and self.parent:
            if self.table.resize_layout:
                self.table.resize_layout()
            elif self.table.resize:
                self.table.resize()  # older
            else:
                self.table.model.document.resize()  # older still

To get these widgets working, the bokeh-tables.js, bokeh-widgets.js and bokeh-mathjax.js files are associated using app.assets.associate_asset. We can then try to add the Bokeh DataTable object:

self.table = BokehWidgetAdapted.from_data_table(self.data_table, title='Table')

This partially works! The table is rendered on the page, but the data is blank and no errors are thrown. Is there something I am missing here? I think this would be a great widget to add to Flexx to have support for tables via Bokeh. I think it should be extendable to most Bokeh widgets such as buttons, checkboxes, etc.

almarklein commented 2 years ago

Looks cool! I don't have a lot of time to look at this now though ...