Component for bi-directional communication with bokeh

Many thanks @andfanilo .

Hey @blob123,

I whipped up something based on my previous answer.
Got it working working with some bokeh magic. Is it what you’re looking for ?

from streamlit_bokeh_events import streamlit_bokeh_events
from bokeh.plotting import figure
from bokeh.models import PointDrawTool, ColumnDataSource, CustomJS
import pandas as pd
import streamlit as st


def plot_and_move(df):
    p = figure(x_range=(0, 10), y_range=(0, 10), tools=[],
            title='Point Draw Tool')

    source = ColumnDataSource(df)

    renderer = p.scatter(x='x', y='y', source=source, size=10)

    draw_tool = PointDrawTool(renderers=[renderer])
    p.add_tools(draw_tool)
    p.toolbar.active_tap = draw_tool

    source.js_on_change("data", CustomJS(
        code="""
            document.dispatchEvent(
                new CustomEvent("DATA_CHANGED", {detail: cb_obj.data})
            )
        """
    ))

    event_result = streamlit_bokeh_events(p, key="foo", events="DATA_CHANGED", refresh_on_update=False, debounce_time=0)

    if event_result:
        df_values = event_result.get("DATA_CHANGED")
        return pd.DataFrame(df_values, index=df_values.pop("index"))
    else:
        return df


df = pd.DataFrame({
        'x': [1, 5, 9], 'y': [1, 5, 9]
})

st.write(plot_and_move(df))

Hope it helps !

2 Likes

You’re becoming quite the Bokeh master there :stuck_out_tongue_winking_eye:

2 Likes

@ash2shukla, this is just awesome !

Many thanks !!

1 Like

Sorry @ash2shukla, by any chance is it possible to prevent the figure from creating new dots when clicking everywhere on the scatter plot ?

Sure, just pass add=False in the PointDrawTool constructor like this,

Change this

draw_tool = PointDrawTool(renderers=[renderer])

To this

draw_tool = PointDrawTool(renderers=[renderer], add=False)

Also check this for more customizations, eg custom icon in toolbar etc. bokeh.models.tools — Bokeh 2.2.3 Documentation

Hope it helps ! :slight_smile:

It does !
Many thanks @ash2shukla.

@ash2shukla This is awesome. Thanks for sharing.

1 Like

Hey @iwatobipen, Welcome to the community !

For my work, I need to have two dot plots side by side. When I select a subset of dots from the left plot, I need to update the right plot, for example, just change the color of the dots in the same area. Is this doable with your component?

This component is only intended to be used when u want some data back from your bokeh plots to your python code. But for your usecase you dont necessarily need to do that, you can achieve it using streamlit’s inbuilt st.bokeh_chart with some bokeh magic.
Whipped up a quick snipped for you,

import streamlit as st
from random import random
from bokeh.layouts import row
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.plotting import figure

x = [random() for x in range(500)]
y = [random() for y in range(500)]

s1 = ColumnDataSource(data=dict(x=x, y=y))
p1 = figure(plot_width=400, plot_height=400, tools="lasso_select", title="Select Here")
p1.circle('x', 'y', source=s1, alpha=0.6)

s2 = ColumnDataSource(data=dict(x=[], y=[]))
p2 = figure(plot_width=400, plot_height=400, x_range=(0, 1), y_range=(0, 1),
            tools="", title="Watch Here")
p2.circle('x', 'y', source=s2, alpha=0.6)

s1.selected.js_on_change('indices', CustomJS(args=dict(s1=s1, s2=s2), code="""
        var inds = cb_obj.indices;
        var d1 = s1.data;
        var d2 = s2.data;
        d2['x'] = []
        d2['y'] = []
        for (var i = 0; i < inds.length; i++) {
            d2['x'].push(d1['x'][inds[i]])
            d2['y'].push(d1['y'][inds[i]])
        }
        s2.change.emit();
    """)
)

layout = row(p1, p2)

st.bokeh_chart(layout)

It will look like this,

I have converted the example from this link into streamlit compatible code, JavaScript callbacks — Bokeh 2.3.0 Documentation
Look into CustomJS for selections section it should do your job.

Hope it helps ! :slight_smile:

PS. Apologies for late reply!

1 Like

Hi, Is there still no support for the use_container_width ?
Thanks a lot

Hi @LeonDpl , I have tried to put a workaround for use_container_width but it hasnt worked for me yet, I’d me more than happy to have a PR for it ! :slight_smile:

1 Like

@ash2shukla I have 4/5 plots in one script and when I click on any row, everyline of code is executed and all plots are updated. How to set a flag that when I click on a row in datatable, only below lines are executed.

    if result:
        if result.get("INDEX_SELECT"):
               st.write(df.iloc[result.get("INDEX_SELECT")])

Hi @kamal_kumawat sorry for late response,
Can you explain the problem in more detail ?
If you want to render a specific graph only on a specific event then u could probably do something like,

if result:
   if result.get("EVENT_1"):
        render_graph_1()
   elif results.get("EVENT_2"):
       render_graph_2()
   ....

Dear all,
I just came across this example from @ash2shukla,

But it seems to not workon on my computer:
The scatter does not show, and It get a console error:

document.js:518 [bokeh] Library versions: JS (2.0.0) / Python (2.4.2)
Any clue ?

Streamlit = version 1.7.0
bokeh= 2.4.1
streamlit-bokeh-events==0.1.2
chrome = Version 98.0.4758.80 (Build officiel) (64 bits)
Python = 3.8.6

from streamlit_bokeh_events import streamlit_bokeh_events
from bokeh.plotting import figure
from bokeh.models import PointDrawTool, ColumnDataSource, CustomJS
import pandas as pd
import streamlit as st


def plot_and_move(df):
    p = figure(x_range=(0, 10), y_range=(0, 10), tools=[],
            title='Point Draw Tool')

    source = ColumnDataSource(df)

    renderer = p.scatter(x='x', y='y', source=source, size=10)

    draw_tool = PointDrawTool(renderers=[renderer])
    p.add_tools(draw_tool)
    p.toolbar.active_tap = draw_tool

    source.js_on_change("data", CustomJS(
        code="""
            document.dispatchEvent(
                new CustomEvent("DATA_CHANGED", {detail: cb_obj.data})
            )
        """
    ))

    event_result = streamlit_bokeh_events(p, key="foo", events="DATA_CHANGED", refresh_on_update=False, debounce_time=0)

    if event_result:
        df_values = event_result.get("DATA_CHANGED")
        return pd.DataFrame(df_values, index=df_values.pop("index"))
    else:
        return df


df = pd.DataFrame({
        'x': [1, 5, 9], 'y': [1, 5, 9]
})

st.write(plot_and_move(df))

EDIT: I did a manual update from 0.1.2 to 0.1.3 but it still not working.
Sometime I get the error:
FileNotFoundError: [Errno 2] No such file or directory: '/home/USER/.virtualenvs/PYENV/lib/python3.8/site-packages/streamlit_bokeh_events/frontend/build/bootstrap.min.css.map'
I downgrade bokeh and now it works :slight_smile:

1 Like

hi @BenBen same pb here, tried to downgrade my bokeh version to 2.3 and 2.2 but still not working.
@ash2shukla is there some specific requirements ?

According to GitHub ‘archived’ stats, it seems this great component is no longer supported. Could somebody recommend a good alternative?

I’m using bokeh 2.2.2 and downgrade the jinja to 3.0.3, and it work for me