widgetti / solara

A Pure Python, React-style Framework for Scaling Your Jupyter and Web Apps
https://solara.dev
MIT License
1.84k stars 134 forks source link

Feature Request: Bokeh support #202

Open theeldermillenial opened 1 year ago

theeldermillenial commented 1 year ago

Just wanted to request Bokeh integration. I realize it can be somewhat supported with the display functionality, but having integration would be nice to get analogous functionality to Altair and Plotly.

https://bokeh.org/

maartenbreddels commented 1 year ago

Bokeh rendering/loading is a bit odd, because there are two parts

  1. injecting the bokehjs library in the notebook using output_notebook()/display()
  2. rendering the bokeh plot, which expects 1 to have happened in the past, (i.e. the window.Bokeh object is expected to be available in the frontend).

In solara, 1 and 2 happen really fast, and the Bokeh object is not available yet.

However, if we poll for this, we can make this work:

from typing import Callable

import numpy as np
from bokeh.io import output_notebook
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from jupyter_bokeh import BokehModel

import solara

rng = np.random.default_rng()

@solara.component_vue('bokeh_loaded.vue')
def BokehLoaded(loaded: bool, on_loaded: Callable[[bool], None]):
    pass

def PlotBokeh(fig: figure):
    loaded = solara.use_reactive(False)
    with solara.Column():
        output_notebook(hide_banner=True)
        BokehLoaded(loaded=loaded.value, on_loaded=loaded.set)
        if loaded.value:
            BokehModel.element(model=fig)

@solara.component
def Page():
    data = {"x_values": [1, 2, 3, 4, 5], "y_values": rng.random(5)}
    source = ColumnDataSource(data=data)
    p = figure(title="Simple line example", x_axis_label="x", y_axis_label="y")
    p.line(x="x_values", y="y_values", source=source, legend_label="Temp.", line_width=2)

    def on_click():
        print("new data")
        data = {"x_values": [1, 2, 3, 4, 5], "y_values": rng.random(5)}
        source.data = data

    with solara.Card("Bokeh"):
        solara.Button(label="Click me", on_click=on_click)
        PlotBokeh(p)

bokeh_loaded.vue:

<template>
    <div v-if="!loaded">Loading bokeh...</div>
</template>
<script>
module.exports = {
    mounted() {
        const check = () => {
            if (window.Bokeh) {
                this.loaded = true;
                return;
            }
            setTimeout(check, 100);
        }
        check();
    }
}
</script>

cc @Jhsmit who is also interested in this.

cc @philippjfr if the above is correct, I wouldn't be surprised if sometimes the rendering in notebook, lab or voila fails because step 1 takes too long.

theeldermillenial commented 1 year ago

This looks like the solution I have been working towards. Almost exactly. It's taking me a bit because I don't know Vue. Your sample code will help me with my solution. I'm happy to share/contribute.

Your Vue template looks more succinct than mine because I was trying to update the Bokeh plot using the Vue template.

theeldermillenial commented 1 year ago

Actually, I created a component that has a time delayed loading function. I created a Solara component to interact with Cardano wallets, and the Cardano wallet extensions in the browser take awhile to load. So I have a delay and repeat tries kind of like what you have here.

maartenbreddels commented 11 months ago

@Jhsmit you might be interested in this